diff --git a/doc/Makefile.am b/doc/Makefile.am
index eb35605ad5..18ff094445 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -20,6 +20,7 @@ nobase_dist_doc_DATA += examples/kea4/advanced.json
nobase_dist_doc_DATA += examples/kea4/backends.json
nobase_dist_doc_DATA += examples/kea4/cassandra.json
nobase_dist_doc_DATA += examples/kea4/classify.json
+nobase_dist_doc_DATA += examples/kea4/classify2.json
nobase_dist_doc_DATA += examples/kea4/comments.json
nobase_dist_doc_DATA += examples/kea4/dhcpv4-over-dhcpv6.json
nobase_dist_doc_DATA += examples/kea4/hooks.json
@@ -36,6 +37,7 @@ nobase_dist_doc_DATA += examples/kea6/advanced.json
nobase_dist_doc_DATA += examples/kea6/backends.json
nobase_dist_doc_DATA += examples/kea6/cassandra.json
nobase_dist_doc_DATA += examples/kea6/classify.json
+nobase_dist_doc_DATA += examples/kea6/classify2.json
nobase_dist_doc_DATA += examples/kea6/comments.json
nobase_dist_doc_DATA += examples/kea6/dhcpv4-over-dhcpv6.json
nobase_dist_doc_DATA += examples/kea6/duid.json
diff --git a/doc/examples/kea4/classify2.json b/doc/examples/kea4/classify2.json
new file mode 100644
index 0000000000..fca82e2ab8
--- /dev/null
+++ b/doc/examples/kea4/classify2.json
@@ -0,0 +1,150 @@
+// This is an example configuration file for the DHCPv4 server in Kea.
+// The purpose of this example is to showcase how clients can be classified
+// with advanced features.
+
+{ "Dhcp4": {
+
+// Kea is told to listen on ethX interface only.
+ "interfaces-config": {
+ "interfaces": [ "ethX" ]
+ },
+
+// Let's use the simplest backend: memfile and use some reasonable values
+// for timers. They are of no concern for the classification demonstration.
+ "lease-database": { "type": "memfile" },
+ "renew-timer": 1000,
+ "rebind-timer": 2000,
+ "valid-lifetime": 4000,
+
+// This list defines several classes that incoming packets can be assigned to.
+// One packet can belong to zero or more classes.
+ "client-classes": [
+
+// This class is required by the second subnet and is evaluated only
+// if it is required. The test expression returns true.
+// Note it is not possible to depend on VoIP class because it is not yet
+// defined.
+ {
+ "name": "second_subnet",
+ "only-if-required": true,
+ "test": "member('ALL')",
+ "option-data": [{
+ "name": "domain-name-servers",
+ "data": "127.0.0.1"
+ }]
+ },
+
+// Let's classify all incoming DISCOVER (message type 1) to a separate
+// class.
+ {
+ "name": "discovers",
+ "test": "pkt4.msgtype == 1"
+ },
+
+// Clients are supposed to set the transaction-id field to a random value.
+// Clients that send it with 0 are most likely broken. Let's mark them
+// as such.
+ {
+ "name": "broken",
+ "test": "pkt4.transid == 0"
+ },
+
+// Let's pick VoIP phones. Those that send their class identifiers
+// as Aastra, should belong to VoIP class. For a list of all options,
+// see www.iana.org/assignments/bootp-dhcp-parameters/.
+// In this particular class, we want to set specific values
+// of certain DHCPv4 fields. If the incoming packet matches the
+// test, those fields will be set in outgoing responses.
+// The option 43 is defined to encapsulate suboption in the aastra space.
+ {
+ "name": "VoIP",
+ "test": "substring(option[60].hex,0,6) == 'Aastra'",
+ "next-server": "192.0.2.254",
+ "server-hostname": "hal9000",
+ "boot-file-name": "/dev/null",
+ "option-def": [ {
+ "name": "vendor-encapsulated-options",
+ "code": 43,
+ "type": "empty",
+ "encapsulate": "aastra" } ]
+ },
+
+// Both a VoIP phone (by evaluation or host reservation) and has a host
+// reservation.
+ {
+ "name": "VoIP_host",
+ "test": "member('VoIP') and member('KNOWN')",
+ "server-hostname": "hal9001"
+ }
+
+ ],
+
+// The following list defines subnets. For some subnets we defined
+// a class that is allowed in that subnet. If not specified,
+// everyone is allowed. When a class is specified, only packets belonging
+// to that class are allowed for that subnet.
+ "subnet4": [
+ {
+// This one is for VoIP devices only.
+ "pools": [ { "pool": "192.0.2.1 - 192.0.2.200" } ],
+ "subnet": "192.0.2.0/24",
+ "client-class": "VoIP",
+ "interface": "ethX"
+ },
+// This one doesn't have any client-class specified, so everyone
+// is allowed in. The normal subnet selection rules still apply,
+// though. There is also a static class reservation for a client
+// using MAC address 1a:1b:1c:1d:1e:1f. This client will always
+// be assigned to this class.
+ {
+ "pools": [ { "pool": "192.0.3.1 - 192.0.3.200" } ],
+ "subnet": "192.0.3.0/24",
+ "reservations": [
+ {
+ "hw-address": "1a:1b:1c:1d:1e:1f",
+ "client-classes": [ "VoIP" ]
+ } ],
+ "interface": "ethX",
+ "require-client-classes": [ "second_subnet" ]
+ },
+
+// The following list defines a subnet with pools. For some pools
+// we defined a class that is allowed in that pool. If not specified
+// everyone is allowed. When a class is specified, only packets belonging
+// to that class are allowed for that pool.
+ {
+ "pools": [
+ {
+// This one is for VoIP devices only.
+ "pool": "192.0.4.1 - 192.0.4.200",
+ "client-class": "VoIP"
+ },
+// This one doesn't have any client-class specified, so everyone
+// is allowed in.
+ {
+ "pool": "192.0.5.1 - 192.0.5.200"
+ } ],
+ "subnet": "192.0.4.0/23",
+ "interface": "ethY"
+ }
+ ]
+},
+
+// The following configures logging. It assumes that messages with at
+// least informational level (info, warn, error and fatal) should be
+// logged to stdout.
+"Logging": {
+ "loggers": [
+ {
+ "name": "kea-dhcp4",
+ "output_options": [
+ {
+ "output": "stdout"
+ }
+ ],
+ "severity": "INFO"
+ }
+ ]
+}
+
+}
diff --git a/doc/examples/kea6/classify.json b/doc/examples/kea6/classify.json
index 151392e845..50c78e1a06 100644
--- a/doc/examples/kea6/classify.json
+++ b/doc/examples/kea6/classify.json
@@ -1,4 +1,4 @@
-// This is an example configuration file for the DHCPv4 server in Kea.
+// This is an example configuration file for the DHCPv6 server in Kea.
// The purpose of this example is to showcase how clients can be classified.
{ "Dhcp6":
diff --git a/doc/examples/kea6/classify2.json b/doc/examples/kea6/classify2.json
new file mode 100644
index 0000000000..f931ecbfd1
--- /dev/null
+++ b/doc/examples/kea6/classify2.json
@@ -0,0 +1,122 @@
+// This is an example configuration file for the DHCPv6 server in Kea.
+// The purpose of this example is to showcase how clients can be classified.
+
+{ "Dhcp6":
+
+{
+// Kea is told to listen on ethX interface only.
+ "interfaces-config": {
+ "interfaces": [ "ethX" ]
+ },
+
+// Let's use the simplest backend: memfile and use some reasonable values
+// for timers. They are of no concern for the classification demonstration.
+ "lease-database": {
+ "type": "memfile",
+ "lfc-interval": 3600
+ },
+ "renew-timer": 1000,
+ "rebind-timer": 2000,
+ "preferred-lifetime": 3000,
+ "valid-lifetime": 4000,
+
+// This list defines several classes that incoming packets can be assigned to.
+// One packet can belong to zero or more classes.
+ "client-classes": [
+
+// This class is required by the second subnet and is evaluated only
+// if it is required. The test expression returns true.
+// Note it is not possible to depend on cable-modems class because it
+// is not yet defined.
+ {
+ "name": "second_subnet",
+ "only-if-required": true,
+ "test": "member('ALL')",
+ "option-data": [{
+ "name": "dns-servers",
+ "data": "2001:db8::1"
+ }]
+ },
+
+// Let's classify all incoming RENEW (message type 5) to a separate
+// class.
+ {
+ "name": "renews",
+ "test": "pkt6.msgtype == 5"
+ },
+
+// Let's pick cable modems. In this simple example we'll assume the device
+// is a cable modem if it sends a vendor option with enterprise-id equal
+// to 4491.
+ {
+ "name": "cable-modems",
+ "test": "vendor.enterprise == 4491"
+ },
+
+// Both a cable modem (by evaluation or host reservation) and has a host
+// reservation.
+ {
+ "name": "cable-modem-hosts",
+ "test": "member('cable-modems') and member('KNOWN')"
+ }
+
+ ],
+
+
+// The following list defines subnets. Each subnet consists of at
+// least subnet and pool entries.
+ "subnet6": [
+ {
+ "pools": [ { "pool": "2001:db8:1::/80" } ],
+ "subnet": "2001:db8:1::/64",
+ "client-class": "cable-modems",
+ "interface": "ethX"
+ },
+// The following subnet contains a class reservation for a client using
+// DUID 01:02:03:04:05:0A:0B:0C:0D:0E. This client will always be assigned
+// to this class.
+ {
+ "pools": [ { "pool": "2001:db8:2::/80" } ],
+ "subnet": "2001:db8:2::/64",
+ "reservations": [
+ {
+ "duid": "01:02:03:04:05:0A:0B:0C:0D:0E",
+ "client-classes": [ "cable-modems" ]
+ } ],
+ "interface": "ethX",
+ "require-client-classes": [ "second_subnet" ]
+ },
+// The following subnet contains a pool with a class constraint: only
+// clients which belong to the class are allowed to use this pool.
+ {
+ "pools": [
+ {
+ "pool": "2001:db8:3::/80",
+ "client-class": "cable-modems"
+ } ],
+ "subnet": "2001:db8:4::/64",
+ "interface": "ethY"
+ }
+
+ ]
+},
+
+// The following configures logging. It assumes that messages with at
+// least informational level (info, warn, error and fatal) should be
+// logged to stdout.
+"Logging": {
+ "loggers": [
+ {
+ "name": "kea-dhcp6",
+ "output_options": [
+ {
+ "output": "stdout"
+ }
+ ],
+ "debuglevel": 0,
+ "severity": "INFO"
+ }
+ ]
+}
+
+}
diff --git a/doc/guide/classify.xml b/doc/guide/classify.xml
index c9ff02116c..23efe97e03 100644
--- a/doc/guide/classify.xml
+++ b/doc/guide/classify.xml
@@ -32,21 +32,56 @@
- It is envisaged that client classification will be used for changing the
- behavior of almost any part of the DHCP message processing, including the assignment of
- leases from different pools, the assignment of different options (or different values of
- the same options) etc. In the current release of the software however, there are
- only four mechanisms that take
- advantage of client classification: subnet selection, definition of DHCPv4 private (codes 224-254) and code 43 options, assignment of different
- options and, for DHCPv4 cable modems, the setting of specific options for use with
- the TFTP server address and the boot file field.
+ Conversely, different clients can be grouped into a client class to get a
+ common option.
- The process of doing classification is conducted in four steps:
+ An incoming packet can be associated with a client class in
+ serveral ways:
+
+
+ Implicitly, using a vendor class option or another builtin condition.
+
+
+ Using an expression which evaluates to true.
+
+
+ Using static host reservations, a shared network, a subnet, etc.
+
+
+ Using a hook.
+
+
+
+
+
+ It is envisaged that client classification will be used for
+ changing the behavior of almost any part of the DHCP message
+ processing. In the current release of the software however,
+ there are only five mechanisms that take advantage of
+ client classification: subnet selection, pool selection,
+ definition of DHCPv4 private (codes 224-254) and code 43
+ options, assignment of different options and, for DHCPv4 cable
+ modems, the setting of specific options for use with the TFTP
+ server address and the boot file field.
+
+
+
+ The process of doing classification is conducted in several steps:
- Assess an incoming packet and assign it to zero or more classes.
+ The ALL class is associated with the incoming packet.
+
+
+ Vendor class options are processed.
+
+
+ Classes with matching expressions and not marked for later ("on
+ request" or depending on the KNOWN builtin class) evaluation are
+ processed in the order they are defined in the configuration:
+ the boolean expression is evaluated and when it returns true
+ ("match") the incoming packet is associated to the class.
If a private or code 43 DHCPv4 option is received, decoding it
@@ -54,47 +89,76 @@
resort) definition.
- Choose a subnet, possibly based on the class information.
+ Choose a subnet, possibly based on the class information when
+ some subnets are guarded. More precisely: when choosing a subnet,
+ the server will iterate over all of the subnets that are
+ feasible given the information found in the packet (client
+ address, relay address etc). It will use the first subnet it
+ finds that either doesn't have a class associated with it or
+ that has a class which matches one of the packet's classes.
- Assign options, again possibly based on the class information.
+ Host reservations are looked for. If an identifier from the incoming
+ packet matches a host reservation in the subnet or shared network,
+ the packet is associated with the KNOWN builtin class and all classes
+ of the host reservation.
+
+
+ Classes with matching expressions using directly or indirectly
+ the KNOWN builtin class and not marked for later ("on request")
+ evaluation are processed in the order they are defined in the
+ configuration: the boolean expression is evaluated and when it
+ returns true ("match") the incoming packet is associated to the
+ class.
+
+
+ If needed, addresses and prefixes from pools are assigned,
+ possibly based on the class information when some pools are
+ reserved to class members.
+
+
+ Evaluate classes marked as "required" in the order in which they
+ are listed as required: first shared network, then the subnet
+ and to finally pools assigned resources belong too.
+
+
+ Assign options, again possibly based on the class information
+ in order classes were associated with the incoming packet.
For DHCPv4 private and code 43 options this includes class local
option definitions.
+
- When determining which options to include in the response the server will examine
- the union of options from all of the assigned classes. In the case two or more
- classes include the same option, the value from the first class examined will
- be used. When choosing a subnet, the server will iterate over all of the
- subnets that are feasible given the information found in the packet (client address,
- relay address etc). It will use the first subnet it finds that either doesn't
- have a class associated with it or that has a class which matches one of
- the packet's classes. In the future the processing order of the
- various classes may be specified but for now it is being left unspecified and
- may change in future releases.
+ Beginning with Kea 1.4.0 release, client classes follow the order
+ in which they are specified in the configuration
+ (vs. alphabetical order in previous releases). Required classes
+ follow the order in which they are required.
+
+
+
+
+ When determining which options to include in the response, the
+ server will examine the union of options from all of the
+ assigned classes. In case when two or more classes include the
+ same option, the value from the first class examined will be
+ used, and classes are examined in the order they were associated
+ so ALL is always the first class and matching required classes
+ are last.
- As an example, imagine that an incoming packet matches two classes.
- Class "foo" defines values for an NTP server
- (option 42 in DHCPv4) and an SMTP server (option 69 in DHCPv4) while class
- "bar" defines values for an NTP server and a POP3 server (option 70 in DHCPv4).
- The server will examine the three options NTP, SMTP and POP3 and return any
- of them that the client requested. As the NTP server was defined twice the
- server will choose only one of the values for the reply: the class from which the
- value is obtained is unspecified.
-
-
-
- There are two methods of doing classification. The first is automatic and relies
- on examining the values in the vendor class options. Information from these
- options is extracted and a class name is constructed from it and added to
- the class list for the packet. The second allows you to specify an expression
- that is evaluated for each packet. If the result is true, the packet is
- a member of the class.
+ As an example, imagine that an incoming packet matches two
+ classes. Class "foo" defines values for an NTP server (option
+ 42 in DHCPv4) and an SMTP server (option 69 in DHCPv4) while
+ class "bar" defines values for an NTP server and a POP3 server
+ (option 70 in DHCPv4). The server will examine the three
+ options NTP, SMTP and POP3 and return any of them that the
+ client requested. As the NTP server was defined twice the
+ server will choose only one of the values for the reply: the
+ class from which the value is obtained is unspecified.
@@ -105,47 +169,57 @@
-
- Using Static Host Reservations In Classification
- Classes can be statically assigned to the clients using techniques described
- in and
- .
-
-
-
- Using Vendor Class Information In Classification
+ Builtin Client Classes
- The server checks whether an incoming DHCPv4 packet includes
- the vendor class identifier option (60) or an incoming DHCPv6 packet
- includes the vendor class option (16). If it does, the content of that
- option is prepended with "VENDOR_CLASS_" and the result is interpreted
- as a class. For example, modern cable modems will send this option with
- value "docsis3.0" and so the packet will belong to
- class "VENDOR_CLASS_docsis3.0".
+ Some classes are builtin so do not need to be defined. The main
+ example uses Vendor Class information: The server checks whether
+ an incoming DHCPv4 packet includes the vendor class identifier
+ option (60) or an incoming DHCPv6 packet includes the vendor
+ class option (16). If it does, the content of that option is
+ prepended with "VENDOR_CLASS_" and the result is
+ interpreted as a class. For example, modern cable modems will
+ send this option with value "docsis3.0" and so the
+ packet will belong to class "VENDOR_CLASS_docsis3.0".
+
+ Other examples are: the ALL class which all incoming packets
+ belong to, and the KNOWN class assigned when host reservations exist
+ for the particular client. By convention, builtin classes' names
+ begin with all capital letters.
+
+
+ Currently recognized builtin class names are ALL and KNOWN
+ and prefixes VENDOR_CLASS_, AFTER_ and EXTERNAL_. The AFTER_ prefix
+ is a provision for a not yet written hook, the EXTERNAL_ prefix
+ can be freely used: builtin classes are implicitly defined so
+ never raise warnings if they do not appear in the configuration.
+
+
Using Expressions In Classification
- The expression portion of classification contains operators and values.
- All values are currently strings and operators take a string or strings and
- return another string. When all the operations have completed
- the result should be a value of "true" or "false".
- The packet belongs to
- the class (and the class name is added to the list of classes) if the result
- is "true". Expressions are written in standard format and can be nested.
+ The expression portion of classification contains operators and
+ values. All values are currently strings and operators take a
+ string or strings and return another string. When all the
+ operations have completed the result should be a value of
+ "true" or "false". The packet belongs to
+ the class (and the class name is added to the list of classes)
+ if the result is "true". Expressions are written in
+ standard format and can be nested.
- Expressions are pre-processed during the parsing of the configuration file
- and converted to an internal representation. This allows certain types of
- errors to be caught and logged during parsing. Examples of these errors
- include an incorrect number or types of arguments to an operator. The
- evaluation code will also check for this class of error and generally
- throw an exception, though this should not occur in a normally functioning
- system.
+ Expressions are pre-processed during the parsing of the
+ configuration file and converted to an internal
+ representation. This allows certain types of errors to be caught
+ and logged during parsing. Examples of these errors include an
+ incorrect number or types of arguments to an operator. The
+ evaluation code will also check for this class of error and
+ generally throw an exception, though this should not occur in a
+ normally functioning system.
@@ -161,6 +235,14 @@
remain the same.
+
+ Dependencies between classes are checked too: for instance forward
+ dependencies are rejected when the configuration is parsed:
+ an expression can only depend on already defined classes (including
+ builtin classes) and which are evaluated in a previous or the
+ same evaluation phase. This does not apply to the KNOWN class.
+
+
List of Classification Values
@@ -226,6 +308,27 @@
If the option with given code is present in the
packet "true" else "false"
+
+ Client class membership
+ member('foobar')
+ 'true'
+ If the packet belongs to the given client class
+ "true" else "false"
+
+
+ Known client
+ known
+ member('KNOWN')
+ If there is a host reservation for the client
+ "true" else "false"
+
+
+ Unknown client
+ unknown
+ not member('KNOWN')
+ If there is a hostreservation for the client
+ "false" else "true"
+
DHCPv4 relay agent sub-option
relay4[123].hex
@@ -475,6 +578,22 @@
in the incoming packet. It can be used with empty options.
+
+ "member('foobar')" checks if the packet belongs to the client
+ class "foobar". To avoid dependency loops the configuration file
+ parser checks if client classes were already defined or are
+ built-in, i.e., beginning by "VENDOR_CLASS_",
+ "AFTER__" (for the to come "after" hook) and
+ "EXTERNAL_" or equal to "ALL", "KNOWN",
+ etc.
+
+ "known" and "unknown" are short hands for "member('KNOWN')" and
+ "not member('KNOWN')". Note the evaluation of any expression using
+ directly or indirectly the "KNOWN" class is deferred
+ after the host reservation lookup (i.e. when the "KNOWN"
+ belonging is determined).
+
+
"relay4[code].hex" attempts to extract the value of the sub-option
"code" from the option inserted as the DHCPv4 Relay Agent Information
@@ -650,9 +769,11 @@ concatenation of the strings
Configuring Classes
- A class contains three items: a name, a test expression and option data.
+ A class contains five items: a name, a test expression, option data,
+ option definition and only-if-required flag.
The name must exist and must be unique amongst all classes. The test
- expression and option data are optional.
+ expression, option data and definition, and only-if-required flag are
+ optional.
@@ -667,7 +788,35 @@ concatenation of the strings
- In the following example the class named "Client_foo" is defined.
+ The option definition is for DHCPv4 option 43 ( and DHCPv4 private options
+ ().
+
+
+
+ Usually the test expression is evaluated before subnet selection
+ but in some cases it is useful to evaluate it later when the
+ subnet, shared-network or pools are known but output option
+ processing not yet done. The only-if-required flag, false by default,
+ allows to perform the evaluation of the test expression only
+ when it was required, i.e. in a require-client-classes list of the
+ selected subnet, shared-network or pool.
+
+
+
+ The require-client-classes list which is valid for shared-network,
+ subnet and pool scope specifies the classes which are evaluated
+ in the second pass before output option processing.
+ The list is built in the reversed precedence order of option
+ data, i.e. an option data in a subnet takes precedence on one
+ in a shared-network but required class in a subnet is added
+ after one in a shared-network.
+ The mechanism is related to the only-if-required flag but it is
+ not mandatory that the flag was set to true.
+
+
+
+ In the following example the class named "Client_foo" is defined.
It is comprised of all clients whose client ids (option 61) start with the
string "foo". Members of this class will be given 192.0.2.1 and
192.0.2.2 as their domain name servers.
@@ -723,6 +872,14 @@ concatenation of the strings
+
+ Using Static Host Reservations In Classification
+ Classes can be statically assigned to the clients using techniques described
+ in and
+ .
+
+
+
Configuring Subnets With Class Information
@@ -806,18 +963,18 @@ concatenation of the strings
Configuring Pools With Class Information
- Similar to the subnets, it is possible to restrict access to the certain address
- or prefix pools to the clients belonging to a specific class, using
- the "client-class" parameter when defining the pool.
+ Similar to subnets in certain cases access to certain address or
+ prefix pools must be restricted to only clients that belong to a
+ given class, using the "client-class" when defining the pool.
- Let's assume that the server is connected to a network segment using
+ Let's assume that the server is connected to a network segment that uses
the 192.0.2.0/24 prefix. The Administrator of that network has decided
- that addresses from the range of 192.0.2.10 to 192.0.2.20 are going to be
- managed by the DHCP4 server. Only the clients belonging to the client class
- Client_foo are allowed to use this pool. Such a configuration can be
- achieved in the following way:
+ that addresses from range 192.0.2.10 to 192.0.2.20 are going to be
+ managed by the DHCP4 server. Only clients belonging to client class
+ Client_foo are allowed to use this pool. Such a
+ configuration can be achieved in the following way:
"Dhcp4": {
"client-classes": [
@@ -835,21 +992,20 @@ concatenation of the strings
]
},
...
- ],
+ ],
"subnet4": [
{
"subnet": "192.0.2.0/24",
-
"pools": [
{
"pool": "192.0.2.10 - 192.0.2.20",
"client-class": "Client_foo"
}
- ]
+ ]
},
...
- ],
- ...
+ ],,
+
}
diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml
index ca6785d790..9629f67b95 100644
--- a/doc/guide/dhcp4-srv.xml
+++ b/doc/guide/dhcp4-srv.xml
@@ -1759,7 +1759,7 @@ It is merely echoed by the server
an old PXEClient vendor:
"Dhcp4": {
- "client-class": [
+ "client-classes": [
{
"name": "pxeclient",
"test": "option[vendor-class-identifier].text == 'PXEClient'",
@@ -1802,7 +1802,7 @@ It is merely echoed by the server
},
...
],
- "client-class": [
+ "client-classes": [
{
"name": "APC",
"test": "(option[vendor-class-identifier].text == 'APC'",
@@ -2203,13 +2203,15 @@ It is merely echoed by the server
discussion of the classification process see .
- In certain cases it is useful to configure the server to differentiate between
- DHCP clients types and treat them accordingly. It is envisaged that client
- classification will be used for modifying the behavior of almost any part of
- the DHCP message processing. In the current release of Kea, there are four
- mechanisms that take advantage of the client classification in DHCPv4: subnet
- selection, address pool selection, DHCP options assignment, and, for cable modems,
- there are specific options for use with the TFTP server address and boot file field.
+
+ In certain cases it is useful to differentiate between different types of
+ clients and treat them accordingly. It is envisaged that client
+ classification will be used for changing the behavior of almost any part of
+ the DHCP message processing. In the current release of the software however,
+ there are only some mechanisms that take advantage of client classification:
+ private options and option 43 deferred unpacking, subnet selection,
+ pool selection, assignment of different options, and, for cable modems, there
+ are specific options for use with the TFTP server address and the boot file field.
@@ -2225,24 +2227,38 @@ It is merely echoed by the server
- Client classification can also be used to restrict access to specific
- pools within a subnet. This is useful when to segregate clients belonging
- to the same subnet into different address ranges.
+ When subnets belong to a shared network the classification applies
+ to subnet selection but not to pools, e.g., a pool in a subnet
+ limited to a particular class can still be used by clients which do not
+ belong to the class if the pool they are expected to use is exhausted.
+ So the limit access based on class information is also available
+ at the pool level, see ,
+ within a subnet.
+ This is useful when to segregate clients belonging to the same subnet
+ into different address ranges.
- The process of doing classification is conducted in three steps. The first step
+ The process of doing classification is conducted in several steps. The first step
is to assess an incoming packet and assign it to zero or more classes. The
second step is to choose a subnet, possibly based on the class information.
- The third step is to assign options, again possibly based on the class
+ The third step is to assign classes from host reservations and
+ evaluate class expressions depending on the "KNOWN" class.
+ After the list of required classes is built and each class of the list
+ has its expression evaluated: when it returns true the packet is added
+ as a member of the class.
+ The last step is to assign options, again possibly based on the class
information.
+ More complete and detailed description is available in
+ .
- There are two methods of doing classification. The first is automatic and relies
- on examining the values in the vendor class options. Information from these
+ There are two main methods of doing classification. The first is automatic and relies
+ on examining the values in the vendor class options or existence of a
+ host reservation. Information from these
options is extracted and a class name is constructed from it and added to
- the class list for the packet. The second allows you to specify an expression
+ the class list for the packet. The second allows for specifying an expression
that is evaluated for each packet. If the result is true the packet is
a member of the class.
@@ -2292,9 +2308,14 @@ It is merely echoed by the server
If there are multiple classes defined and an incoming packet is matched
- to multiple classes, the class whose name is alphabetically the first
- is used.
+ to multiple classes, the class which is evaluated first is used.
+
+
+ In Kea versions prior to 1.4.0 the alphabetical order of class names was used.
+ Starting from Kea 1.4.0 the classes are ordered as specified in the configuration.
+
+
@@ -2380,6 +2401,77 @@ It is merely echoed by the server
}
+
+
+ Required Classification
+
+ In some cases it is useful to limit the scope of a class to
+ a shared-network, subnet or pool. There are two parameters
+ which are used to limit the scope of the class by instructing
+ the server to perform evaluation of test expressions when
+ required.
+
+
+
+ The first one is the per-class only-if-required
+ flag which is false by default. When it is set to
+ true the test expression of the class is not
+ evaluated at the reception of the incoming packet but later and
+ only if the class evaluation is required.
+
+
+
+ The second is the require-client-classes which
+ takes a list of class names and is valid in shared-network,
+ subnet and pool scope. Classes in these lists are marked as
+ required and evaluated after selection of this specific
+ shared-network/subnet/pool and before output option processing.
+
+
+
+ In this example, a class is assigned to the incoming packet
+ when the specified subnet is used.
+
+
+"Dhcp4": {
+ "client-classes": [
+ {
+ "name": "Client_foo",
+ "test": "member('ALL')",
+ "only-if-required": true
+ },
+ ...
+ ],
+ "subnet4": [
+ {
+ "subnet": "192.0.2.0/24",
+ "pools": [ { "pool": "192.0.2.10 - 192.0.2.20" } ],
+ "require-client-classes": [ "Client_foo" ],
+ ...
+ },
+ ...
+ ],
+ ...
+}
+
+
+
+ Required evaluation can be used to express complex dependencies,
+ for example, subnet membership. It can also be used to reverse the
+ precedence: if you set an option-data in a subnet it takes
+ precedence over an option-data in a class. When you move the
+ option-data to a required class and require it in
+ the subnet, a class evaluated earlier may take precedence.
+
+
+
+ Required evaluation is also available at shared-network and
+ pool levels. The order in which required classes are considered is:
+ shared-network, subnet and pool, i.e. the opposite order
+ option-data are processed.
+
+
+
@@ -3474,7 +3566,7 @@ It is merely echoed by the server
to configure the server to assign classes to a client based on the content
of the options that this client sends to the server. Host reservations
mechanisms also allow for statically assigning classes to the clients.
- The definitions of these classes must exist in the Kea
+ The definitions of these classes should exist in the Kea
configuration. The following configuration snippet shows how to specify
that a client belongs to classes reserved-class1
and reserved-class2. Those classes are associated with
@@ -3520,7 +3612,18 @@ It is merely echoed by the server
Static class assignments, as shown above, can be used in conjunction
- with classification using expressions.
+ with classification using expressions. The "KNOWN" builtin class is
+ added to the packet and any class depending on it directly or indirectly
+ and not only-if-required is evaluated.
+
+
+
+ If you want to force the evaluation of a class expression after
+ the host reservation lookup, for instance because of a dependency on
+ "reserved-class1" from the previous example, you should add a
+ "member('KNOWN')" in the expression.
+
+
@@ -3889,7 +3992,7 @@ for each subnet. Here's an example:
"shared-networks": [
- {"
+ {
"name": "kakapo",
"relay": {
"ip-address": "192.3.5.6"
diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml
index 39d449124a..9199ad5009 100644
--- a/doc/guide/dhcp6-srv.xml
+++ b/doc/guide/dhcp6-srv.xml
@@ -2207,11 +2207,9 @@ should include options from the isc option space:
In certain cases it is useful to differentiate between different types
of clients and treat them accordingly. It is envisaged that client
classification will be used for changing the behavior of almost any part of
- the DHCP message processing, including the assignment of leases from different
- pools, the assignment of different options (or different values of the same
- options) etc. In the current release of the software however, there are
- only two mechanisms that take advantage of client classification:
- subnet selection and assignment of different options.
+ the DHCP message processing. In the current release of the software however,
+ there are only some mechanisms that take advantage of client classification:
+ subnet selection, pool selection, and assignment of different options.
@@ -2225,26 +2223,38 @@ should include options from the isc option space:
users from playing with their cable modems. For details on how to set up
class restrictions on subnets, see .
-
-
- Client classification can also be used to restrict access to specific
- pools within a subnet. This is useful when to segregate clients belonging
- to the same subnet into different address or prefix ranges.
+ When subnets belong to a shared network the classification applies
+ to subnet selection but not to pools, e.g., a pool in a subnet
+ limited to a particular class can still be used by clients which do not
+ belong to the class if the pool they are expected to use is exhausted.
+ So the limit access based on class information is also available
+ at the address/prefix pool level, see within a subnet.
+ This is useful when to segregate clients belonging to the same subnet
+ into different address ranges.
- The process of doing classification is conducted in three steps. The first step
+ The process of doing classification is conducted in several steps. The first step
is to assess an incoming packet and assign it to zero or more classes. The
second step is to choose a subnet, possibly based on the class information.
- The third step is to assign options again possibly based on the class
+ The third step is to assign classes from host reservations and
+ evaluate class expressions depending on the "KNOWN" class.
+ After the list of required classes is built and each class of the list
+ has its expression evaluated: when it returns true the packet is added
+ as a member of the class.
+ The last step is to assign options again possibly based on the class
information.
+ More complete and detailed description is available in
+ .
- There are two methods of doing classification. The first is automatic and relies
- on examining the values in the vendor class options. Information from these
+ There are two main methods of doing classification. The first is automatic and relies
+ on examining the values in the vendor class options or existence of a
+ host reservation. Information from these
options is extracted and a class name is constructed from it and added to
- the class list for the packet. The second allows you to specify an expression
+ the class list for the packet. The second allows for specifying an expression
that is evaluated for each packet. If the result is true the packet is
a member of the class.
@@ -2319,6 +2329,81 @@ should include options from the isc option space:
+
+
+ Required classification
+
+ In some cases it is useful to limit the scope of a class to
+ a shared-network, subnet or pool. There are two parameters
+ which are used to limit the scope of the class by instructing
+ the server to perform evaluation of test expressions when
+ required.
+
+
+
+ The first one is the per-class only-if-required
+ flag which is false by default. When it is set to
+ true the test expression of the class is not
+ evaluated at the reception of the incoming packet but later and
+ only if the class evaluation is required.
+
+
+
+ The second is the require-client-classes which
+ takes a list of class names and is valid in shared-network,
+ subnet and pool scope. Classes in these lists are marked as
+ required and evaluated after selection of this specific
+ shared-network/subnet/pool and before output option processing.
+
+
+
+ In this example, a class is assigned to the incoming packet
+ when the specified subnet is used.
+
+
+"Dhcp6": {
+ "client-classes": [
+ {
+ "name": "Client_foo",
+ "test": "member('ALL')",
+ "only-if-required": true
+ },
+ ...
+ ],
+ "subnet6": [
+ {
+ "subnet": "2001:db8:1::/64"
+ "pools": [
+ {
+ "pool": "2001:db8:1::-2001:db8:1::ffff"
+ }
+ ],
+ "require-client-classes": [ "Client_foo" ],
+ ...
+ },
+ ...
+ ],
+ ...
+}
+
+
+
+ Required evaluation can be used to express complex dependencies,
+ for example, subnet membership. It can also be used to reverse the
+ precedence: if you set an option-data in a subnet it takes
+ precedence over an option-data in a class. When you move the
+ option-data to a required class and require it in
+ the subnet, a class evaluated earlier may take precedence.
+
+
+
+ Required evaluation is also available at shared-network and
+ pool/pd-pool levels. The order in which required classes are
+ considered is: shared-network, subnet and (pd-)pool, i.e.
+ the opposite order option-data are processed.
+
+
+
@@ -3184,7 +3269,18 @@ should include options from the isc option space:
Static class assignments, as shown above, can be used in conjunction
- with classification using expressions.
+ with classification using expressions. The "KNOWN" builtin class is
+ added to the packet and any class depending on it directly or indirectly
+ and not only-if-required is evaluated.
+
+
+
+ If you want to force the evaluation of a class expression after
+ the host reservation lookup, for instance because of a dependency on
+ "reserved-class1" from the previous example, you should add a
+ "member('KNOWN')" in the expression.
+
+
@@ -3570,7 +3666,7 @@ for each subnet. Here's an example:
"shared-networks": [
- {"
+ {
"name": "kakapo",
"relay": {
"ip-address": "2001:db8::abcd"
diff --git a/src/bin/dhcp4/dhcp4_lexer.ll b/src/bin/dhcp4/dhcp4_lexer.ll
index 316d9f12a0..6dd8dd6a02 100644
--- a/src/bin/dhcp4/dhcp4_lexer.ll
+++ b/src/bin/dhcp4/dhcp4_lexer.ll
@@ -578,7 +578,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
case isc::dhcp::Parser4Context::POOLS:
case isc::dhcp::Parser4Context::RESERVATIONS:
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
- case isc::dhcp::Parser4Context::CLIENT_CLASS:
return isc::dhcp::Dhcp4Parser::make_OPTION_DATA(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("option-data", driver.loc_);
@@ -592,7 +591,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
case isc::dhcp::Parser4Context::OPTION_DEF:
case isc::dhcp::Parser4Context::OPTION_DATA:
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
- case isc::dhcp::Parser4Context::CLIENT_CLASS:
case isc::dhcp::Parser4Context::SHARED_NETWORK:
case isc::dhcp::Parser4Context::LOGGERS:
return isc::dhcp::Dhcp4Parser::make_NAME(driver.loc_);
@@ -879,6 +877,17 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
}
}
+\"require-client-classes\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::SUBNET4:
+ case isc::dhcp::Parser4Context::POOLS:
+ case isc::dhcp::Parser4Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp4Parser::make_REQUIRE_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("require-client-classes", driver.loc_);
+ }
+}
+
\"client-class\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::SUBNET4:
@@ -894,13 +903,21 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
\"test\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::CLIENT_CLASSES:
- case isc::dhcp::Parser4Context::CLIENT_CLASS:
return isc::dhcp::Dhcp4Parser::make_TEST(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("test", driver.loc_);
}
}
+\"only-if-required\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser4Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp4Parser::make_ONLY_IF_REQUIRED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp4Parser::make_STRING("only-if-required", driver.loc_);
+ }
+}
+
\"reservations\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::SUBNET4:
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 66d48f077c..1767fb38c5 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -59,6 +59,14 @@ which cannot be found in the configuration. Either a hook written
before the classification was added to Kea is used, or class naming is
inconsistent.
+% DHCP4_CLASS_UNDEFINED required class %1 has no definition
+This debug message informs that a class is listed for required evaluation but
+has no definition.
+
+% DHCP4_CLASS_UNTESTABLE required class %1 has no test expression
+This debug message informs that a class was listed for required evaluation but
+its definition does not include a test expression to evaluate.
+
% DHCP4_CLIENTID_IGNORED_FOR_LEASES %1: not using client identifier for lease allocation for subnet %2
This debug message is issued when the server is processing the DHCPv4 message
for which client identifier will not be used when allocating new lease or
diff --git a/src/bin/dhcp4/dhcp4_parser.yy b/src/bin/dhcp4/dhcp4_parser.yy
index e6b322e6c3..8606eb1e6a 100644
--- a/src/bin/dhcp4/dhcp4_parser.yy
+++ b/src/bin/dhcp4/dhcp4_parser.yy
@@ -130,7 +130,9 @@ using namespace std;
HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers"
CLIENT_CLASSES "client-classes"
+ REQUIRE_CLIENT_CLASSES "require-client-classes"
TEST "test"
+ ONLY_IF_REQUIRED "only-if-required"
CLIENT_CLASS "client-class"
RESERVATIONS "reservations"
@@ -981,6 +983,7 @@ subnet4_param: valid_lifetime
| id
| rapid_commit
| client_class
+ | require_client_classes
| reservations
| reservation_mode
| relay
@@ -1045,13 +1048,23 @@ interface_id: INTERFACE_ID {
};
client_class: CLIENT_CLASS {
- ctx.enter(ctx.CLIENT_CLASS);
+ ctx.enter(ctx.NO_KEYWORD);
} COLON STRING {
ElementPtr cls(new StringElement($4, ctx.loc2pos(@4)));
ctx.stack_.back()->set("client-class", cls);
ctx.leave();
};
+require_client_classes: REQUIRE_CLIENT_CLASSES {
+ ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("require-client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
reservation_mode: RESERVATION_MODE {
ctx.enter(ctx.RESERVATION_MODE);
} COLON hr_mode {
@@ -1121,6 +1134,7 @@ shared_network_param: name
| relay
| reservation_mode
| client_class
+ | require_client_classes
| valid_lifetime
| user_context
| comment
@@ -1409,6 +1423,7 @@ pool_params: pool_param
pool_param: pool_entry
| option_data_list
| client_class
+ | require_client_classes
| user_context
| comment
| unknown_map_entry
@@ -1687,6 +1702,7 @@ not_empty_client_class_params: client_class_param
client_class_param: client_class_name
| client_class_test
+ | only_if_required
| option_def_list
| option_data_list
| next_server
@@ -1707,6 +1723,11 @@ client_class_test: TEST {
ctx.leave();
};
+only_if_required: ONLY_IF_REQUIRED COLON BOOLEAN {
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("only-if-required", b);
+};
+
// --- end of client classes ---------------------------------
// was server-id but in is DHCPv6-only
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index dfa90d8bbe..0662cb8f9f 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -165,10 +165,9 @@ Dhcpv4Exchange::Dhcpv4Exchange(const AllocEnginePtr& alloc_engine,
const ClientClasses& classes = query_->getClasses();
if (!classes.empty()) {
- std::string joined_classes = boost::algorithm::join(classes, ", ");
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_ASSIGNED)
.arg(query_->getLabel())
- .arg(joined_classes);
+ .arg(classes.toText());
}
};
@@ -388,9 +387,10 @@ Dhcpv4Exchange::setHostIdentifiers() {
void
Dhcpv4Exchange::setReservedClientClasses() {
if (context_->currentHost() && query_) {
- BOOST_FOREACH(const std::string& client_class,
- context_->currentHost()->getClientClasses4()) {
- query_->addClass(client_class);
+ const ClientClasses& classes = context_->currentHost()->getClientClasses4();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ query_->addClass(*cclass);
}
}
}
@@ -1318,12 +1318,25 @@ Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
co_list.push_back(host->getCfgOption4());
}
- // Secondly, subnet configured options.
+ // Secondly, pool specific options.
+ Pkt4Ptr resp = ex.getResponse();
+ IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
+ if (resp) {
+ addr = resp->getYiaddr();
+ }
+ if (!addr.isV4Zero()) {
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
+ if (pool && !pool->getCfgOption()->empty()) {
+ co_list.push_back(pool->getCfgOption());
+ }
+ }
+
+ // Thirdly, subnet configured options.
if (!subnet->getCfgOption()->empty()) {
co_list.push_back(subnet->getCfgOption());
}
- // Thirdly, shared network specific options.
+ // Forthly, shared network specific options.
SharedNetwork4Ptr network;
subnet->getSharedNetwork(network);
if (network && !network->getCfgOption()->empty()) {
@@ -1332,16 +1345,14 @@ Dhcpv4Srv::buildCfgOptionList(Dhcpv4Exchange& ex) {
// Each class in the incoming packet
const ClientClasses& classes = ex.getQuery()->getClasses();
- for (ClientClasses::const_iterator cclass = classes.begin();
- cclass != classes.end(); ++cclass) {
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
// Find the client class definition for this class
const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
getClientClassDictionary()->findClass(*cclass);
if (!ccdef) {
- // Not found: the class is not configured
- if (((*cclass).size() <= VENDOR_CLASS_PREFIX.size()) ||
- ((*cclass).compare(0, VENDOR_CLASS_PREFIX.size(), VENDOR_CLASS_PREFIX) != 0)) {
- // Not a VENDOR_CLASS_* so should be configured
+ // Not found: the class is built-in or not configured
+ if (!isClientClassBuiltIn(*cclass)) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNCONFIGURED)
.arg(ex.getQuery()->getLabel())
.arg(*cclass);
@@ -2438,16 +2449,17 @@ Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
if (!classes.empty()) {
// Let's get class definitions
- const ClientClassDefMapPtr& defs = CfgMgr::instance().getCurrentCfg()->
- getClientClassDictionary()->getClasses();
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs = dict->getClasses();
// Now we need to iterate over the classes assigned to the
// query packet and find corresponding class definitions for it.
- for (ClientClasses::const_iterator name = classes.begin();
- name != classes.end(); ++name) {
+ for (ClientClasses::const_iterator name = classes.cbegin();
+ name != classes.cend(); ++name) {
- ClientClassDefMap::const_iterator cl = defs->find(*name);
- if (cl == defs->end()) {
+ ClientClassDefPtr cl = dict->findClass(*name);
+ if (!cl) {
// Let's skip classes that don't have definitions. Currently
// these are automatic classes VENDOR_CLASS_something, but there
// may be other classes assigned under other circumstances, e.g.
@@ -2455,12 +2467,12 @@ Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
continue;
}
- IOAddress next_server = cl->second->getNextServer();
+ IOAddress next_server = cl->getNextServer();
if (!next_server.isV4Zero()) {
response->setSiaddr(next_server);
}
- const string& sname = cl->second->getSname();
+ const string& sname = cl->getSname();
if (!sname.empty()) {
// Converting string to (const uint8_t*, size_t len) format is
// tricky. reinterpret_cast is not the most elegant solution,
@@ -2471,7 +2483,7 @@ Dhcpv4Srv::setFixedFields(Dhcpv4Exchange& ex) {
sname.size());
}
- const string& filename = cl->second->getFilename();
+ const string& filename = cl->getFilename();
if (!filename.empty()) {
// Converting string to (const uint8_t*, size_t len) format is
// tricky. reinterpret_cast is not the most elegant solution,
@@ -2524,11 +2536,13 @@ Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
return (Pkt4Ptr());
}
- // Assign reserved classes.
- ex.setReservedClientClasses();
-
// Adding any other options makes sense only when we got the lease.
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
+ // Assign reserved classes.
+ ex.setReservedClientClasses();
+ // Required classification
+ requiredClassify(ex);
+
buildCfgOptionList(ex);
appendRequestedOptions(ex);
appendRequestedVendorOptions(ex);
@@ -2585,11 +2599,13 @@ Dhcpv4Srv::processRequest(Pkt4Ptr& request, AllocEngine::ClientContext4Ptr& cont
return (Pkt4Ptr());
}
- // Assign reserved classes.
- ex.setReservedClientClasses();
-
// Adding any other options makes sense only when we got the lease.
if (!ex.getResponse()->getYiaddr().isV4Zero()) {
+ // Assign reserved classes.
+ ex.setReservedClientClasses();
+ // Required classification
+ requiredClassify(ex);
+
buildCfgOptionList(ex);
appendRequestedOptions(ex);
appendRequestedVendorOptions(ex);
@@ -2891,6 +2907,7 @@ Dhcpv4Srv::processInform(Pkt4Ptr& inform) {
Pkt4Ptr ack = ex.getResponse();
ex.setReservedClientClasses();
+ requiredClassify(ex);
buildCfgOptionList(ex);
appendRequestedOptions(ex);
@@ -3196,43 +3213,142 @@ void Dhcpv4Srv::classifyByVendor(const Pkt4Ptr& pkt) {
}
void Dhcpv4Srv::classifyPacket(const Pkt4Ptr& pkt) {
+ // All packets belongs to ALL.
+ pkt->addClass("ALL");
+
// First phase: built-in vendor class processing
classifyByVendor(pkt);
// Run match expressions
// Note getClientClassDictionary() cannot be null
- const ClientClassDefMapPtr& defs_ptr = CfgMgr::instance().getCurrentCfg()->
- getClientClassDictionary()->getClasses();
- for (ClientClassDefMap::const_iterator it = defs_ptr->begin();
- it != defs_ptr->end(); ++it) {
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (ClientClassDefList::const_iterator it = defs_ptr->cbegin();
+ it != defs_ptr->cend(); ++it) {
// Note second cannot be null
- const ExpressionPtr& expr_ptr = it->second->getMatchExpr();
+ const ExpressionPtr& expr_ptr = (*it)->getMatchExpr();
// Nothing to do without an expression to evaluate
if (!expr_ptr) {
continue;
}
+ // Not the right time if only when required
+ if ((*it)->getRequired()) {
+ continue;
+ }
// Evaluate the expression which can return false (no match),
// true (match) or raise an exception (error)
try {
bool status = evaluateBool(*expr_ptr, *pkt);
if (status) {
LOG_INFO(options4_logger, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg(status);
// Matching: add the class
- pkt->addClass(it->first);
+ pkt->addClass((*it)->getName());
} else {
LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg(status);
}
} catch (const Exception& ex) {
LOG_ERROR(options4_logger, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg(ex.what());
} catch (...) {
LOG_ERROR(options4_logger, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
+ .arg("get exception?");
+ }
+ }
+}
+
+void Dhcpv4Srv::requiredClassify(Dhcpv4Exchange& ex) {
+ // First collect required classes
+ Pkt4Ptr query = ex.getQuery();
+ ClientClasses classes = query->getClasses(true);
+ Subnet4Ptr subnet = ex.getContext()->subnet_;
+
+ if (subnet) {
+ // Begin by the shared-network
+ SharedNetwork4Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ const ClientClasses& to_add = network->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+
+ // Followed by the subnet
+ const ClientClasses& to_add = subnet->getRequiredClasses();
+ for(ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+
+ // And finish by the pool
+ Pkt4Ptr resp = ex.getResponse();
+ IOAddress addr = IOAddress::IPV4_ZERO_ADDRESS();
+ if (resp) {
+ addr = resp->getYiaddr();
+ }
+ if (!addr.isV4Zero()) {
+ PoolPtr pool = subnet->getPool(Lease::TYPE_V4, addr, false);
+ if (pool) {
+ const ClientClasses& to_add = pool->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+ }
+
+ // host reservation???
+ }
+
+ // Run match expressions
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ const ClientClassDefPtr class_def = dict->findClass(*cclass);
+ if (!class_def) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNDEFINED)
+ .arg(*cclass);
+ continue;
+ }
+ const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_BASIC, DHCP4_CLASS_UNTESTABLE)
+ .arg(*cclass);
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *query);
+ if (status) {
+ LOG_INFO(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ // Matching: add the class
+ query->addClass(*cclass);
+ } else {
+ LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(options4_logger, EVAL_RESULT)
+ .arg(*cclass)
.arg("get exception?");
}
}
@@ -3246,8 +3362,8 @@ Dhcpv4Srv::deferredUnpack(Pkt4Ptr& query)
OptionDefinitionPtr def;
// Iterate on client classes
const ClientClasses& classes = query->getClasses();
- for (ClientClasses::const_iterator cclass = classes.begin();
- cclass != classes.end(); ++cclass) {
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
// Get the client class definition for this class
const ClientClassDefPtr& ccdef =
CfgMgr::instance().getCurrentCfg()->
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 8922e5bf04..6f2362b2dc 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -832,6 +832,18 @@ protected:
/// @param pkt packet to be classified
void classifyPacket(const Pkt4Ptr& pkt);
+ /// @brief Assigns incoming packet to zero or more classes (required pass).
+ ///
+ /// @note This required classification evaluates all classes which
+ /// were marked for required evaluation. Classes are collected so
+ /// evaluated in the reversed order than output option processing.
+ ///
+ /// @note The only-if-required flag is related because it avoids
+ /// double evaluation (which is not forbidden).
+ ///
+ /// @param ex The exchange holding needed informations.
+ void requiredClassify(Dhcpv4Exchange& ex);
+
/// @brief Perform deferred option unpacking.
///
/// @note Options 43 and 224-254 are processed after classification.
diff --git a/src/bin/dhcp4/location.hh b/src/bin/dhcp4/location.hh
index 0c20e8ef6d..151cf81093 100644
--- a/src/bin/dhcp4/location.hh
+++ b/src/bin/dhcp4/location.hh
@@ -1,4 +1,4 @@
-// Generated 201803271227
+// Generated 201804061423
// A Bison parser, made by GNU Bison 3.0.4.
// Locations for Bison parsers in C++
diff --git a/src/bin/dhcp4/parser_context.cc b/src/bin/dhcp4/parser_context.cc
index 17c268a929..d160a2f6c7 100644
--- a/src/bin/dhcp4/parser_context.cc
+++ b/src/bin/dhcp4/parser_context.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -178,8 +178,6 @@ Parser4Context::contextName()
return ("reservations");
case RELAY:
return ("relay");
- case CLIENT_CLASS:
- return ("client-class");
case LOGGERS:
return ("loggers");
case OUTPUT_OPTIONS:
diff --git a/src/bin/dhcp4/parser_context.h b/src/bin/dhcp4/parser_context.h
index 23a8c1ca32..e0beff259a 100644
--- a/src/bin/dhcp4/parser_context.h
+++ b/src/bin/dhcp4/parser_context.h
@@ -273,9 +273,6 @@ public:
/// Used while parsing Dhcp4/subnet4relay structures.
RELAY,
- /// Used while parsing Dhcp4/client-classes structures.
- CLIENT_CLASS,
-
/// Used while parsing Logging/loggers structures.
LOGGERS,
diff --git a/src/bin/dhcp4/position.hh b/src/bin/dhcp4/position.hh
index ec7111ace8..ad49fa5f89 100644
--- a/src/bin/dhcp4/position.hh
+++ b/src/bin/dhcp4/position.hh
@@ -1,4 +1,4 @@
-// Generated 201803271227
+// Generated 201804061423
// A Bison parser, made by GNU Bison 3.0.4.
// Positions for Bison parsers in C++
diff --git a/src/bin/dhcp4/stack.hh b/src/bin/dhcp4/stack.hh
index 81f7ac7ce3..8467d55e10 100644
--- a/src/bin/dhcp4/stack.hh
+++ b/src/bin/dhcp4/stack.hh
@@ -1,4 +1,4 @@
-// Generated 201803271227
+// Generated 201804061423
// A Bison parser, made by GNU Bison 3.0.4.
// Stack handling for Bison parsers in C++
diff --git a/src/bin/dhcp4/tests/classify_unittest.cc b/src/bin/dhcp4/tests/classify_unittest.cc
index 377590f022..227abd7fa5 100644
--- a/src/bin/dhcp4/tests/classify_unittest.cc
+++ b/src/bin/dhcp4/tests/classify_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -43,6 +43,38 @@ namespace {
/// also reserved for the host using HW address
/// 'aa:bb:cc:dd:ee:ff'
/// - Subnet of 10.0.0.0/24 with a single address pool
+///
+/// - Configuration 2:
+/// - Used for testing client class combination
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - the following classes defined:
+/// not (option[93].hex == 0x0009)
+/// not member(), next-server set to 1.2.3.4
+/// option[93].hex == 0x0006
+/// option[93].hex == 0x0001
+/// or member(), set boot-file-name to pxelinux.0
+///
+/// - Configuration 3:
+/// - Used for required classification
+/// - 1 subnet: 10.0.0.0/24
+/// - 1 pool: 10.0.0.10-10.0.0.100
+/// - the following classes defined:
+/// option[93].hex == 0x0009, next-server set to 1.2.3.4
+/// option[93].hex == 0x0007, set server-hostname to deneb
+/// option[93].hex == 0x0006, set boot-file-name to pxelinux.0
+/// option[93].hex == 0x0001, set boot-file-name to ipxe.efi
+///
+/// - Configuration 4:
+/// - Used for complex membership (example taken from HA)
+/// - 1 subnet: 10.0.0.0/24
+/// - 4 pools: 10.0.0.10-10.0.0.49, 10.0.0.60-10.0.0.99,
+/// 10.0.0.110-10.0.0.149, 10.0.0.1.60-10.0.0.199
+/// - 4 classes to compose:
+/// server1 and server2 for each HA server
+/// option[93].hex == 0x0009 aka telephones
+/// option[93].hex == 0x0007 aka computers
+///
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
@@ -118,7 +150,131 @@ const char* CONFIGS[] = {
" }"
" ]"
" } ]"
- "}"
+ "}",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"not-pxe1\","
+ " \"test\": \"not (option[93].hex == 0x0009)\""
+ "},"
+ "{"
+ " \"name\": \"pxe1\","
+ " \"test\": \"not member('not-pxe1')\","
+ " \"next-server\": \"1.2.3.4\""
+ "},"
+ "{"
+ " \"name\": \"pxe3\","
+ " \"test\": \"option[93].hex == 0x0006\""
+ "},"
+ "{"
+ " \"name\": \"pxe4\","
+ " \"test\": \"option[93].hex == 0x0001\""
+ "},"
+ "{"
+ " \"name\": \"pxe34\","
+ " \"test\": \"member('pxe3') or member('pxe4')\","
+ " \"boot-file-name\": \"pxelinux.0\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]"
+ " } ]"
+ "}",
+
+ // Configuration 3
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"pxe1\","
+ " \"test\": \"option[93].hex == 0x0009\","
+ " \"only-if-required\": true,"
+ " \"next-server\": \"1.2.3.4\""
+ "},"
+ "{"
+ " \"name\": \"pxe2\","
+ " \"test\": \"option[93].hex == 0x0007\","
+ " \"only-if-required\": true,"
+ " \"server-hostname\": \"deneb\""
+ "},"
+ "{"
+ " \"name\": \"pxe3\","
+ " \"test\": \"option[93].hex == 0x0006\","
+ " \"only-if-required\": false,"
+ " \"boot-file-name\": \"pxelinux.0\""
+ "},"
+ "{"
+ " \"name\": \"pxe4\","
+ " \"test\": \"option[93].hex == 0x0001\","
+ " \"boot-file-name\": \"ipxe.efi\""
+ "}],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ],"
+ " \"require-client-classes\": [ \"pxe2\" ]"
+ " } ]"
+ "}",
+
+ // Configuration 4
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"server1\""
+ "},"
+ "{"
+ " \"name\": \"server2\""
+ "},"
+ "{"
+ " \"name\": \"telephones\","
+ " \"test\": \"option[93].hex == 0x0009\""
+ "},"
+ "{"
+ " \"name\": \"computers\","
+ " \"test\": \"option[93].hex == 0x0007\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_telephones\","
+ " \"test\": \"member('server1') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_computers\","
+ " \"test\": \"member('server1') and member('computers')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_telephones\","
+ " \"test\": \"member('server2') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_computers\","
+ " \"test\": \"member('server2') and member('computers')\""
+ "} ],"
+ "\"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\", "
+ " \"id\": 1,"
+ " \"pools\": [ "
+ " { \"pool\": \"10.0.0.10-10.0.0.49\","
+ " \"client-class\": \"server1_and_telephones\" },"
+ " { \"pool\": \"10.0.0.60-10.0.0.99\","
+ " \"client-class\": \"server1_and_computers\" },"
+ " { \"pool\": \"10.0.0.110-10.0.0.149\","
+ " \"client-class\": \"server2_and_telephones\" },"
+ " { \"pool\": \"10.0.0.160-10.0.0.199\","
+ " \"client-class\": \"server2_and_computers\" } ]"
+ " } ]"
+ "}",
+
};
/// @brief Test fixture class for testing classification.
@@ -376,5 +532,579 @@ TEST_F(ClassifyTest, clientClassesInHostReservations) {
EXPECT_EQ("10.0.0.201", client.config_.dns_servers_[0].toText());
}
+// This test checks that an incoming DISCOVER that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsDiscoverNoClasses2) {
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, OptionPtr(), "0.0.0.0", "", "");
+}
+// This test checks that an incoming REQUEST that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsRequestNoClasses2) {
+ testFixedFields(CONFIGS[2], DHCPREQUEST, OptionPtr(), "0.0.0.0", "", "");
+}
+// This test checks that an incoming INFORM that does not match any classes
+// will get the fixed fields empty.
+TEST_F(ClassifyTest, fixedFieldsInformNoClasses2) {
+ testFixedFields(CONFIGS[2], DHCPINFORM, OptionPtr(), "0.0.0.0", "", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverNextServer2) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "1.2.3.4", "", "");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsRequestNextServer2) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[2], DHCPREQUEST, pxe, "1.2.3.4", "", "");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// next-server specified will result in a response that has the next-server set.
+TEST_F(ClassifyTest, fixedFieldsInformNextServer2) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[2], DHCPINFORM, pxe, "1.2.3.4", "", "");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile21) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming REQUEST that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsRequestFile21) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[2], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming INFORM that does match a class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsInformFile21) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+
+
+// This test checks that an incoming DISCOVER that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile22) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[2], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming REQUEST that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsRequestFile22) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[2], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+// This test checks that an incoming INFORM that does match a different class that has
+// boot-file-name specified will result in a response that has the filename field set.
+TEST_F(ClassifyTest, fixedFieldsInformFile22) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[2], DHCPINFORM, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+
+// No class
+TEST_F(ClassifyTest, fixedFieldsDiscoverNoClasses3) {
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, OptionPtr(), "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestNoClasses3) {
+ testFixedFields(CONFIGS[3], DHCPREQUEST, OptionPtr(), "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsInformNoClasses3) {
+ testFixedFields(CONFIGS[3], DHCPINFORM, OptionPtr(), "0.0.0.0", "", "");
+}
+
+// Class 'pxe1' is only-if-required and not subject to required evaluation
+TEST_F(ClassifyTest, fixedFieldsDiscoverNextServer3) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestNextServer3) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "");
+}
+TEST_F(ClassifyTest, fixedFieldsInformNextServer3) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+
+ testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "", "");
+}
+
+
+// Class pxe2 is only-if-required but the subnet requires its evaluation
+TEST_F(ClassifyTest, fixedFieldsDiscoverHostname3) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "deneb", "");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestHostname3) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "deneb", "");
+}
+TEST_F(ClassifyTest, fixedFieldsInformHostname3) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0007));
+
+ testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "deneb", "");
+}
+
+// No change from config #0 for pxe3 and pxe4
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile31) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestFile31) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+TEST_F(ClassifyTest, fixedFieldsInformFile31) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0006));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "pxelinux.0");
+}
+TEST_F(ClassifyTest, fixedFieldsDiscoverFile32) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[3], DHCPDISCOVER, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+TEST_F(ClassifyTest, fixedFieldsRequestFile32) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[3], DHCPREQUEST, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+TEST_F(ClassifyTest, fixedFieldsInformFile32) {
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0001));
+
+ testFixedFields(CONFIGS[3], DHCPINFORM, pxe, "0.0.0.0", "", "ipxe.efi");
+}
+
+// This test checks the complex membership from HA with server1 telephone.
+TEST_F(ClassifyTest, server1Telephone) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+ client.addExtraOption(pxe);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the first pool.
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+}
+
+// This test checks the complex membership from HA with server1 computer.
+TEST_F(ClassifyTest, server1computer) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0007));
+ client.addExtraOption(pxe);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the second pool.
+ EXPECT_EQ("10.0.0.60", resp->getYiaddr().toText());
+}
+
+// This test checks the complex membership from HA with server2 telephone.
+TEST_F(ClassifyTest, server2Telephone) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0009));
+ client.addExtraOption(pxe);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the third pool.
+ EXPECT_EQ("10.0.0.110", resp->getYiaddr().toText());
+}
+
+// This test checks the complex membership from HA with server2 computer.
+TEST_F(ClassifyTest, server2computer) {
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+
+ // Configure DHCP server.
+ configure(CONFIGS[4], *client.getServer());
+
+ // Add option.
+ OptionPtr pxe(new OptionInt(Option::V4, 93, 0x0007));
+ client.addExtraOption(pxe);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Get an address
+ client.doDORA();
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the forth pool.
+ EXPECT_EQ("10.0.0.160", resp->getYiaddr().toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNone) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\""
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ EXPECT_FALSE(opt);
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedencePool) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.0.0.1", addrs[0].toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceSubnet) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.0.0.2", addrs[0].toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNetwork) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"10.0.0.3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"require-client-classes\": [ \"for-network\" ],"
+ " \"subnet4\": [ { "
+ " \"subnet\": \"10.0.0.0/24\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"10.0.0.10-10.0.0.100\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ]"
+ "}";
+
+ // Create a client requesting domain-name-servers option
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Load the config and perform a DORA
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doDORA());
+
+ // Check response
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ("10.0.0.10", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("10.0.0.3", addrs[0].toText());
+}
} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index a527419f1a..61fc5ffbfe 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -5742,8 +5742,7 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDeriveClientClass) {
SharedNetwork4Ptr net = nets->at(0);
ASSERT_TRUE(net);
- auto classes = net->getClientClasses();
- EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_EQ("alpha", net->getClientClass());
// The first shared network has two subnets.
const Subnet4Collection * subs = net->getAllSubnets();
@@ -5754,15 +5753,12 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDeriveClientClass) {
// shared-network level.
Subnet4Ptr s = checkSubnet(*subs, "192.0.1.0/24", 1, 2, 4);
ASSERT_TRUE(s);
- classes = s->getClientClasses();
- EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_EQ("alpha", s->getClientClass());
// For the second subnet, the values are overridden on subnet level.
// The value should not be inherited.
s = checkSubnet(*subs, "192.0.2.0/24", 1, 2, 4);
- classes = s->getClientClasses();
- EXPECT_TRUE(classes.contains("beta")); // beta defined on subnet level
- EXPECT_FALSE(classes.contains("alpha")); // alpha defined on shared-network level
+ EXPECT_EQ("beta", s->getClientClass()); // beta defined on subnet level
// Ok, now check the second shared network. It doesn't have anything defined
// on shared-network or subnet level, so everything should have default
@@ -5775,8 +5771,7 @@ TEST_F(Dhcp4ParserTest, sharedNetworksDeriveClientClass) {
EXPECT_EQ(1, subs->size());
s = checkSubnet(*subs, "192.0.3.0/24", 1, 2, 4);
- classes = s->getClientClasses();
- EXPECT_TRUE(classes.empty());
+ EXPECT_TRUE(s->getClientClass().empty());
}
// This test checks multiple host data sources.
diff --git a/src/bin/dhcp4/tests/dhcp4_client.cc b/src/bin/dhcp4/tests/dhcp4_client.cc
index 0340a01cea..19565308a4 100644
--- a/src/bin/dhcp4/tests/dhcp4_client.cc
+++ b/src/bin/dhcp4/tests/dhcp4_client.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -238,6 +238,14 @@ Dhcp4Client::appendExtraOptions() {
}
}
+void
+Dhcp4Client::appendClasses() {
+ for (ClientClasses::const_iterator cclass = classes_.cbegin();
+ cclass != classes_.cend(); ++cclass) {
+ context_.query_->addClass(*cclass);
+ }
+}
+
void
Dhcp4Client::doDiscover(const boost::shared_ptr& requested_addr) {
context_.query_ = createMsg(DHCPDISCOVER);
@@ -255,6 +263,7 @@ Dhcp4Client::doDiscover(const boost::shared_ptr& requested_addr) {
context_.query_->setCiaddr(ciaddr_.get());
}
appendExtraOptions();
+ appendClasses();
// Send the message to the server.
sendMsg(context_.query_);
@@ -277,6 +286,8 @@ Dhcp4Client::doInform(const bool set_ciaddr) {
appendPRL();
// Any other options to be sent by a client.
appendExtraOptions();
+ // Add classes.
+ appendClasses();
// The client sending a DHCPINFORM message has an IP address obtained
// by some other means, e.g. static configuration. The lease which we
// are using here is most likely set by the createLease method.
@@ -396,6 +407,8 @@ Dhcp4Client::doRequest() {
appendClientId();
// Any other options to be sent by a client.
appendExtraOptions();
+ // Add classes.
+ appendClasses();
// Send the message to the server.
sendMsg(context_.query_);
// Expect response.
@@ -527,6 +540,12 @@ Dhcp4Client::sendMsg(const Pkt4Ptr& msg) {
msg_copy->setRemoteAddr(msg->getLocalAddr());
msg_copy->setLocalAddr(dest_addr_);
msg_copy->setIface(iface_name_);
+ // Copy classes
+ const ClientClasses& classes = msg->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ msg_copy->addClass(*cclass);
+ }
srv_->fakeReceive(msg_copy);
try {
@@ -552,6 +571,13 @@ Dhcp4Client::addExtraOption(const OptionPtr& opt) {
extra_options_.insert(std::make_pair(opt->getType(), opt));
}
+void
+Dhcp4Client::addClass(const ClientClass& client_class) {
+ if (!classes_.contains(client_class)) {
+ classes_.insert(client_class);
+ }
+}
+
} // end of namespace isc::dhcp::test
} // end of namespace isc::dhcp
} // end of namespace isc
diff --git a/src/bin/dhcp4/tests/dhcp4_client.h b/src/bin/dhcp4/tests/dhcp4_client.h
index b10e5535a0..d86491f1fe 100644
--- a/src/bin/dhcp4/tests/dhcp4_client.h
+++ b/src/bin/dhcp4/tests/dhcp4_client.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -378,12 +378,22 @@ public:
/// @param opt additional option to be sent
void addExtraOption(const OptionPtr& opt);
+ /// @brief Add a client class.
+ ///
+ /// @param client_class name of the class to be added.
+ void addClass(const ClientClass& client_class);
+
private:
/// @brief Appends extra options, previously added with addExtraOption()
///
/// @brief Copies options from extra_options_ into outgoing message
void appendExtraOptions();
+ /// @brief Appends extra classes, previously added with addClass()
+ ///
+ /// @brief Add client classes from classes_ to incoming message
+ void appendClasses();
+
/// @brief Creates and adds Requested IP Address option to the client's
/// query.
///
@@ -500,6 +510,9 @@ private:
/// @brief Extra options the client will send.
OptionCollection extra_options_;
+
+ /// @brief Extra classes to add to the query.
+ ClientClasses classes_;
};
} // end of namespace isc::dhcp::test
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index 99789e1cf5..02eda7cd73 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -2464,6 +2464,75 @@ TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
EXPECT_FALSE(offer->getYiaddr().isV4Zero());
}
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(Dhcpv4SrvTest, clientPoolClassify) {
+ IfaceMgrTestConfig test_config(true);
+ IfaceMgr::instance().openSockets4();
+
+ NakedDhcpv4Srv srv(0);
+
+ // This test configures 2 pools.
+ // The second pool does not play any role here. The client's
+ // IP address belongs to the first pool, so only that first
+ // pool is being tested.
+ string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ "
+ "{ \"pools\": [ { "
+ " \"pool\": \"192.0.2.1 - 192.0.2.100\", "
+ " \"client-class\": \"foo\" }, "
+ " { \"pool\": \"192.0.3.1 - 192.0.3.100\", "
+ " \"client-class\": \"xyzzy\" } ], "
+ " \"subnet\": \"192.0.0.0/16\" } "
+ "],"
+ "\"valid-lifetime\": 4000 }";
+
+ ConstElementPtr json;
+ ASSERT_NO_THROW(json = parseDHCP4(config, true));
+
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
+
+ CfgMgr::instance().commit();
+
+ // check if returned status is OK
+ ASSERT_TRUE(status);
+ comment_ = config::parseAnswer(rcode_, status);
+ ASSERT_EQ(0, rcode_);
+
+ // Create a simple packet that we'll use for classification
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setCiaddr(IOAddress("192.0.2.1"));
+ dis->setIface("eth0");
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ Pkt4Ptr offer = srv.processDiscover(dis);
+ EXPECT_FALSE(offer);
+
+ // Let's add the packet to bar class and try again.
+ dis->addClass("bar");
+
+ // Still not supported, because it belongs to wrong class.
+ offer = srv.processDiscover(dis);
+ EXPECT_FALSE(offer);
+
+ // Let's add it to matching class.
+ dis->addClass("foo");
+
+ // This time it should work
+ offer = srv.processDiscover(dis);
+ ASSERT_TRUE(offer);
+ EXPECT_EQ(DHCPOFFER, offer->getType());
+ EXPECT_FALSE(offer->getYiaddr().isV4Zero());
+}
+
// Verifies last resort option 43 is backward compatible
TEST_F(Dhcpv4SrvTest, option43LastResort) {
IfaceMgrTestConfig test_config(true);
diff --git a/src/bin/dhcp4/tests/parser_unittest.cc b/src/bin/dhcp4/tests/parser_unittest.cc
index b3c822e24a..b73cf22578 100644
--- a/src/bin/dhcp4/tests/parser_unittest.cc
+++ b/src/bin/dhcp4/tests/parser_unittest.cc
@@ -265,6 +265,7 @@ TEST(ParserTest, file) {
"backends.json",
"cassandra.json",
"classify.json",
+ "classify2.json",
"comments.json",
"dhcpv4-over-dhcpv6.json",
"hooks.json",
diff --git a/src/bin/dhcp4/tests/shared_network_unittest.cc b/src/bin/dhcp4/tests/shared_network_unittest.cc
index 64240ec6f5..47005f3f3a 100644
--- a/src/bin/dhcp4/tests/shared_network_unittest.cc
+++ b/src/bin/dhcp4/tests/shared_network_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -1207,6 +1207,41 @@ public:
ASSERT_NO_FATAL_FAILURE(verifyAssignedStats());
}
+ /// @brief Check precedence.
+ ///
+ /// @param config the configuration.
+ /// @param ns_address expected name server address.
+ void testPrecedence(const std::string& config, const std::string& ns_address) {
+ // Create client and set MAC address to the one that has a reservation.
+ Dhcp4Client client(Dhcp4Client::SELECTING);
+ client.setIfaceName("eth1");
+ client.setHWAddress("aa:bb:cc:dd:ee:ff");
+ // Request domain-name-servers.
+ client.requestOptions(DHO_DOMAIN_NAME_SERVERS);
+
+ // Create server configuration.
+ configure(config, *client.getServer());
+
+ // Perform a DORA.
+ doDORA(client, "192.0.2.28", "192.0.2.28");
+
+ // Check response.
+ Pkt4Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+ EXPECT_EQ(DHCPACK, resp->getType());
+ EXPECT_EQ("192.0.2.28", resp->getYiaddr().toText());
+
+ // Check domain-name-servers option.
+ OptionPtr opt = resp->getOption(DHO_DOMAIN_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option4AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ(ns_address, addrs[0].toText());
+ }
+
/// @brief Destructor.
virtual ~Dhcpv4SharedNetworkTest() {
StatsMgr::instance().removeAll();
@@ -2084,4 +2119,449 @@ TEST_F(Dhcpv4SharedNetworkTest, poolInSubnetSelectedByClass) {
doRequest(client2, "192.0.2.100");
});
}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceGlobal) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.1");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceClass) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceClasses) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"beta\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Class order is the insert order
+ testPrecedence(config, "192.0.2.2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceNetwork) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.3");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceSubnet) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.4");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedencePool) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.5");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pool < host reservation
+TEST_F(Dhcpv4SharedNetworkTest, precedenceReservation) {
+ const std::string config =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"valid-lifetime\": 600,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.3\""
+ " }"
+ " ],"
+ " \"subnet4\": ["
+ " {"
+ " \"subnet\": \"192.0.2.0/26\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"192.0.2.1 - 192.0.2.63\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"hw-address\": \"aa:bb:cc:dd:ee:ff\","
+ " \"ip-address\": \"192.0.2.28\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"domain-name-servers\","
+ " \"data\": \"192.0.2.6\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "192.0.2.6");
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp6/dhcp6_lexer.ll b/src/bin/dhcp6/dhcp6_lexer.ll
index a2a9193c11..f02b9b58b6 100644
--- a/src/bin/dhcp6/dhcp6_lexer.ll
+++ b/src/bin/dhcp6/dhcp6_lexer.ll
@@ -363,6 +363,15 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
return isc::dhcp::Dhcp6Parser::make_STRING(tmp, driver.loc_);
}
+\"never\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser6Context::REPLACE_CLIENT_NAME:
+ return isc::dhcp::Dhcp6Parser::make_NEVER(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp6Parser::make_STRING("never", driver.loc_);
+ }
+}
+
(?i:\"never\") {
/* dhcp-ddns value keywords are case insensitive */
if (driver.ctx_ == isc::dhcp::Parser6Context::REPLACE_CLIENT_NAME) {
@@ -753,7 +762,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
case isc::dhcp::Parser6Context::PD_POOLS:
case isc::dhcp::Parser6Context::RESERVATIONS:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
- case isc::dhcp::Parser6Context::CLIENT_CLASS:
case isc::dhcp::Parser6Context::SHARED_NETWORK:
return isc::dhcp::Dhcp6Parser::make_OPTION_DATA(driver.loc_);
default:
@@ -768,7 +776,6 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
case isc::dhcp::Parser6Context::OPTION_DEF:
case isc::dhcp::Parser6Context::OPTION_DATA:
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
- case isc::dhcp::Parser6Context::CLIENT_CLASS:
case isc::dhcp::Parser6Context::LOGGERS:
case isc::dhcp::Parser6Context::SHARED_NETWORK:
return isc::dhcp::Dhcp6Parser::make_NAME(driver.loc_);
@@ -1134,6 +1141,18 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
}
}
+\"require-client-classes\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser6Context::SUBNET6:
+ case isc::dhcp::Parser6Context::POOLS:
+ case isc::dhcp::Parser6Context::PD_POOLS:
+ case isc::dhcp::Parser6Context::SHARED_NETWORK:
+ return isc::dhcp::Dhcp6Parser::make_REQUIRE_CLIENT_CLASSES(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp6Parser::make_STRING("require-client-classes", driver.loc_);
+ }
+}
+
\"client-class\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::SUBNET6:
@@ -1150,13 +1169,21 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
\"test\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::CLIENT_CLASSES:
- case isc::dhcp::Parser6Context::CLIENT_CLASS:
return isc::dhcp::Dhcp6Parser::make_TEST(driver.loc_);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("test", driver.loc_);
}
}
+\"only-if-required\" {
+ switch(driver.ctx_) {
+ case isc::dhcp::Parser6Context::CLIENT_CLASSES:
+ return isc::dhcp::Dhcp6Parser::make_ONLY_IF_REQUIRED(driver.loc_);
+ default:
+ return isc::dhcp::Dhcp6Parser::make_STRING("only-if-required", driver.loc_);
+ }
+}
+
\"reservations\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::SUBNET6:
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index a7e897c2e2..da4320145b 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -66,6 +66,14 @@ which cannot be found in the configuration. Either a hook written
before the classification was added to Kea is used, or class naming is
inconsistent.
+% DHCP6_CLASS_UNDEFINED required class %1 has no definition
+This debug message informs that a class is listed for required evaluation but
+has no definition.
+
+% DHCP6_CLASS_UNTESTABLE required class %1 has no test expression
+This debug message informs that a class was listed for required evaluation but
+its definition does not include a test expression to evaluate.
+
% DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the Kea control system by the IPv6 DHCP server.
diff --git a/src/bin/dhcp6/dhcp6_parser.yy b/src/bin/dhcp6/dhcp6_parser.yy
index ef73454555..38337d8bde 100644
--- a/src/bin/dhcp6/dhcp6_parser.yy
+++ b/src/bin/dhcp6/dhcp6_parser.yy
@@ -123,7 +123,9 @@ using namespace std;
HOST_RESERVATION_IDENTIFIERS "host-reservation-identifiers"
CLIENT_CLASSES "client-classes"
+ REQUIRE_CLIENT_CLASSES "require-client-classes"
TEST "test"
+ ONLY_IF_REQUIRED "only-if-required"
CLIENT_CLASS "client-class"
RESERVATIONS "reservations"
@@ -977,6 +979,7 @@ subnet6_param: preferred_lifetime
| id
| rapid_commit
| client_class
+ | require_client_classes
| reservations
| reservation_mode
| relay
@@ -1010,13 +1013,23 @@ interface_id: INTERFACE_ID {
};
client_class: CLIENT_CLASS {
- ctx.enter(ctx.CLIENT_CLASS);
+ ctx.enter(ctx.NO_KEYWORD);
} COLON STRING {
ElementPtr cls(new StringElement($4, ctx.loc2pos(@4)));
ctx.stack_.back()->set("client-class", cls);
ctx.leave();
};
+require_client_classes: REQUIRE_CLIENT_CLASSES {
+ ElementPtr c(new ListElement(ctx.loc2pos(@1)));
+ ctx.stack_.back()->set("require-client-classes", c);
+ ctx.stack_.push_back(c);
+ ctx.enter(ctx.NO_KEYWORD);
+} COLON list_strings {
+ ctx.stack_.pop_back();
+ ctx.leave();
+};
+
reservation_mode: RESERVATION_MODE {
ctx.enter(ctx.RESERVATION_MODE);
} COLON hr_mode {
@@ -1084,6 +1097,7 @@ shared_network_param: name
| relay
| reservation_mode
| client_class
+ | require_client_classes
| preferred_lifetime
| rapid_commit
| valid_lifetime
@@ -1373,6 +1387,7 @@ pool_params: pool_param
pool_param: pool_entry
| option_data_list
| client_class
+ | require_client_classes
| user_context
| comment
| unknown_map_entry
@@ -1494,6 +1509,7 @@ pd_pool_param: pd_prefix
| pd_delegated_len
| option_data_list
| client_class
+ | require_client_classes
| excluded_prefix
| excluded_prefix_len
| user_context
@@ -1713,6 +1729,7 @@ not_empty_client_class_params: client_class_param
client_class_param: client_class_name
| client_class_test
+ | only_if_required
| option_data_list
| user_context
| comment
@@ -1729,6 +1746,11 @@ client_class_test: TEST {
ctx.leave();
};
+only_if_required: ONLY_IF_REQUIRED COLON BOOLEAN {
+ ElementPtr b(new BoolElement($3, ctx.loc2pos(@3)));
+ ctx.stack_.back()->set("only-if-required", b);
+};
+
// --- end of client classes ---------------------------------
// --- server-id ---------------------------------------------
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 5b39edaad9..987280acab 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -937,16 +937,14 @@ Dhcpv6Srv::buildCfgOptionList(const Pkt6Ptr& question,
// Each class in the incoming packet
const ClientClasses& classes = question->getClasses();
- for (ClientClasses::const_iterator cclass = classes.begin();
- cclass != classes.end(); ++cclass) {
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
// Find the client class definition for this class
const ClientClassDefPtr& ccdef = CfgMgr::instance().getCurrentCfg()->
getClientClassDictionary()->findClass(*cclass);
if (!ccdef) {
- // Not found: the class is not configured
- if (((*cclass).size() <= VENDOR_CLASS_PREFIX.size()) ||
- ((*cclass).compare(0, VENDOR_CLASS_PREFIX.size(), VENDOR_CLASS_PREFIX) != 0)) {
- // Not a VENDOR_CLASS_* so should be configured
+ // Not found: the class is built-in or not configured
+ if (!isClientClassBuiltIn(*cclass)) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_UNCONFIGURED)
.arg(question->getLabel())
.arg(*cclass);
@@ -2555,6 +2553,7 @@ Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
assignLeases(solicit, response, ctx);
setReservedClientClasses(solicit, ctx);
+ requiredClassify(solicit, ctx);
copyClientOptions(solicit, response);
CfgOptionList co_list;
@@ -2595,6 +2594,7 @@ Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
assignLeases(request, reply, ctx);
setReservedClientClasses(request, ctx);
+ requiredClassify(request, ctx);
copyClientOptions(request, reply);
CfgOptionList co_list;
@@ -2631,6 +2631,7 @@ Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
extendLeases(renew, reply, ctx);
setReservedClientClasses(renew, ctx);
+ requiredClassify(renew, ctx);
copyClientOptions(renew, reply);
CfgOptionList co_list;
@@ -2667,6 +2668,7 @@ Dhcpv6Srv::processRebind(const Pkt6Ptr& rebind) {
extendLeases(rebind, reply, ctx);
setReservedClientClasses(rebind, ctx);
+ requiredClassify(rebind, ctx);
copyClientOptions(rebind, reply);
CfgOptionList co_list;
@@ -2698,6 +2700,7 @@ Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
}
setReservedClientClasses(confirm, ctx);
+ requiredClassify(confirm, ctx);
// Get IA_NAs from the Confirm. If there are none, the message is
// invalid and must be discarded. There is nothing more to do.
@@ -2798,6 +2801,7 @@ Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
}
setReservedClientClasses(release, ctx);
+ requiredClassify(release, ctx);
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
@@ -2834,6 +2838,7 @@ Dhcpv6Srv::processDecline(const Pkt6Ptr& decline) {
}
setReservedClientClasses(decline, ctx);
+ requiredClassify(decline, ctx);
// Copy client options (client-id, also relay information if present)
copyClientOptions(decline, reply);
@@ -3122,6 +3127,7 @@ Dhcpv6Srv::processInfRequest(const Pkt6Ptr& inf_request) {
}
setReservedClientClasses(inf_request, ctx);
+ requiredClassify(inf_request, ctx);
// Create a Reply packet, with the same trans-id as the client's.
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, inf_request->getTransid()));
@@ -3190,46 +3196,53 @@ void Dhcpv6Srv::classifyByVendor(const Pkt6Ptr& pkt, std::string& classes) {
}
void Dhcpv6Srv::classifyPacket(const Pkt6Ptr& pkt) {
- string classes = "";
+ // All packets belongs to ALL
+ pkt->addClass("ALL");
+ string classes = "ALL ";
// First phase: built-in vendor class processing
classifyByVendor(pkt, classes);
// Run match expressions
// Note getClientClassDictionary() cannot be null
- const ClientClassDefMapPtr& defs_ptr = CfgMgr::instance().getCurrentCfg()->
- getClientClassDictionary()->getClasses();
- for (ClientClassDefMap::const_iterator it = defs_ptr->begin();
- it != defs_ptr->end(); ++it) {
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ const ClientClassDefListPtr& defs_ptr = dict->getClasses();
+ for (ClientClassDefList::const_iterator it = defs_ptr->cbegin();
+ it != defs_ptr->cend(); ++it) {
// Note second cannot be null
- const ExpressionPtr& expr_ptr = it->second->getMatchExpr();
+ const ExpressionPtr& expr_ptr = (*it)->getMatchExpr();
// Nothing to do without an expression to evaluate
if (!expr_ptr) {
continue;
}
+ // Not the right time if only when required
+ if ((*it)->getRequired()) {
+ continue;
+ }
// Evaluate the expression which can return false (no match),
// true (match) or raise an exception (error)
try {
bool status = evaluateBool(*expr_ptr, *pkt);
if (status) {
LOG_INFO(dhcp6_logger, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg(status);
// Matching: add the class
- pkt->addClass(it->first);
- classes += it->first + " ";
+ pkt->addClass((*it)->getName());
+ classes += (*it)->getName() + " ";
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg(status);
}
} catch (const Exception& ex) {
LOG_ERROR(dhcp6_logger, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg(ex.what());
} catch (...) {
LOG_ERROR(dhcp6_logger, EVAL_RESULT)
- .arg(it->first)
+ .arg((*it)->getName())
.arg("get exception?");
}
}
@@ -3239,18 +3252,109 @@ void
Dhcpv6Srv::setReservedClientClasses(const Pkt6Ptr& pkt,
const AllocEngine::ClientContext6& ctx) {
if (ctx.currentHost() && pkt) {
- BOOST_FOREACH(const std::string& client_class,
- ctx.currentHost()->getClientClasses6()) {
- pkt->addClass(client_class);
+ const ClientClasses& classes = ctx.currentHost()->getClientClasses6();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ pkt->addClass(*cclass);
}
}
const ClientClasses& classes = pkt->getClasses();
if (!classes.empty()) {
- std::string joined_classes = boost::algorithm::join(classes, ", ");
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_ASSIGNED)
.arg(pkt->getLabel())
- .arg(joined_classes);
+ .arg(classes.toText());
+ }
+}
+
+void
+Dhcpv6Srv::requiredClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx) {
+ // First collect required classes
+ ClientClasses classes = pkt->getClasses(true);
+ Subnet6Ptr subnet = ctx.subnet_;
+
+ if (subnet) {
+ // Begin by the shared-network
+ SharedNetwork6Ptr network;
+ subnet->getSharedNetwork(network);
+ if (network) {
+ const ClientClasses& to_add = network->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+
+ // Followed by the subnet
+ const ClientClasses& to_add = subnet->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+
+ // And finish by pools
+ BOOST_FOREACH(const AllocEngine::ResourceType& resource,
+ ctx.allocated_resources_) {
+ PoolPtr pool = ctx.subnet_->getPool(resource.second == 128 ?
+ Lease::TYPE_NA :
+ Lease::TYPE_PD,
+ resource.first,
+ false);
+ if (pool) {
+ const ClientClasses& to_add = pool->getRequiredClasses();
+ for (ClientClasses::const_iterator cclass = to_add.cbegin();
+ cclass != to_add.cend(); ++cclass) {
+ classes.insert(*cclass);
+ }
+ }
+ }
+
+ // host reservation???
+ }
+
+ // Run match expressions
+ // Note getClientClassDictionary() cannot be null
+ const ClientClassDictionaryPtr& dict =
+ CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ const ClientClassDefPtr class_def = dict->findClass(*cclass);
+ if (!class_def) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_UNDEFINED)
+ .arg(*cclass);
+ continue;
+ }
+ const ExpressionPtr& expr_ptr = class_def->getMatchExpr();
+ // Nothing to do without an expression to evaluate
+ if (!expr_ptr) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLASS_UNTESTABLE)
+ .arg(*cclass);
+ continue;
+ }
+ // Evaluate the expression which can return false (no match),
+ // true (match) or raise an exception (error)
+ try {
+ bool status = evaluateBool(*expr_ptr, *pkt);
+ if (status) {
+ LOG_INFO(dhcp6_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ // Matching: add the class
+ pkt->addClass(*cclass);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(status);
+ }
+ } catch (const Exception& ex) {
+ LOG_ERROR(dhcp6_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg(ex.what());
+ } catch (...) {
+ LOG_ERROR(dhcp6_logger, EVAL_RESULT)
+ .arg(*cclass)
+ .arg("get exception?");
+ }
}
}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index 96d4432e39..898bd0c195 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -651,6 +651,19 @@ protected:
void setReservedClientClasses(const Pkt6Ptr& pkt,
const AllocEngine::ClientContext6& ctx);
+ /// @brief Assigns incoming packet to zero or more classes (required pass).
+ ///
+ /// @note This required classification evaluates all classes which
+ /// were marked for required evaluation. Classes are collected so
+ /// evaluated in the reversed order than output option processing.
+ ///
+ /// @note The only-if-required flag is related because it avoids
+ /// double evaluation (which is not forbidden).
+ ///
+ /// @param pkt packet to be classified
+ /// @param ctx allocation context where to get informations
+ void requiredClassify(const Pkt6Ptr& pkt, AllocEngine::ClientContext6& ctx);
+
/// @brief Attempts to get a MAC/hardware address using configured sources
///
/// Tries to extract MAC/hardware address information from the packet
diff --git a/src/bin/dhcp6/location.hh b/src/bin/dhcp6/location.hh
index c940d232ff..e2446254dc 100644
--- a/src/bin/dhcp6/location.hh
+++ b/src/bin/dhcp6/location.hh
@@ -1,4 +1,4 @@
-// Generated 201803271227
+// Generated 201804061423
// A Bison parser, made by GNU Bison 3.0.4.
// Locations for Bison parsers in C++
diff --git a/src/bin/dhcp6/parser_context.cc b/src/bin/dhcp6/parser_context.cc
index abef30928e..0ed107886f 100644
--- a/src/bin/dhcp6/parser_context.cc
+++ b/src/bin/dhcp6/parser_context.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -180,8 +180,6 @@ Parser6Context::contextName()
return ("reservations");
case RELAY:
return ("relay");
- case CLIENT_CLASS:
- return ("client-class");
case LOGGERS:
return ("loggers");
case OUTPUT_OPTIONS:
diff --git a/src/bin/dhcp6/parser_context.h b/src/bin/dhcp6/parser_context.h
index 8fe201b157..a3f7cc7101 100644
--- a/src/bin/dhcp6/parser_context.h
+++ b/src/bin/dhcp6/parser_context.h
@@ -280,9 +280,6 @@ public:
/// Used while parsing Dhcp6/subnet6/relay structures.
RELAY,
- /// Used while parsing Dhcp6/client-classes structures.
- CLIENT_CLASS,
-
/// Used while parsing Logging/loggers structures.
LOGGERS,
diff --git a/src/bin/dhcp6/position.hh b/src/bin/dhcp6/position.hh
index b7eec0678d..ae5967d920 100644
--- a/src/bin/dhcp6/position.hh
+++ b/src/bin/dhcp6/position.hh
@@ -1,4 +1,4 @@
-// Generated 201803271227
+// Generated 201804061423
// A Bison parser, made by GNU Bison 3.0.4.
// Positions for Bison parsers in C++
diff --git a/src/bin/dhcp6/stack.hh b/src/bin/dhcp6/stack.hh
index 0c7f8e17b8..75bd52db7f 100644
--- a/src/bin/dhcp6/stack.hh
+++ b/src/bin/dhcp6/stack.hh
@@ -1,4 +1,4 @@
-// Generated 201803271227
+// Generated 201804061423
// A Bison parser, made by GNU Bison 3.0.4.
// Stack handling for Bison parsers in C++
diff --git a/src/bin/dhcp6/tests/classify_unittests.cc b/src/bin/dhcp6/tests/classify_unittests.cc
index b63a297c54..631ae9f777 100644
--- a/src/bin/dhcp6/tests/classify_unittests.cc
+++ b/src/bin/dhcp6/tests/classify_unittests.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2016-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -46,6 +46,27 @@ namespace {
/// the 'foo' value.
/// - There is one subnet specified 2001:db8:1::/48 with pool of
/// IPv6 addresses.
+///
+/// - Configuration 1:
+/// - Used for complex membership (example taken from HA)
+/// - 1 subnet: 2001:db8:1::/48
+/// - 4 pools: 2001:db8:1:1::/64, 2001:db8:1:2::/64,
+/// 2001:db8:1:3::/64 and 2001:db8:1:4::/64
+/// - 4 classes to compose:
+/// server1 and server2 for each HA server
+/// option 1234 'foo' aka telephones
+/// option 1234 'bar' aka computers
+///
+/// - Configuration 2:
+/// - Used for complex membership (example taken from HA) and pd-pools
+/// - 1 subnet: 2001:db8::/32
+/// - 4 pd-pools: 2001:db8:1::/48, 2001:db8:2::/48,
+/// 2001:db8:3::/48 and 2001:db8:4::/48
+/// - 4 classes to compose:
+/// server1 and server2 for each HA server
+/// option 1234 'foo' aka telephones
+/// option 1234 'bar' aka computers
+///
const char* CONFIGS[] = {
// Configuration 0
"{ \"interfaces-config\": {"
@@ -104,7 +125,132 @@ const char* CONFIGS[] = {
" \"client-classes\": [ \"reserved-class1\", \"reserved-class2\" ]"
" } ]"
" } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 1
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ "
+ "{"
+ " \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\""
+ "} ],"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"server1\""
+ "},"
+ "{"
+ " \"name\": \"server2\""
+ "},"
+ "{"
+ " \"name\": \"telephones\","
+ " \"test\": \"option[host-name].text == 'foo'\""
+ "},"
+ "{"
+ " \"name\": \"computers\","
+ " \"test\": \"option[host-name].text == 'bar'\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_telephones\","
+ " \"test\": \"member('server1') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_computers\","
+ " \"test\": \"member('server1') and member('computers')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_telephones\","
+ " \"test\": \"member('server2') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_computers\","
+ " \"test\": \"member('server2') and member('computers')\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\","
+ " \"pools\": [ "
+ " { \"pool\": \"2001:db8:1:1::/64\","
+ " \"client-class\": \"server1_and_telephones\" },"
+ " { \"pool\": \"2001:db8:1:2::/64\","
+ " \"client-class\": \"server1_and_computers\" },"
+ " { \"pool\": \"2001:db8:1:3::/64\","
+ " \"client-class\": \"server2_and_telephones\" },"
+ " { \"pool\": \"2001:db8:1:4::/64\","
+ " \"client-class\": \"server2_and_computers\" } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }",
+
+ // Configuration 2
+ "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-def\": [ "
+ "{"
+ " \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\""
+ "} ],"
+ "\"client-classes\": ["
+ "{"
+ " \"name\": \"server1\""
+ "},"
+ "{"
+ " \"name\": \"server2\""
+ "},"
+ "{"
+ " \"name\": \"telephones\","
+ " \"test\": \"option[host-name].text == 'foo'\""
+ "},"
+ "{"
+ " \"name\": \"computers\","
+ " \"test\": \"option[host-name].text == 'bar'\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_telephones\","
+ " \"test\": \"member('server1') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server1_and_computers\","
+ " \"test\": \"member('server1') and member('computers')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_telephones\","
+ " \"test\": \"member('server2') and member('telephones')\""
+ "},"
+ "{"
+ " \"name\": \"server2_and_computers\","
+ " \"test\": \"member('server2') and member('computers')\""
+ "}"
+ "],"
+ "\"subnet6\": [ "
+ "{ \"subnet\": \"2001:db8::/32\", "
+ " \"interface\": \"eth1\","
+ " \"pd-pools\": [ "
+ " { \"prefix\": \"2001:db8:1::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server1_and_telephones\" },"
+ " { \"prefix\": \"2001:db8:2::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server1_and_computers\" },"
+ " { \"prefix\": \"2001:db8:3::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server2_and_telephones\" },"
+ " { \"prefix\": \"2001:db8:4::\","
+ " \"prefix-len\": 48, \"delegated-len\": 64,"
+ " \"client-class\": \"server2_and_computers\" } ]"
+ " } ],"
"\"valid-lifetime\": 4000 }"
+
};
/// @brief Test fixture class for testing client classification by the
@@ -300,6 +446,189 @@ TEST_F(ClassifyTest, matchClassification) {
EXPECT_FALSE(opt3);
}
+// Check that only-if-required classes are not evaluated by classifyPacket
+TEST_F(ClassifyTest, required) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"only-if-required\": true, "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // No packet is in the router class
+ EXPECT_FALSE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_FALSE(query3->inClass("router"));
+
+ // Process queries
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+
+ // Classification processing should do nothing
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_FALSE(opt1);
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
+// Checks that when only-if-required classes are still evaluated
+TEST_F(ClassifyTest, requiredClassification) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"require-client-classes\": [ \"router\" ], "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"router\", "
+ " \"only-if-required\": true, "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"option[host-name].text == 'foo'\" } ] }";
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to the first 2 queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname);
+ query1->addOption(hostname);
+ query3->addOption(hostname);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // No packet is in the router class yet
+ EXPECT_FALSE(query1->inClass("router"));
+ EXPECT_FALSE(query2->inClass("router"));
+ EXPECT_FALSE(query3->inClass("router"));
+
+ // Process queries
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_TRUE(opt1);
+
+ // But only for the first query: second was not classified
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+
+ // But only for the first query: third has no ORO
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_FALSE(opt3);
+}
+
// Checks subnet options have the priority over class options
TEST_F(ClassifyTest, subnetClassPriority) {
IfaceMgrTestConfig test_config(true);
@@ -700,13 +1029,109 @@ TEST_F(ClassifyTest, clientClassifyPool) {
EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
}
+// Checks if the client-class field is indeed used for pool selection.
+TEST_F(ClassifyTest, clientClassifyPool) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // This test configures 2 pools.
+ // The second pool does not play any role here. The client's
+ // IP address belongs to the first pool, so only that first
+ // pool is being tested.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"client-classes\": [ "
+ " { "
+ " \"name\": \"foo\" "
+ " }, "
+ " { "
+ " \"name\": \"bar\" "
+ " } "
+ "], "
+ "\"subnet6\": [ "
+ " { \"pools\": [ "
+ " { "
+ " \"pool\": \"2001:db8:1::/64\", "
+ " \"client-class\": \"foo\" "
+ " }, "
+ " { "
+ " \"pool\": \"2001:db8:2::/64\", "
+ " \"client-class\": \"xyzzy\" "
+ " } "
+ " ], "
+ " \"subnet\": \"2001:db8:2::/40\" "
+ " } "
+ "], "
+ "\"valid-lifetime\": 4000 }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query1->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ Pkt6Ptr query2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ Pkt6Ptr query3 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ query3->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+
+ // This discover does not belong to foo class, so it will not
+ // be serviced
+ srv.classifyPacket(query1);
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ ASSERT_TRUE(response1);
+ OptionPtr ia_na1 = response1->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na1);
+ EXPECT_TRUE(ia_na1->getOption(D6O_STATUS_CODE));
+ EXPECT_FALSE(ia_na1->getOption(D6O_IAADDR));
+
+ // Let's add the packet to bar class and try again.
+ query2->addClass("bar");
+ // Still not supported, because it belongs to wrong class.
+ srv.classifyPacket(query2);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ ASSERT_TRUE(response2);
+ OptionPtr ia_na2 = response2->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na2);
+ EXPECT_TRUE(ia_na2->getOption(D6O_STATUS_CODE));
+ EXPECT_FALSE(ia_na2->getOption(D6O_IAADDR));
+
+ // Let's add it to matching class.
+ query3->addClass("foo");
+ // This time it should work
+ srv.classifyPacket(query3);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+ ASSERT_TRUE(response3);
+ OptionPtr ia_na3 = response3->getOption(D6O_IA_NA);
+ ASSERT_TRUE(ia_na3);
+ EXPECT_FALSE(ia_na3->getOption(D6O_STATUS_CODE));
+ EXPECT_TRUE(ia_na3->getOption(D6O_IAADDR));
+}
+
// Tests whether a packet with custom vendor-class (not erouter or docsis)
// is classified properly.
TEST_F(ClassifyTest, vendorClientClassification2) {
NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT.
- Pkt6Ptr sol = createSolicit("2001:db8:1::3");
+ Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ sol->setRemoteAddr(IOAddress("2001:db8:1::3"));
+ sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ OptionPtr clientid = generateClientId();
+ sol->addOption(clientid);
// Now let's add a vendor-class with id=1234 and content "foo"
OptionVendorClassPtr vendor_class(new OptionVendorClass(Option::V6, 1234));
@@ -865,5 +1290,695 @@ TEST_F(ClassifyTest, clientClassesInHostReservations) {
"2001:db8:1::100"));
}
+// Check classification using membership expressions.
+TEST_F(ClassifyTest, member) {
+ IfaceMgrTestConfig test_config(true);
+
+ NakedDhcpv6Srv srv(0);
+
+ // The router class matches incoming packets with foo in a host-name
+ // option (code 1234) and sets an ipv6-forwarding option in the response.
+ std::string config = "{ \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ] }, "
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"option-def\": [ "
+ "{ \"name\": \"host-name\","
+ " \"code\": 1234,"
+ " \"type\": \"string\" },"
+ "{ \"name\": \"ipv6-forwarding\","
+ " \"code\": 2345,"
+ " \"type\": \"boolean\" }],"
+ "\"subnet6\": [ "
+ "{ \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ], "
+ " \"subnet\": \"2001:db8:1::/48\", "
+ " \"interface\": \"eth1\" } ],"
+ "\"client-classes\": [ "
+ "{ \"name\": \"not-foo\", "
+ " \"test\": \"not (option[host-name].text == 'foo')\""
+ "},"
+ "{ \"name\": \"foo\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"true\" } ], "
+ " \"test\": \"not member('not-foo')\""
+ "},"
+ "{ \"name\": \"bar\", "
+ " \"test\": \"option[host-name].text == 'bar'\""
+ "},"
+ "{ \"name\": \"baz\", "
+ " \"test\": \"option[host-name].text == 'baz'\""
+ "},"
+ "{ \"name\": \"barz\", "
+ " \"option-data\": ["
+ " { \"name\": \"ipv6-forwarding\", "
+ " \"data\": \"false\" } ], "
+ " \"test\": \"member('bar') or member('baz')\" } ] }";
+
+ ASSERT_NO_THROW(configure(config));
+
+ // Create packets with enough to select the subnet
+ OptionPtr clientid = generateClientId();
+ Pkt6Ptr query1(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query1->setRemoteAddr(IOAddress("fe80::abcd"));
+ query1->addOption(clientid);
+ query1->setIface("eth1");
+ query1->addOption(generateIA(D6O_IA_NA, 123, 1500, 3000));
+ Pkt6Ptr query2(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query2->setRemoteAddr(IOAddress("fe80::abcd"));
+ query2->addOption(clientid);
+ query2->setIface("eth1");
+ query2->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+ Pkt6Ptr query3(new Pkt6(DHCPV6_SOLICIT, 1234));
+ query3->setRemoteAddr(IOAddress("fe80::abcd"));
+ query3->addOption(clientid);
+ query3->setIface("eth1");
+ query3->addOption(generateIA(D6O_IA_NA, 345, 1500, 3000));
+
+ // Create and add an ORO option to queries
+ OptionUint16ArrayPtr oro(new OptionUint16Array(Option::V6, D6O_ORO));
+ ASSERT_TRUE(oro);
+ oro->addValue(2345);
+ query1->addOption(oro);
+ query2->addOption(oro);
+ query3->addOption(oro);
+
+ // Create and add a host-name option to the first and last queries
+ OptionStringPtr hostname1(new OptionString(Option::V6, 1234, "foo"));
+ ASSERT_TRUE(hostname1);
+ query1->addOption(hostname1);
+ OptionStringPtr hostname3(new OptionString(Option::V6, 1234, "baz"));
+ ASSERT_TRUE(hostname3);
+ query3->addOption(hostname3);
+
+ // Classify packets
+ srv.classifyPacket(query1);
+ srv.classifyPacket(query2);
+ srv.classifyPacket(query3);
+
+ // Check classes
+ EXPECT_FALSE(query1->inClass("not-foo"));
+ EXPECT_TRUE(query1->inClass("foo"));
+ EXPECT_FALSE(query1->inClass("bar"));
+ EXPECT_FALSE(query1->inClass("baz"));
+ EXPECT_FALSE(query1->inClass("barz"));
+
+ EXPECT_TRUE(query2->inClass("not-foo"));
+ EXPECT_FALSE(query2->inClass("foo"));
+ EXPECT_FALSE(query2->inClass("bar"));
+ EXPECT_FALSE(query2->inClass("baz"));
+ EXPECT_FALSE(query2->inClass("barz"));
+
+ EXPECT_TRUE(query3->inClass("not-foo"));
+ EXPECT_FALSE(query3->inClass("foo"));
+ EXPECT_FALSE(query3->inClass("bar"));
+ EXPECT_TRUE(query3->inClass("baz"));
+ EXPECT_TRUE(query3->inClass("barz"));
+
+ // Process queries
+ Pkt6Ptr response1 = srv.processSolicit(query1);
+ Pkt6Ptr response2 = srv.processSolicit(query2);
+ Pkt6Ptr response3 = srv.processSolicit(query3);
+
+ // Classification processing should add an ip-forwarding option
+ OptionPtr opt1 = response1->getOption(2345);
+ EXPECT_TRUE(opt1);
+ OptionCustomPtr ipf1 =
+ boost::dynamic_pointer_cast(opt1);
+ ASSERT_TRUE(ipf1);
+ EXPECT_TRUE(ipf1->readBoolean());
+
+ // But not the second query which was not classified
+ OptionPtr opt2 = response2->getOption(2345);
+ EXPECT_FALSE(opt2);
+
+ // The third has the option but with another value
+ OptionPtr opt3 = response3->getOption(2345);
+ EXPECT_TRUE(opt3);
+ OptionCustomPtr ipf3 =
+ boost::dynamic_pointer_cast(opt3);
+ ASSERT_TRUE(ipf3);
+ EXPECT_FALSE(ipf3->readBoolean());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNone) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ EXPECT_FALSE(opt);
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedencePool) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::1", addrs[0].toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceSubnet) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::2", addrs[0].toText());
+}
+
+// This test checks the precedence order in required evaluation.
+// This order is: shared-network > subnet > pools
+TEST_F(ClassifyTest, precedenceNetwork) {
+ std::string config =
+ "{"
+ "\"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ "},"
+ "\"valid-lifetime\": 600,"
+ "\"client-classes\": ["
+ " {"
+ " \"name\": \"all\","
+ " \"test\": \"'' == ''\""
+ " },"
+ " {"
+ " \"name\": \"for-pool\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-subnet\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " } ]"
+ " },"
+ " {"
+ " \"name\": \"for-network\","
+ " \"test\": \"member('all')\","
+ " \"only-if-required\": true,"
+ " \"option-data\": [ {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " } ]"
+ " }"
+ "],"
+ "\"shared-networks\": [ {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"require-client-classes\": [ \"for-network\" ],"
+ " \"subnet6\": [ { "
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 1,"
+ " \"require-client-classes\": [ \"for-subnet\" ],"
+ " \"pools\": [ { "
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"require-client-classes\": [ \"for-pool\" ]"
+ " } ]"
+ " } ]"
+ "} ],"
+ "\"valid-lifetime\": 600"
+ "}";
+
+ // Create a client requesting dns-servers option
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Load the config and perform a SARR
+ configure(config, *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ("2001:db8:1::3", addrs[0].toText());
+}
+
+// This test checks the complex membership from HA with server1 telephone.
+TEST_F(ClassifyTest, server1Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the first pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:1::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server1 computer.
+TEST_F(ClassifyTest, server1Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the second pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:2::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 telephone.
+TEST_F(ClassifyTest, server2Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the third pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:3::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 computer.
+TEST_F(ClassifyTest, server2Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestAddress(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[1], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The address is from the forth pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1:4::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server1 telephone
+// with prefixes.
+TEST_F(ClassifyTest, pDserver1Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the first pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:1::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server1 computer
+// with prefix.
+TEST_F(ClassifyTest, pDserver1Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server1
+ client.addClass("server1");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the second pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:2::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 telephone
+// with prefixes.
+TEST_F(ClassifyTest, pDserver2Telephone) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "foo"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the third pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:3::", lease_client.addr_.toText());
+}
+
+// This test checks the complex membership from HA with server2 computer
+// with prefix.
+TEST_F(ClassifyTest, pDserver2Computer) {
+ // Create a client.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ ASSERT_NO_THROW(client.requestPrefix(0xabca0));
+
+ // Add option.
+ OptionStringPtr hostname(new OptionString(Option::V6, 1234, "bar"));
+ client.addExtraOption(hostname);
+
+ // Add server2
+ client.addClass("server2");
+
+ // Load the config and perform a SARR
+ configure(CONFIGS[2], *client.getServer());
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // The prefix is from the forth pool.
+ ASSERT_EQ(1, client.getLeaseNum());
+ Lease6 lease_client = client.getLease(0);
+ EXPECT_EQ("2001:db8:4::", lease_client.addr_.toText());
+}
} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index d9b63a55e0..120e41d27a 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -6202,9 +6202,7 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) {
// Let's check the first one.
SharedNetwork6Ptr net = nets->at(0);
ASSERT_TRUE(net);
-
- auto classes = net->getClientClasses();
- EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_EQ("alpha", net->getClientClass());
const Subnet6Collection * subs = net->getAllSubnets();
ASSERT_TRUE(subs);
@@ -6214,16 +6212,13 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) {
// shared-network level.
Subnet6Ptr s = checkSubnet(*subs, "2001:db1::/48", 900, 1800, 3600, 7200);
ASSERT_TRUE(s);
- classes = s->getClientClasses();
- EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_EQ("alpha", s->getClientClass());
// For the second subnet, the values are overridden on subnet level.
// The value should not be inherited.
s = checkSubnet(*subs, "2001:db2::/48", 900, 1800, 3600, 7200);
ASSERT_TRUE(s);
- classes = s->getClientClasses();
- EXPECT_TRUE(classes.contains("beta")); // beta defined on subnet level
- EXPECT_FALSE(classes.contains("alpha")); // alpha defined on shared-network level
+ EXPECT_EQ("beta", s->getClientClass()); // beta defined on subnet level
// Ok, now check the second shared network. It doesn't have
// anything defined on shared-network or subnet level, so
@@ -6237,8 +6232,7 @@ TEST_F(Dhcp6ParserTest, sharedNetworksDeriveClientClass) {
// This subnet should derive its renew-timer from global scope.
s = checkSubnet(*subs, "2001:db3::/48", 900, 1800, 3600, 7200);
- classes = s->getClientClasses();
- EXPECT_TRUE(classes.empty());
+ EXPECT_TRUE(s->getClientClass().empty());
}
// Tests if rapid-commit is derived properly.
diff --git a/src/bin/dhcp6/tests/dhcp6_client.cc b/src/bin/dhcp6/tests/dhcp6_client.cc
index f32189d294..8ab931cd6c 100644
--- a/src/bin/dhcp6/tests/dhcp6_client.cc
+++ b/src/bin/dhcp6/tests/dhcp6_client.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -423,6 +423,12 @@ Dhcp6Client::createMsg(const uint8_t msg_type) {
}
}
+ // Add classes.
+ for (ClientClasses::const_iterator cclass = classes_.cbegin();
+ cclass != classes_.cend(); ++cclass) {
+ msg->addClass(*cclass);
+ }
+
return (msg);
}
@@ -930,6 +936,13 @@ Dhcp6Client::sendMsg(const Pkt6Ptr& msg) {
msg_copy->setLocalAddr(dest_addr_);
msg_copy->setIface(iface_name_);
+ // Copy classes
+ const ClientClasses& classes = msg->getClasses();
+ for (ClientClasses::const_iterator cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ msg_copy->addClass(*cclass);
+ }
+
srv_->fakeReceive(msg_copy);
srv_->run();
}
@@ -969,6 +982,18 @@ Dhcp6Client::clearExtraOptions() {
extra_options_.clear();
}
+void
+Dhcp6Client::addClass(const ClientClass& client_class) {
+ if (!classes_.contains(client_class)) {
+ classes_.insert(client_class);
+ }
+}
+
+void
+Dhcp6Client::clearClasses() {
+ classes_.clear();
+}
+
void
Dhcp6Client::printConfiguration() const {
diff --git a/src/bin/dhcp6/tests/dhcp6_client.h b/src/bin/dhcp6/tests/dhcp6_client.h
index 3402bc6c26..80ff2c37c1 100644
--- a/src/bin/dhcp6/tests/dhcp6_client.h
+++ b/src/bin/dhcp6/tests/dhcp6_client.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -753,6 +753,14 @@ public:
/// @brief Configures the client to not send any extra options.
void clearExtraOptions();
+ /// @brief Add a client class.
+ ///
+ /// @param client_class name of the class to be added.
+ void addClass(const ClientClass& client_class);
+
+ /// @brief Configures the client to not add client classes.
+ void clearClasses();
+
/// @brief Debugging method the prints currently held configuration
///
/// @todo: This is mostly useful when debugging tests. This method
@@ -927,6 +935,9 @@ private:
/// @brief Interface id.
OptionPtr interface_id_;
+
+ /// @brief Extra classes to add to the query.
+ ClientClasses classes_;
};
} // end of namespace isc::dhcp::test
diff --git a/src/bin/dhcp6/tests/get_config_unittest.cc b/src/bin/dhcp6/tests/get_config_unittest.cc
index 55e06b18de..ee15fc3483 100644
--- a/src/bin/dhcp6/tests/get_config_unittest.cc
+++ b/src/bin/dhcp6/tests/get_config_unittest.cc
@@ -5631,11 +5631,11 @@ const char* UNPARSED_CONFIGS[] = {
" \"option-data\": [ ]\n"
" },\n"
" {\n"
-" \"name\": \"three\",\n"
+" \"name\": \"two\",\n"
" \"option-data\": [ ]\n"
" },\n"
" {\n"
-" \"name\": \"two\",\n"
+" \"name\": \"three\",\n"
" \"option-data\": [ ]\n"
" }\n"
" ],\n"
diff --git a/src/bin/dhcp6/tests/parser_unittest.cc b/src/bin/dhcp6/tests/parser_unittest.cc
index 9a88454206..a83e4a1b1a 100644
--- a/src/bin/dhcp6/tests/parser_unittest.cc
+++ b/src/bin/dhcp6/tests/parser_unittest.cc
@@ -271,6 +271,7 @@ TEST(ParserTest, file) {
configs.push_back("backends.json");
configs.push_back("cassandra.json");
configs.push_back("classify.json");
+ configs.push_back("classify2.json");
configs.push_back("comments.json");
configs.push_back("dhcpv4-over-dhcpv6.json");
configs.push_back("duid.json");
diff --git a/src/bin/dhcp6/tests/shared_network_unittest.cc b/src/bin/dhcp6/tests/shared_network_unittest.cc
index 32fa5d53ec..b3081aa9a5 100644
--- a/src/bin/dhcp6/tests/shared_network_unittest.cc
+++ b/src/bin/dhcp6/tests/shared_network_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -1454,6 +1455,41 @@ public:
}
}
+ /// @brief Check precedence.
+ ///
+ /// @param config the configuration.
+ /// @param ns_address expected name server address.
+ void testPrecedence(const std::string& config, const std::string& ns_address) {
+ // Create client and set DUID to the one that has a reservation.
+ Dhcp6Client client;
+ client.setInterface("eth1");
+ client.setDUID("00:03:00:01:aa:bb:cc:dd:ee:ff");
+ client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+ // Request dns-servers.
+ client.requestOption(D6O_NAME_SERVERS);
+
+ // Create server configuration.
+ configure(config, *client.getServer());
+
+ // Perform SARR.
+ ASSERT_NO_THROW(client.doSARR());
+
+ // Check response.
+ EXPECT_EQ(1, client.getLeaseNum());
+ Pkt6Ptr resp = client.getContext().response_;
+ ASSERT_TRUE(resp);
+
+ // Check dns-servers option.
+ OptionPtr opt = resp->getOption(D6O_NAME_SERVERS);
+ ASSERT_TRUE(opt);
+ Option6AddrLstPtr servers =
+ boost::dynamic_pointer_cast(opt);
+ ASSERT_TRUE(servers);
+ auto addrs = servers->getAddresses();
+ ASSERT_EQ(1, addrs.size());
+ EXPECT_EQ(ns_address, addrs[0].toText());
+ }
+
/// @brief Destructor.
virtual ~Dhcpv6SharedNetworkTest() {
StatsMgr::instance().removeAll();
@@ -2462,4 +2498,420 @@ TEST_F(Dhcpv6SharedNetworkTest, poolInSubnetSelectedByClass) {
EXPECT_EQ(1, client2.getLeasesWithNonZeroLifetime().size());
}
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceGlobal) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::1");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceClass) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceClasses) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"beta\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " },"
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ // Class order is the insert order
+ testPrecedence(config, "2001:db8:1::2");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceNetworkClass) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::3");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceSubnet) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\""
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::4");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedencePool) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::5");
+}
+
+// Verify option processing precedence
+// Order is global < class < shared-network < subnet < pools < host reservation
+TEST_F(Dhcpv6SharedNetworkTest, precedenceReservation) {
+ const std::string config =
+ "{"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::1\""
+ " }"
+ " ],"
+ " \"client-classes\": ["
+ " {"
+ " \"name\": \"alpha\","
+ " \"test\": \"'' == ''\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::2\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"shared-networks\": ["
+ " {"
+ " \"name\": \"frog\","
+ " \"interface\": \"eth1\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::3\""
+ " }"
+ " ],"
+ " \"subnet6\": ["
+ " {"
+ " \"subnet\": \"2001:db8:1::/64\","
+ " \"id\": 10,"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::4\""
+ " }"
+ " ],"
+ " \"pools\": ["
+ " {"
+ " \"pool\": \"2001:db8:1::1 - 2001:db8:1::64\","
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::5\""
+ " }"
+ " ]"
+ " }"
+ " ],"
+ " \"reservations\": ["
+ " {"
+ " \"duid\": \"00:03:00:01:aa:bb:cc:dd:ee:ff\","
+ " \"ip-addresses\": [ \"2001:db8:1::28\" ],"
+ " \"option-data\": ["
+ " {"
+ " \"name\": \"dns-servers\","
+ " \"data\": \"2001:db8:1::6\""
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}";
+
+ testPrecedence(config, "2001:db8:1::6");
+}
+
} // end of anonymous namespace
diff --git a/src/lib/dhcp/classify.cc b/src/lib/dhcp/classify.cc
index 95fc5dd08b..e234598e94 100644
--- a/src/lib/dhcp/classify.cc
+++ b/src/lib/dhcp/classify.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -17,7 +17,7 @@ namespace isc {
namespace dhcp {
ClientClasses::ClientClasses(const std::string& class_names)
- : std::set() {
+ : list_(), set_() {
std::vector split_text;
boost::split(split_text, class_names, boost::is_any_of(","),
boost::algorithm::token_compress_off);
@@ -33,8 +33,8 @@ ClientClasses::ClientClasses(const std::string& class_names)
std::string
ClientClasses::toText(const std::string& separator) const {
std::stringstream s;
- for (const_iterator class_it = begin(); class_it != end(); ++class_it) {
- if (class_it != begin()) {
+ for (const_iterator class_it = cbegin(); class_it != cend(); ++class_it) {
+ if (class_it != cbegin()) {
s << separator;
}
s << *class_it;
@@ -44,4 +44,3 @@ ClientClasses::toText(const std::string& separator) const {
} // end of namespace isc::dhcp
} // end of namespace isc
-
diff --git a/src/lib/dhcp/classify.h b/src/lib/dhcp/classify.h
index 174930c348..14614313fc 100644
--- a/src/lib/dhcp/classify.h
+++ b/src/lib/dhcp/classify.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,8 +7,10 @@
#ifndef CLASSIFY_H
#define CLASSIFY_H
-#include
#include
+#include
+#include
+#include
/// @file classify.h
///
@@ -36,34 +38,67 @@ namespace dhcp {
/// @brief Container for storing client class names
///
- /// Depending on how you look at it, this is either a little more than just
- /// a set of strings or a client classifier that performs access control.
- /// For now, it is a simple access list that may contain zero or more
- /// class names. It is expected to grow in complexity once support for
- /// client classes becomes more feature rich.
- ///
- /// Note: This class is derived from std::set which may not have Doxygen
- /// documentation. See http://www.cplusplus.com/reference/set/set/.
- class ClientClasses : public std::set {
+ /// Both a list to iterate on it in insert order and unordered
+ /// set of names for existence.
+ class ClientClasses {
public:
+ /// @brief Type of iterators
+ typedef std::list::const_iterator const_iterator;
+
/// @brief Default constructor.
- ClientClasses() : std::set() {
+ ClientClasses() : list_(), set_() {
}
/// @brief Constructor from comma separated values.
///
/// @param class_names A string containing a client classes separated
/// with commas. The class names are trimmed before insertion to the set.
- ClientClasses(const std::string& class_names);
+ ClientClasses(const ClientClass& class_names);
+
+ /// @brief Insert an element.
+ ///
+ /// @param class_name The name of the class to insert
+ void insert(const ClientClass& class_name) {
+ list_.push_back(class_name);
+ set_.insert(class_name);
+ }
+
+ /// @brief Check if classes is empty.
+ bool empty() const {
+ return (list_.empty());
+ }
+
+ /// @brief Returns the number of classes.
+ ///
+ /// @note; in C++ 11 list size complexity is constant so
+ /// there is no advantage to use the set part.
+ size_t size() const {
+ return (list_.size());
+ }
+
+ /// @brief Iterator to the first element.
+ const_iterator cbegin() const {
+ return (list_.cbegin());
+ }
+
+ /// @brief Iterator to the past the end element.
+ const_iterator cend() const {
+ return (list_.cend());
+ }
/// @brief returns if class x belongs to the defined classes
///
/// @param x client class to be checked
/// @return true if x belongs to the classes
- bool
- contains(const ClientClass& x) const {
- return (find(x) != end());
+ bool contains(const ClientClass& x) const {
+ return (set_.count(x) != 0);
+ }
+
+ /// @brief Clears containers.
+ void clear() {
+ list_.clear();
+ set_.clear();
}
/// @brief Returns all class names as text
@@ -72,6 +107,13 @@ namespace dhcp {
/// default separator comprises comma sign followed by space
/// character.
std::string toText(const std::string& separator = ", ") const;
+
+ private:
+ /// @brief List/ordered part
+ std::list list_;
+
+ /// @brief Set/unordered part
+ std::unordered_set set_;
};
};
diff --git a/src/lib/dhcp/pkt.cc b/src/lib/dhcp/pkt.cc
index bfee10e0e4..cc7c2739cc 100644
--- a/src/lib/dhcp/pkt.cc
+++ b/src/lib/dhcp/pkt.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -93,13 +93,14 @@ Pkt::delOption(uint16_t type) {
bool
Pkt::inClass(const std::string& client_class) {
- return (classes_.find(client_class) != classes_.end());
+ return (classes_.contains(client_class));
}
void
-Pkt::addClass(const std::string& client_class) {
- if (classes_.find(client_class) == classes_.end()) {
- classes_.insert(client_class);
+Pkt::addClass(const std::string& client_class, bool required) {
+ ClientClasses& classes = !required ? classes_ : required_classes_;
+ if (!classes.contains(client_class)) {
+ classes.insert(client_class);
}
}
diff --git a/src/lib/dhcp/pkt.h b/src/lib/dhcp/pkt.h
index 078584b1d1..f2efc14de8 100644
--- a/src/lib/dhcp/pkt.h
+++ b/src/lib/dhcp/pkt.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -285,13 +285,20 @@ public:
/// so I decided to stick with addClass().
///
/// @param client_class name of the class to be added
- void addClass(const isc::dhcp::ClientClass& client_class);
+ /// @param required the class is marked for required evaluation
+ void addClass(const isc::dhcp::ClientClass& client_class,
+ bool required = false);
/// @brief Returns the class set
///
/// @note This should be used only to iterate over the class set.
- /// @return
- const ClientClasses& getClasses() const { return (classes_); }
+ /// @param required return classes or required to be evaluated classes.
+ /// @return if required is false (the default) the classes the
+ /// packet belongs to else the classes which are required to be
+ /// evaluated.
+ const ClientClasses& getClasses(bool required = false) const {
+ return (!required ? classes_ : required_classes_);
+ }
/// @brief Unparsed data (in received packets).
///
@@ -579,6 +586,14 @@ public:
/// @ref addClass should be used to operate on this field.
ClientClasses classes_;
+ /// @brief Classes which are required to be evaluated.
+ ///
+ /// The comment on @ref classes_ applies here.
+ ///
+ /// Before output option processing these classes will be evaluated
+ /// and if evaluation status is true added to the previous collection.
+ ClientClasses required_classes_;
+
/// @brief Collection of options present in this message.
///
/// @warning This public member is accessed by derived
diff --git a/src/lib/dhcp/tests/classify_unittest.cc b/src/lib/dhcp/tests/classify_unittest.cc
index 215551eb06..14e9d58443 100644
--- a/src/lib/dhcp/tests/classify_unittest.cc
+++ b/src/lib/dhcp/tests/classify_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -86,8 +86,8 @@ TEST(ClassifyTest, ClientClassesIterator) {
bool seenbeta = false;
bool seengamma = false;
bool seendelta = false;
- for (ClientClasses::const_iterator it = classes.begin();
- it != classes.end(); ++it) {
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
++count;
if (*it == "alpha") {
seenalpha = true;
@@ -124,10 +124,10 @@ TEST(ClassifyTest, ClientClassesToText) {
classes.insert("gamma");
EXPECT_EQ("alpha, gamma", classes.toText());
- // Insert third class and make sure they get ordered alphabetically.
+ // Insert third class and make sure they get ordered in insert order.
classes.insert("beta");
- EXPECT_EQ("alpha, beta, gamma", classes.toText());
+ EXPECT_EQ("alpha, gamma, beta", classes.toText());
// Check non-standard separator.
- EXPECT_EQ("alpha.beta.gamma", classes.toText("."));
+ EXPECT_EQ("alpha.gamma.beta", classes.toText("."));
}
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 970f4ef367..930f76547a 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -919,13 +919,13 @@ TEST_F(Pkt4Test, clientClasses) {
// Default values (do not belong to any class)
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- EXPECT_TRUE(pkt.classes_.empty());
+ EXPECT_TRUE(pkt.getClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER);
EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- ASSERT_FALSE(pkt.classes_.empty());
+ ASSERT_FALSE(pkt.getClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM);
@@ -941,6 +941,34 @@ TEST_F(Pkt4Test, clientClasses) {
EXPECT_TRUE(pkt.inClass("foo"));
}
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt4Test, deferredClientClasses) {
+ Pkt4 pkt(DHCPOFFER, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt4Test, getMAC) {
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index 28339c9a8c..042ffe8916 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -1089,13 +1089,13 @@ TEST_F(Pkt6Test, clientClasses) {
// Default values (do not belong to any class)
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- EXPECT_TRUE(pkt.classes_.empty());
+ EXPECT_TRUE(pkt.getClasses().empty());
// Add to the first class
pkt.addClass(DOCSIS3_CLASS_EROUTER);
EXPECT_TRUE(pkt.inClass(DOCSIS3_CLASS_EROUTER));
EXPECT_FALSE(pkt.inClass(DOCSIS3_CLASS_MODEM));
- ASSERT_FALSE(pkt.classes_.empty());
+ ASSERT_FALSE(pkt.getClasses().empty());
// Add to a second class
pkt.addClass(DOCSIS3_CLASS_MODEM);
@@ -1111,6 +1111,34 @@ TEST_F(Pkt6Test, clientClasses) {
EXPECT_TRUE(pkt.inClass("foo"));
}
+// Tests whether a packet can be marked to evaluate later a class and
+// after check if a given class is in the collection
+TEST_F(Pkt6Test, deferredClientClasses) {
+ Pkt6 pkt(DHCPV6_ADVERTISE, 1234);
+
+ // Default values (do not belong to any class)
+ EXPECT_TRUE(pkt.getClasses(true).empty());
+
+ // Add to the first class
+ pkt.addClass(DOCSIS3_CLASS_EROUTER, true);
+ EXPECT_EQ(1, pkt.getClasses(true).size());
+
+ // Add to a second class
+ pkt.addClass(DOCSIS3_CLASS_MODEM, true);
+ EXPECT_EQ(2, pkt.getClasses(true).size());
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_EROUTER));
+ EXPECT_TRUE(pkt.getClasses(true).contains(DOCSIS3_CLASS_MODEM));
+ EXPECT_FALSE(pkt.getClasses(true).contains("foo"));
+
+ // Check that it's ok to add to the same class repeatedly
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+ EXPECT_NO_THROW(pkt.addClass("foo", true));
+
+ // Check that the packet belongs to 'foo'
+ EXPECT_TRUE(pkt.getClasses(true).contains("foo"));
+}
+
// Tests whether MAC can be obtained and that MAC sources are not
// confused.
TEST_F(Pkt6Test, getMAC) {
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index f57669139e..58a778aab9 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -780,7 +780,8 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
// check if the hint is in pool and is available
// This is equivalent of subnet->inPool(hint), but returns the pool
pool = boost::dynamic_pointer_cast
- (subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(), hint));
+ (subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(),
+ hint));
// check if the pool is allowed
if (pool && !pool->clientSupported(ctx.query_->getClasses())) {
@@ -916,7 +917,6 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
continue;
}
uint64_t max_attempts = (attempts_ > 0 ? attempts_ : possible_attempts);
-
Network::HRMode hr_mode = subnet->getHostReservationMode();
for (uint64_t i = 0; i < max_attempts; ++i) {
@@ -943,7 +943,9 @@ AllocEngine::allocateUnreservedLeases6(ClientContext6& ctx) {
uint8_t prefix_len = 128;
if (ctx.currentIA().type_ == Lease::TYPE_PD) {
pool = boost::dynamic_pointer_cast(
- subnet->getPool(ctx.currentIA().type_, ctx.query_->getClasses(), candidate));
+ subnet->getPool(ctx.currentIA().type_,
+ ctx.query_->getClasses(),
+ candidate));
if (pool) {
prefix_len = pool->getLength();
}
diff --git a/src/lib/dhcpsrv/client_class_def.cc b/src/lib/dhcpsrv/client_class_def.cc
index e60a42626e..ca91116c7f 100644
--- a/src/lib/dhcpsrv/client_class_def.cc
+++ b/src/lib/dhcpsrv/client_class_def.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -20,7 +20,8 @@ namespace dhcp {
ClientClassDef::ClientClassDef(const std::string& name,
const ExpressionPtr& match_expr,
const CfgOptionPtr& cfg_option)
- : name_(name), match_expr_(match_expr), cfg_option_(cfg_option),
+ : name_(name), match_expr_(match_expr), required_(false),
+ cfg_option_(cfg_option),
next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()) {
// Name can't be blank
@@ -39,7 +40,7 @@ ClientClassDef::ClientClassDef(const std::string& name,
ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
: name_(rhs.name_), match_expr_(ExpressionPtr()),
- cfg_option_(new CfgOption()),
+ required_(false), cfg_option_(new CfgOption()),
next_server_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()) {
if (rhs.match_expr_) {
@@ -55,6 +56,7 @@ ClientClassDef::ClientClassDef(const ClientClassDef& rhs)
rhs.cfg_option_->copyTo(*cfg_option_);
}
+ required_ = rhs.required_;
next_server_ = rhs.next_server_;
sname_ = rhs.sname_;
filename_ = rhs.filename_;
@@ -93,6 +95,16 @@ ClientClassDef::setTest(const std::string& test) {
test_ = test;
}
+bool
+ClientClassDef::getRequired() const {
+ return (required_);
+}
+
+void
+ClientClassDef::setRequired(bool required) {
+ required_ = required;
+}
+
const CfgOptionDefPtr&
ClientClassDef::getCfgOptionDef() const {
return (cfg_option_def_);
@@ -125,6 +137,7 @@ ClientClassDef::equals(const ClientClassDef& other) const {
((!cfg_option_def_ && !other.cfg_option_def_) ||
(cfg_option_def_ && other.cfg_option_def_ &&
(*cfg_option_def_ == *other.cfg_option_def_))) &&
+ (required_ == other.required_) &&
(next_server_ == other.next_server_) &&
(sname_ == other.sname_) &&
(filename_ == other.filename_));
@@ -142,6 +155,10 @@ ClientClassDef:: toElement() const {
if (!test_.empty()) {
result->set("test", Element::create(test_));
}
+ // Set only-if-required
+ if (required_) {
+ result->set("only-if-required", Element::create(required_));
+ }
// Set option-def (used only by DHCPv4)
if (cfg_option_def_ && (family == AF_INET)) {
result->set("option-def", cfg_option_def_->toElement());
@@ -169,13 +186,13 @@ std::ostream& operator<<(std::ostream& os, const ClientClassDef& x) {
//********** ClientClassDictionary ******************//
ClientClassDictionary::ClientClassDictionary()
- : classes_(new ClientClassDefMap()) {
+ : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
}
ClientClassDictionary::ClientClassDictionary(const ClientClassDictionary& rhs)
- : classes_(new ClientClassDefMap()) {
- BOOST_FOREACH(ClientClassMapPair cclass, *(rhs.classes_)) {
- ClientClassDefPtr copy(new ClientClassDef(*(cclass.second)));
+ : map_(new ClientClassDefMap()), list_(new ClientClassDefList()) {
+ BOOST_FOREACH(ClientClassDefPtr cclass, *(rhs.list_)) {
+ ClientClassDefPtr copy(new ClientClassDef(*cclass));
addClass(copy);
}
}
@@ -187,6 +204,7 @@ void
ClientClassDictionary::addClass(const std::string& name,
const ExpressionPtr& match_expr,
const std::string& test,
+ bool required,
const CfgOptionPtr& cfg_option,
CfgOptionDefPtr cfg_option_def,
ConstElementPtr user_context,
@@ -195,6 +213,7 @@ ClientClassDictionary::addClass(const std::string& name,
const std::string& filename) {
ClientClassDefPtr cclass(new ClientClassDef(name, match_expr, cfg_option));
cclass->setTest(test);
+ cclass->setRequired(required);
cclass->setCfgOptionDef(cfg_option_def);
cclass->setContext(user_context),
cclass->setNextServer(next_server);
@@ -215,13 +234,14 @@ ClientClassDictionary::addClass(ClientClassDefPtr& class_def) {
<< class_def->getName() << " has already been defined");
}
- (*classes_)[class_def->getName()] = class_def;
+ list_->push_back(class_def);
+ (*map_)[class_def->getName()] = class_def;
}
ClientClassDefPtr
ClientClassDictionary::findClass(const std::string& name) const {
- ClientClassDefMap::iterator it = classes_->find(name);
- if (it != classes_->end()) {
+ ClientClassDefMap::iterator it = map_->find(name);
+ if (it != map_->end()) {
return (*it).second;
}
@@ -230,26 +250,33 @@ ClientClassDictionary::findClass(const std::string& name) const {
void
ClientClassDictionary::removeClass(const std::string& name) {
- classes_->erase(name);
+ for (ClientClassDefList::const_iterator this_class = list_->cbegin();
+ this_class != list_->cend(); ++this_class) {
+ if ((*this_class)->getName() == name) {
+ list_->erase(this_class);
+ break;
+ }
+ }
+ map_->erase(name);
}
-const ClientClassDefMapPtr&
+const ClientClassDefListPtr&
ClientClassDictionary::getClasses() const {
- return (classes_);
+ return (list_);
}
bool
ClientClassDictionary::equals(const ClientClassDictionary& other) const {
- if (classes_->size() != other.classes_->size()) {
+ if (list_->size() != other.list_->size()) {
return (false);
}
- ClientClassDefMap::iterator this_class = classes_->begin();
- ClientClassDefMap::iterator other_class = other.classes_->begin();
- while (this_class != classes_->end() &&
- other_class != other.classes_->end()) {
- if (!(*this_class).second || !(*other_class).second ||
- (*(*this_class).second) != (*(*other_class).second)) {
+ ClientClassDefList::const_iterator this_class = list_->cbegin();
+ ClientClassDefList::const_iterator other_class = other.list_->cbegin();
+ while (this_class != list_->cend() &&
+ other_class != other.list_->cend()) {
+ if (!*this_class || !*other_class ||
+ **this_class != **other_class) {
return false;
}
@@ -264,12 +291,63 @@ ElementPtr
ClientClassDictionary::toElement() const {
ElementPtr result = Element::createList();
// Iterate on the map
- for (ClientClassDefMap::iterator this_class = classes_->begin();
- this_class != classes_->end(); ++this_class) {
- result->add(this_class->second->toElement());
+ for (ClientClassDefList::const_iterator this_class = list_->begin();
+ this_class != list_->cend(); ++this_class) {
+ result->add((*this_class)->toElement());
}
return (result);
}
+std::list
+builtinNames = {
+ "ALL", "KNOWN"
+};
+
+std::list
+builtinPrefixes = {
+ "VENDOR_CLASS_", "AFTER_", "EXTERNAL_"
+};
+
+bool
+isClientClassBuiltIn(const ClientClass& client_class) {
+ for (std::list::const_iterator bn = builtinNames.cbegin();
+ bn != builtinNames.cend(); ++bn) {
+ if (client_class == *bn) {
+ return true;
+ }
+ }
+
+ for (std::list::const_iterator bt = builtinPrefixes.cbegin();
+ bt != builtinPrefixes.cend(); ++bt) {
+ if (client_class.size() <= bt->size()) {
+ continue;
+ }
+ auto mis = std::mismatch(bt->cbegin(), bt->cend(), client_class.cbegin());
+ if (mis.first == bt->cend()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+isClientClassDefined(ClientClassDictionaryPtr& class_dictionary,
+ const ClientClass& client_class) {
+ // First check built-in classes
+ if (isClientClassBuiltIn(client_class)) {
+ return (true);
+ }
+
+ // Second check already defined, i.e. in the dictionary
+ ClientClassDefPtr def = class_dictionary->findClass(client_class);
+ if (def) {
+ return (true);
+ }
+
+ // Not defined...
+ return (false);
+}
+
} // namespace isc::dhcp
} // namespace isc
diff --git a/src/lib/dhcpsrv/client_class_def.h b/src/lib/dhcpsrv/client_class_def.h
index 81e4a179aa..623710eb83 100644
--- a/src/lib/dhcpsrv/client_class_def.h
+++ b/src/lib/dhcpsrv/client_class_def.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,6 +15,8 @@
#include
#include
+#include
+#include
/// @file client_class_def.h
///
@@ -81,6 +83,12 @@ public:
/// @param test the original expression to assign the class
void setTest(const std::string& test);
+ /// @brief Fetches the only if required flag
+ bool getRequired() const;
+
+ /// @brief Sets the only if required flag
+ void setRequired(bool required);
+
/// @brief Fetches the class's option definitions
const CfgOptionDefPtr& getCfgOptionDef() const;
@@ -181,6 +189,12 @@ private:
/// this class.
std::string test_;
+ /// @brief The only-if-required flag: when false (the default) membership
+ /// is determined during classification so is available, of instance,
+ /// for subnet selection. When true, membership is evaluated
+ /// only when required and is usable only for option configuration.
+ bool required_;
+
/// @brief The option definition configuration for this class
CfgOptionDefPtr cfg_option_def_;
@@ -210,13 +224,16 @@ private:
typedef boost::shared_ptr ClientClassDefPtr;
/// @brief Defines a map of ClientClassDef's, keyed by the class name.
-typedef std::map ClientClassDefMap;
+typedef std::unordered_map ClientClassDefMap;
/// @brief Defines a pointer to a ClientClassDefMap
typedef boost::shared_ptr ClientClassDefMapPtr;
-/// @brief Defines a pair for working with ClientClassMap
-typedef std::pair ClientClassMapPair;
+/// @brief Defines a list of ClientClassDefPtr's, using insert order.
+typedef std::list ClientClassDefList;
+
+/// @brief Defines a pointer to a ClientClassDefList
+typedef boost::shared_ptr ClientClassDefListPtr;
/// @brief Maintains a list of ClientClassDef's
class ClientClassDictionary : public isc::data::CfgToElement {
@@ -235,6 +252,7 @@ public:
/// @param name Name to assign to this class
/// @param match_expr Expression the class will use to determine membership
/// @param test Original version of match_expr
+ /// @param required Original value of the only if required flag
/// @param options Collection of options members should be given
/// @param defs Option definitions (optional)
/// @param user_context User context (optional)
@@ -246,7 +264,8 @@ public:
/// dictionary. See @ref dhcp::ClientClassDef::ClientClassDef() for
/// others.
void addClass(const std::string& name, const ExpressionPtr& match_expr,
- const std::string& test, const CfgOptionPtr& options,
+ const std::string& test, bool required,
+ const CfgOptionPtr& options,
CfgOptionDefPtr defs = CfgOptionDefPtr(),
isc::data::ConstElementPtr user_context = isc::data::ConstElementPtr(),
asiolink::IOAddress next_server = asiolink::IOAddress("0.0.0.0"),
@@ -277,10 +296,10 @@ public:
/// @param name the name of the class to remove
void removeClass(const std::string& name);
- /// @brief Fetches the dictionary's map of classes
+ /// @brief Fetches the dictionary's list of classes
///
- /// @return ClientClassDefMapPtr to the map of classes
- const ClientClassDefMapPtr& getClasses() const;
+ /// @return ClientClassDefListPtr to the list of classes
+ const ClientClassDefListPtr& getClasses() const;
/// @brief Compares two @c ClientClassDictionary objects for equality.
///
@@ -315,13 +334,39 @@ public:
private:
/// @brief Map of the class definitions
- ClientClassDefMapPtr classes_;
+ ClientClassDefMapPtr map_;
+ /// @brief List of the class definitions
+ ClientClassDefListPtr list_;
};
/// @brief Defines a pointer to a ClientClassDictionary
typedef boost::shared_ptr ClientClassDictionaryPtr;
+/// @brief List of built-in client class names.
+/// i.e. ALL and KNOWN.
+extern std::list builtinNames;
+
+/// @brief List of built-in client class prefixes
+/// i.e. VENDOR_CLASS_, AFTER_ and EXTERNAL_.
+extern std::list builtinPrefixes;
+
+/// @brief Check if a client class name is builtin.
+///
+/// @param client_class A client class name to look for.
+/// @return true if built-in, false if not.
+bool isClientClassBuiltIn(const ClientClass& client_class);
+
+
+/// @brief Check if a client class name is already defined,
+/// i.e. is built-in or in the dictionary,
+///
+/// @param class_dictionary A class dictionary where to look for.
+/// @param client_class A client class name to look for.
+/// @return true if defined or built-in, false if not.
+bool isClientClassDefined(ClientClassDictionaryPtr& class_dictionary,
+ const ClientClass& client_class);
+
} // namespace isc::dhcp
} // namespace isc
diff --git a/src/lib/dhcpsrv/host.cc b/src/lib/dhcpsrv/host.cc
index 134c0acc58..38b29c13f0 100644
--- a/src/lib/dhcpsrv/host.cc
+++ b/src/lib/dhcpsrv/host.cc
@@ -457,7 +457,7 @@ Host::toElement4() const {
const ClientClasses& cclasses = getClientClasses4();
ElementPtr classes = Element::createList();
for (ClientClasses::const_iterator cclass = cclasses.cbegin();
- cclass != cclasses.end(); ++cclass) {
+ cclass != cclasses.cend(); ++cclass) {
classes->add(Element::create(*cclass));
}
map->set("client-classes", classes);
@@ -516,7 +516,7 @@ Host::toElement6() const {
const ClientClasses& cclasses = getClientClasses6();
ElementPtr classes = Element::createList();
for (ClientClasses::const_iterator cclass = cclasses.cbegin();
- cclass != cclasses.end(); ++cclass) {
+ cclass != cclasses.cend(); ++cclass) {
classes->add(Element::create(*cclass));
}
map->set("client-classes", classes);
@@ -576,18 +576,18 @@ Host::toText() const {
}
// Add DHCPv4 client classes.
- for (ClientClasses::const_iterator cclass = dhcp4_client_classes_.begin();
- cclass != dhcp4_client_classes_.end(); ++cclass) {
+ for (ClientClasses::const_iterator cclass = dhcp4_client_classes_.cbegin();
+ cclass != dhcp4_client_classes_.cend(); ++cclass) {
s << " dhcp4_class"
- << std::distance(dhcp4_client_classes_.begin(), cclass)
+ << std::distance(dhcp4_client_classes_.cbegin(), cclass)
<< "=" << *cclass;
}
// Add DHCPv6 client classes.
- for (ClientClasses::const_iterator cclass = dhcp6_client_classes_.begin();
- cclass != dhcp6_client_classes_.end(); ++cclass) {
+ for (ClientClasses::const_iterator cclass = dhcp6_client_classes_.cbegin();
+ cclass != dhcp6_client_classes_.cend(); ++cclass) {
s << " dhcp6_class"
- << std::distance(dhcp6_client_classes_.begin(), cclass)
+ << std::distance(dhcp6_client_classes_.cbegin(), cclass)
<< "=" << *cclass;
}
diff --git a/src/lib/dhcpsrv/network.cc b/src/lib/dhcpsrv/network.cc
index c6c0ec7c15..1071ec3096 100644
--- a/src/lib/dhcpsrv/network.cc
+++ b/src/lib/dhcpsrv/network.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -22,25 +22,30 @@ Network::RelayInfo::RelayInfo(const isc::asiolink::IOAddress& addr)
bool
Network::clientSupported(const isc::dhcp::ClientClasses& classes) const {
- if (white_list_.empty()) {
+ if (client_class_.empty()) {
// There is no class defined for this network, so we do
// support everyone.
return (true);
}
- for (ClientClasses::const_iterator it = white_list_.begin();
- it != white_list_.end(); ++it) {
- if (classes.contains(*it)) {
- return (true);
- }
- }
-
- return (false);
+ return (classes.contains(client_class_));
}
void
Network::allowClientClass(const isc::dhcp::ClientClass& class_name) {
- white_list_.insert(class_name);
+ client_class_ = class_name;
+}
+
+void
+Network::requireClientClass(const isc::dhcp::ClientClass& class_name) {
+ if (!required_classes_.contains(class_name)) {
+ required_classes_.insert(class_name);
+ }
+}
+
+const ClientClasses&
+Network::getRequiredClasses() const {
+ return (required_classes_);
}
ElementPtr
@@ -63,12 +68,20 @@ Network::toElement() const {
map->set("relay", relay);
// Set client-class
- const ClientClasses& cclasses = getClientClasses();
- if (cclasses.size() > 1) {
- isc_throw(ToElementError, "client-class has too many items: "
- << cclasses.size());
- } else if (!cclasses.empty()) {
- map->set("client-class", Element::create(*cclasses.cbegin()));
+ const ClientClass& cclass = getClientClass();
+ if (!cclass.empty()) {
+ map->set("client-class", Element::create(cclass));
+ }
+
+ // Set require-client-classes
+ const ClientClasses& classes = getRequiredClasses();
+ if (!classes.empty()) {
+ ElementPtr class_list = Element::createList();
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ class_list->add(Element::create(*it));
+ }
+ map->set("require-client-classes", class_list);
}
// Set renew-timer
diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h
index 11cd5264bb..815826a0e7 100644
--- a/src/lib/dhcpsrv/network.h
+++ b/src/lib/dhcpsrv/network.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -89,7 +89,7 @@ public:
/// @brief Constructor.
Network()
: iface_name_(), relay_(asiolink::IOAddress::IPV4_ZERO_ADDRESS()),
- white_list_(), t1_(0), t2_(0), valid_(0),
+ client_class_(""), t1_(0), t2_(0), valid_(0),
host_reservation_mode_(HR_ALL), cfg_option_(new CfgOption()) {
}
@@ -160,31 +160,35 @@ public:
/// it is supported. On the other hand, client belonging to classes
/// "foobar" and "zyxxy" is not supported.
///
- /// @todo: Currently the logic is simple: client is supported if it belongs
- /// to any class mentioned in white_list_. We will eventually need a
- /// way to specify more fancy logic (e.g. to meet all classes, not just
- /// any)
+ /// @note: changed the planned white and black lists idea to a simple
+ /// client class name.
///
/// @param client_classes list of all classes the client belongs to
/// @return true if client can be supported, false otherwise
virtual bool
clientSupported(const isc::dhcp::ClientClasses& client_classes) const;
- /// @brief Adds class class_name to the list of supported classes
+ /// @brief Sets the supported class to class class_name
///
- /// Also see explanation note in @ref white_list_.
- ///
- /// @param class_name client class to be supported by this subnet
+ /// @param class_name client class to be supported by this network
void allowClientClass(const isc::dhcp::ClientClass& class_name);
- /// @brief returns the client class white list
+ /// @brief Adds class class_name to classes required to be evaluated.
+ ///
+ /// @param class_name client class required to be evaluated
+ void requireClientClass(const isc::dhcp::ClientClass& class_name);
+
+ /// @brief Returns classes which are required to be evaluated
+ const isc::dhcp::ClientClasses& getRequiredClasses() const;
+
+ /// @brief returns the client class
///
/// @note The returned reference is only valid as long as the object
/// returned it is valid.
///
- /// @return client classes @ref white_list_
- const isc::dhcp::ClientClasses& getClientClasses() const {
- return (white_list_);
+ /// @return client class @ref client_class_
+ const isc::dhcp::ClientClass& getClientClass() const {
+ return (client_class_);
}
/// @brief Return valid-lifetime for addresses in that prefix
@@ -274,14 +278,15 @@ protected:
/// @brief Optional definition of a client class
///
/// If defined, only clients belonging to that class will be allowed to use
- /// this particular network. The default value for this is an empty list,
+ /// this particular network. The default value for this is an empty string,
/// which means that any client is allowed, regardless of its class.
+ ClientClass client_class_;
+
+ /// @brief Required classes
///
- /// @todo This is just a single list of allowed classes. We'll also need
- /// to add a black-list (only classes on the list are rejected, the rest
- /// are allowed). Implementing this will require more fancy parser logic,
- /// so it may be a while until we support this.
- ClientClasses white_list_;
+ /// If the network is selected these classes will be added to the
+ /// incoming packet and their evaluation will be required.
+ ClientClasses required_classes_;
/// @brief a Triplet (min/default/max) holding allowed renew timer values
Triplet t1_;
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
index bd1ac03510..f44efbec2a 100644
--- a/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,6 +18,7 @@
#include
#include
+#include
using namespace isc::data;
using namespace isc::asiolink;
@@ -35,7 +36,8 @@ namespace dhcp {
void
ExpressionParser::parse(ExpressionPtr& expression,
ConstElementPtr expression_cfg,
- uint16_t family) {
+ uint16_t family,
+ EvalContext::CheckDefined check_defined) {
if (expression_cfg->getType() != Element::string) {
isc_throw(DhcpConfigError, "expression ["
<< expression_cfg->str() << "] must be a string, at ("
@@ -47,7 +49,8 @@ ExpressionParser::parse(ExpressionPtr& expression,
std::string value;
expression_cfg->getValue(value);
try {
- EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6);
+ EvalContext eval_ctx(family == AF_INET ? Option::V4 : Option::V6,
+ check_defined);
eval_ctx.parseString(value);
expression.reset(new Expression());
*expression = eval_ctx.expression;
@@ -80,7 +83,10 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
std::string test;
if (test_cfg) {
ExpressionParser parser;
- parser.parse(match_expr, test_cfg, family);
+ using std::placeholders::_1;
+ auto check_defined =
+ std::bind(isClientClassDefined, class_dictionary, _1);
+ parser.parse(match_expr, test_cfg, family, check_defined);
test = test_cfg->stringValue();
}
@@ -130,6 +136,12 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
// Parse user context
ConstElementPtr user_context = class_def_cfg->get("user-context");
+ // Let's try to parse the only-if-required flag
+ bool required = false;
+ if (class_def_cfg->contains("only-if-required")) {
+ required = getBoolean(class_def_cfg, "only-if-required");
+ }
+
// Let's try to parse the next-server field
IOAddress next_server("0.0.0.0");
if (class_def_cfg->contains("next-server")) {
@@ -185,8 +197,8 @@ ClientClassDefParser::parse(ClientClassDictionaryPtr& class_dictionary,
// Add the client class definition
try {
- class_dictionary->addClass(name, match_expr, test, options, defs,
- user_context, next_server, sname, filename);
+ class_dictionary->addClass(name, match_expr, test, required, options,
+ defs, next_server, sname, filename);
} catch (const std::exception& ex) {
isc_throw(DhcpConfigError, "Can't add class: " << ex.what()
<< " (" << class_def_cfg->getPosition() << ")");
diff --git a/src/lib/dhcpsrv/parsers/client_class_def_parser.h b/src/lib/dhcpsrv/parsers/client_class_def_parser.h
index 576ca4d63c..f490f08f6c 100644
--- a/src/lib/dhcpsrv/parsers/client_class_def_parser.h
+++ b/src/lib/dhcpsrv/parsers/client_class_def_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015, 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,10 @@
#include
#include
+#include
#include
+#include
+#include
/// @file client_class_def_parser.h
///
@@ -64,10 +67,14 @@ public:
/// @param expression variable in which to store the new expression
/// @param expression_cfg the configuration entry to be parsed.
/// @param family the address family of the expression.
+ /// @param check_defined a closure to check if a client class is defined.
///
/// @throw DhcpConfigError if parsing was unsuccessful.
void parse(ExpressionPtr& expression,
- isc::data::ConstElementPtr expression_cfg, uint16_t family);
+ isc::data::ConstElementPtr expression_cfg,
+ uint16_t family,
+ isc::eval::EvalContext::CheckDefined check_defined =
+ isc::eval::EvalContext::acceptAll);
};
/// @brief Parser for a single client class definition.
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
index eb80578712..8b3f2efdcd 100644
--- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -386,6 +386,21 @@ PoolParser::parse(PoolStoragePtr pools,
pool->allowClientClass(cclass);
}
}
+
+ // Try setting up required client classes.
+ ConstElementPtr class_list = pool_structure->get("require-client-classes");
+ if (class_list) {
+ const std::vector& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool->requireClientClass((*cclass)->stringValue());
+ }
+ }
}
//****************************** Pool4Parser *************************
@@ -695,6 +710,21 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params,
subnet4->allowClientClass(client_class);
}
+ // Try setting up required client classes.
+ ConstElementPtr class_list = params->get("require-client-classes");
+ if (class_list) {
+ const std::vector& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet4->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
// 4o6 specific parameter: 4o6-interface. If not explicitly specified,
// it will have the default value of "".
string iface4o6 = getString(params, "4o6-interface");
@@ -860,6 +890,8 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) {
client_class_ = client_class;
}
+ ConstElementPtr class_list = pd_pool_->get("require-client-classes");
+
// Check the pool parameters. It will throw an exception if any
// of the required parameters are invalid.
try {
@@ -883,7 +915,6 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) {
pool_->setContext(user_context_);
}
-
if (client_class_) {
string cclass = client_class_->stringValue();
if (!cclass.empty()) {
@@ -891,6 +922,19 @@ PdPoolParser::parse(PoolStoragePtr pools, ConstElementPtr pd_pool_) {
}
}
+ if (class_list) {
+ const std::vector& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ pool_->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
// Add the local pool to the external storage ptr.
pools->push_back(pool_);
}
@@ -1065,6 +1109,21 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params,
subnet6->allowClientClass(client_class);
}
+ // Try setting up required client classes.
+ ConstElementPtr class_list = params->get("require-client-classes");
+ if (class_list) {
+ const std::vector& classes = class_list->listValue();
+ for (auto cclass = classes.cbegin();
+ cclass != classes.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ subnet6->requireClientClass((*cclass)->stringValue());
+ }
+ }
+
/// client-class processing is now generic and handled in the common
/// code (see isc::data::SubnetConfigParser::createSubnet)
diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.h b/src/lib/dhcpsrv/parsers/dhcp_parsers.h
index 12dd20cb0b..abf1e57a02 100644
--- a/src/lib/dhcpsrv/parsers/dhcp_parsers.h
+++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.h
@@ -660,7 +660,6 @@ private:
///
/// If null, everyone is allowed.
isc::data::ConstElementPtr client_class_;
-
};
/// @brief Parser for a list of prefix delegation pools.
diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc
index ec6bdbbdfc..2b65776e64 100644
--- a/src/lib/dhcpsrv/parsers/shared_network_parser.cc
+++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -73,6 +73,19 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) {
ConstElementPtr user_context = shared_network_data->get("user-context");
if (user_context) {
shared_network->setContext(user_context);
+
+ if (shared_network_data->contains("require-client-classes")) {
+ const std::vector& class_list =
+ shared_network_data->get("require-client-classes")->listValue();
+ for (auto cclass = class_list.cbegin();
+ cclass != class_list.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ shared_network->requireClientClass((*cclass)->stringValue());
+ }
}
} catch (const DhcpConfigError&) {
@@ -119,6 +132,19 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) {
ConstElementPtr user_context = shared_network_data->get("user-context");
if (user_context) {
shared_network->setContext(user_context);
+
+ if (shared_network_data->contains("require-client-classes")) {
+ const std::vector& class_list =
+ shared_network_data->get("require-client-classes")->listValue();
+ for (auto cclass = class_list.cbegin();
+ cclass != class_list.cend(); ++cclass) {
+ if (((*cclass)->getType() != Element::string) ||
+ (*cclass)->stringValue().empty()) {
+ isc_throw(DhcpConfigError, "invalid class name ("
+ << (*cclass)->getPosition() << ")");
+ }
+ shared_network->requireClientClass((*cclass)->stringValue());
+ }
}
if (shared_network_data->contains("subnet6")) {
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
index 0e03a6f858..cba3b0f41f 100644
--- a/src/lib/dhcpsrv/pool.cc
+++ b/src/lib/dhcpsrv/pool.cc
@@ -20,7 +20,7 @@ namespace dhcp {
Pool::Pool(Lease::Type type, const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
:id_(getNextID()), first_(first), last_(last), type_(type),
- capacity_(0), cfg_option_(new CfgOption()), white_list_(),
+ capacity_(0), cfg_option_(new CfgOption()), client_class_(""),
last_allocated_(first), last_allocated_valid_(false) {
}
@@ -29,24 +29,11 @@ bool Pool::inRange(const isc::asiolink::IOAddress& addr) const {
}
bool Pool::clientSupported(const ClientClasses& classes) const {
- if (white_list_.empty()) {
- // There is no class defined for this pool, so we do
- // support everyone.
- return (true);
- }
-
- for (ClientClasses::const_iterator it = white_list_.begin();
- it != white_list_.end(); ++it) {
- if (classes.contains(*it)) {
- return (true);
- }
- }
-
- return (false);
+ return (client_class_.empty() || classes.contains(client_class_));
}
void Pool::allowClientClass(const ClientClass& class_name) {
- white_list_.insert(class_name);
+ client_class_ = class_name;
}
std::string
@@ -112,12 +99,20 @@ Pool::toElement() const {
map->set("option-data", opts->toElement());
// Set client-class
- const ClientClasses& cclasses = getClientClasses();
- if (cclasses.size() > 1) {
- isc_throw(ToElementError, "client-class has too many items: "
- << cclasses.size());
- } else if (!cclasses.empty()) {
- map->set("client-class", Element::create(*cclasses.cbegin()));
+ const ClientClass& cclass = getClientClass();
+ if (!cclass.empty()) {
+ map->set("client-class", Element::create(cclass));
+ }
+
+ // Set require-client-classes
+ const ClientClasses& classes = getRequiredClasses();
+ if (!classes.empty()) {
+ ElementPtr class_list =Element::createList();
+ for (ClientClasses::const_iterator it = classes.cbegin();
+ it != classes.cend(); ++it) {
+ class_list->add(Element::create(*it));
+ }
+ map->set("require-client-classes", class_list);
}
return (map);
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index bba123cfcf..53bd28d9bd 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -148,6 +148,68 @@ public:
last_allocated_valid_ = false;
}
+ /// @brief Checks whether this pool supports client that belongs to
+ /// specified classes.
+ ///
+ /// @param client_classes list of all classes the client belongs to
+ /// @return true if client can be supported, false otherwise
+ bool clientSupported(const ClientClasses& client_classes) const;
+
+ /// @brief Sets the supported class to class class_name
+ ///
+ /// @param class_name client class to be supported by this pool
+ void allowClientClass(const ClientClass& class_name);
+
+ /// @brief returns the client class
+ ///
+ /// @note The returned reference is only valid as long as the object
+ /// returned is valid.
+ ///
+ /// @return client class @ref client_class_
+ const ClientClass& getClientClass() const {
+ return (client_class_);
+ }
+
+ /// @brief Adds class class_name to classes required to be evaluated
+ ///
+ /// @param class_name client class required to be evaluated
+ void requireClientClass(const ClientClass& class_name) {
+ if (!required_classes_.contains(class_name)) {
+ required_classes_.insert(class_name);
+ }
+ }
+
+ /// @brief Returns classes which are required to be evaluated
+ const ClientClasses& getRequiredClasses() const {
+ return (required_classes_);
+ }
+
+ /// @brief returns the last address that was tried from this pool
+ ///
+ /// @return address/prefix that was last tried from this pool
+ isc::asiolink::IOAddress getLastAllocated() const {
+ return last_allocated_;
+ }
+
+ /// @brief checks if the last address is valid
+ /// @return true if the last address is valid
+ bool isLastAllocatedValid() const {
+ return last_allocated_valid_;
+ }
+
+ /// @brief sets the last address that was tried from this pool
+ ///
+ /// @param addr address/prefix to that was tried last
+ void setLastAllocated(const isc::asiolink::IOAddress& addr) {
+ last_allocated_ = addr;
+ last_allocated_valid_ = true;
+ }
+
+ /// @brief resets the last address to invalid
+ void resetLastAllocated() {
+ last_allocated_valid_ = false;
+ }
+
/// @brief Unparse a pool object.
///
/// @return A pointer to unparsed pool configuration.
@@ -203,14 +265,19 @@ protected:
/// @brief Optional definition of a client class
///
- /// If empty, all classes are allowed. If non-empty, only those listed
- /// here are allowed.
+ /// @ref Network::client_class_
+ ClientClass client_class_;
+
+ /// @brief Required classes
///
- /// @ref Network::white_list_
- ClientClasses white_list_;
+ /// @ref isc::dhcp::Network::required_classes
+ ClientClasses required_classes_;
+
+ /// @brief Pointer to the user context (may be NULL)
+ data::ConstElementPtr user_context_;
/// @brief Last allocated address
- /// See @ref isc::dhcp::Subnet::last_allocated_ia_
+ /// See @ref isc::dhcp::subnet::last_allocated_ia_
/// Initialized and reset to first
isc::asiolink::IOAddress last_allocated_;
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 3859891154..1e25f15714 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -408,7 +408,8 @@ const PoolPtr Subnet::getPool(Lease::Type type,
if (ub != pools.begin()) {
--ub;
- if ((*ub)->inRange(hint) && (*ub)->clientSupported(client_classes)) {
+ if ((*ub)->inRange(hint) &&
+ (*ub)->clientSupported(client_classes)) {
candidate = *ub;
}
}
@@ -732,7 +733,8 @@ Subnet6::toElement() const {
ElementPtr pool_list = Element::createList();
for (PoolCollection::const_iterator pool = pools.cbegin();
pool != pools.cend(); ++pool) {
- pool_list->add((*pool)->toElement());
+ // Add the elementized pool to the list
+ pool_list->add((*pool)->toElement());
}
map->set("pools", pool_list);
@@ -741,6 +743,7 @@ Subnet6::toElement() const {
ElementPtr pdpool_list = Element::createList();
for (PoolCollection::const_iterator pool = pdpools.cbegin();
pool != pdpools.cend(); ++pool) {
+ // Add the elementized pool to the list
pdpool_list->add((*pool)->toElement());
}
map->set("pd-pools", pdpool_list);
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index cb41bb2ebd..176e3dbb43 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -94,7 +94,7 @@ public:
/// not recognized (which is unlikely).
boost::posix_time::ptime getLastAllocatedTime(const Lease::Type& lease_type) const;
- /// @brief sets the last address that was tried from this pool
+ /// @brief sets the last address that was tried from this subnet
///
/// This method sets the last address that was attempted to be allocated
/// from this subnet. This is used as helper information for the next
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
index 52a5aac6b7..0a8afebc32 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
@@ -189,7 +189,8 @@ TEST_F(AllocEngine6Test, IterativeAllocator) {
alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA));
for (int i = 0; i < 1000; ++i) {
- IOAddress candidate = alloc->pickAddress(subnet_, cc_, duid_, IOAddress("::"));
+ IOAddress candidate = alloc->pickAddress(subnet_, cc_,
+ duid_, IOAddress("::"));
EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
}
}
@@ -211,7 +212,8 @@ TEST_F(AllocEngine6Test, IterativeAllocator_class) {
cc_.insert("bar");
for (int i = 0; i < 1000; ++i) {
- IOAddress candidate = alloc->pickAddress(subnet_, cc_, duid_, IOAddress("::"));
+ IOAddress candidate = alloc->pickAddress(subnet_, cc_,
+ duid_, IOAddress("::"));
EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_));
}
@@ -233,23 +235,33 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) {
subnet_->addPool(pool3);
// Let's check the first pool (5 addresses here)
- EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// The second pool is easy - only one address here
- EXPECT_EQ("2001:db8:1::100", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::100",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// This is the third and last pool, with 2 addresses in it
- EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// We iterated over all addresses and reached to the end of the last pool.
// Let's wrap around and start from the beginning
- EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepInClass) {
@@ -274,23 +286,33 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepInClass) {
cc_.insert("foo");
// Let's check the first pool (5 addresses here)
- EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// The second pool is easy - only one address here
- EXPECT_EQ("2001:db8:1::100", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::100",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// This is the third and last pool, with 2 addresses in it
- EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// We iterated over all addresses and reached to the end of the last pool.
// Let's wrap around and start from the beginning
- EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepOutClass) {
@@ -311,22 +333,31 @@ TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepOutClass) {
subnet_->addPool(pool3);
// Let's check the first pool (5 addresses here)
- EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::3", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::4", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::5", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::3",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::4",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::5",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// The second pool is skipped
// This is the third and last pool, with 2 addresses in it
- EXPECT_EQ("2001:db8:1::105", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::106", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::105",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::106",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// We iterated over all addresses and reached to the end of the last pool.
// Let's wrap around and start from the beginning
- EXPECT_EQ("2001:db8:1::1", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:1::2", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::1",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::2",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) {
@@ -347,41 +378,63 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) {
// 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
// First pool check (Let's check over all 16 leases)
- EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Second pool (just one lease here)
- EXPECT_EQ("2001:db8:1::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Third pool (256 leases, let's check first and last explicitly and the
// rest over in a pool
- EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
for (int i = 1; i < 255; i++) {
stringstream exp;
exp << "2001:db8:2:" << hex << i << dec << "::";
- EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ(exp.str(),
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
- EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Ok, we've iterated over all prefixes in all pools. We now wrap around.
// We're looping over now (iterating over first pool again)
- EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepInClass) {
@@ -408,41 +461,63 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepInClass) {
// 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
// First pool check (Let's check over all 16 leases)
- EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Second pool (just one lease here)
- EXPECT_EQ("2001:db8:1::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:1::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Third pool (256 leases, let's check first and last explicitly and the
// rest over in a pool
- EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
for (int i = 1; i < 255; i++) {
stringstream exp;
exp << "2001:db8:2:" << hex << i << dec << "::";
- EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ(exp.str(),
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
- EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Ok, we've iterated over all prefixes in all pools. We now wrap around.
// We're looping over now (iterating over first pool again)
- EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepOutClass) {
@@ -465,40 +540,61 @@ TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepOutClass) {
// 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::)
// First pool check (Let's check over all 16 leases)
- EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:20::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:30::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:40::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:50::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:60::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:70::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:80::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:90::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:a0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:b0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:c0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:d0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:e0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:f0::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:20::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:30::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:40::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:50::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:60::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:70::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:80::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:90::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:a0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:b0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:c0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:d0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:e0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:f0::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// The second pool is skipped
// Third pool (256 leases, let's check first and last explicitly and the
// rest over in a pool
- EXPECT_EQ("2001:db8:2::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:2::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
for (int i = 1; i < 255; i++) {
stringstream exp;
exp << "2001:db8:2:" << hex << i << dec << "::";
- EXPECT_EQ(exp.str(), alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ(exp.str(),
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
- EXPECT_EQ("2001:db8:2:ff::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:2:ff::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
// Ok, we've iterated over all prefixes in all pools. We now wrap around.
// We're looping over now (iterating over first pool again)
- EXPECT_EQ("2001:db8::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
- EXPECT_EQ("2001:db8:0:10::", alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
+ EXPECT_EQ("2001:db8:0:10::",
+ alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText());
}
// This test verifies that the iterative allocator can step over addresses
@@ -596,7 +692,8 @@ TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
std::set generated_addrs;
int cnt = 0;
while (++cnt) {
- IOAddress candidate = alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::"));
+ IOAddress candidate = alloc.pickAddress(subnet_, cc_,
+ duid_, IOAddress("::"));
EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate));
// One way to easily verify that the iterative allocator really works is
@@ -635,7 +732,7 @@ TEST_F(AllocEngine6Test, smallPool6) {
// Create a subnet with a pool that has one address.
initSubnet(IOAddress("2001:db8:1::"), addr, addr);
-
+
// Initialize FQDN for a lease.
initFqdn("myhost.example.com", true, true);
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
index eeed469755..5b86b4e6a5 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h
+++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
index fb2f22a554..6cd404aed5 100644
--- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc
@@ -739,6 +739,8 @@ TEST(CfgSubnets4Test, unparseSubnet) {
subnet2->setIface("lo");
subnet2->setRelayInfo(IOAddress("10.0.0.1"));
subnet3->setIface("eth1");
+ subnet3->requireClientClass("foo");
+ subnet3->requireClientClass("bar");
data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
subnet1->setContext(ctx1);
@@ -806,7 +808,8 @@ TEST(CfgSubnets4Test, unparseSubnet) {
" \"4o6-subnet\": \"\",\n"
" \"reservation-mode\": \"all\",\n"
" \"option-data\": [ ],\n"
- " \"pools\": [ ]\n"
+ " \"pools\": [ ]\n,"
+ " \"require-client-classes\": [ \"foo\", \"bar\" ]\n"
"} ]\n";
runToElementTest(expected, cfg);
}
@@ -826,6 +829,7 @@ TEST(CfgSubnets4Test, unparsePool) {
pool1->setContext(ctx1);
data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }");
pool2->setContext(ctx2);
+ pool2->requireClientClass("foo");
subnet->addPool(pool1);
subnet->addPool(pool2);
@@ -857,9 +861,10 @@ TEST(CfgSubnets4Test, unparsePool) {
" \"user-context\": { \"version\": 1 }\n"
" },{\n"
" \"option-data\": [ ],\n"
- " \"pool\": \"192.0.2.64/26\"\n,"
+ " \"pool\": \"192.0.2.64/26\",\n"
+ " \"user-context\": { \"foo\": \"bar\" },\n"
" \"client-class\": \"bar\",\n"
- " \"user-context\": { \"foo\": \"bar\" }\n"
+ " \"require-client-classes\": [ \"foo\" ]\n"
" }\n"
" ]\n"
"} ]\n";
diff --git a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
index 5565387d26..c9dd4b0fd0 100644
--- a/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfg_subnets6_unittest.cc
@@ -438,6 +438,8 @@ TEST(CfgSubnets6Test, unparseSubnet) {
subnet2->setIface("lo");
subnet2->setRelayInfo(IOAddress("2001:db8:ff::2"));
subnet3->setIface("eth1");
+ subnet3->requireClientClass("foo");
+ subnet3->requireClientClass("bar");
data::ElementPtr ctx1 = data::Element::fromJSON("{ \"comment\": \"foo\" }");
subnet1->setContext(ctx1);
@@ -494,7 +496,8 @@ TEST(CfgSubnets6Test, unparseSubnet) {
" \"reservation-mode\": \"all\",\n"
" \"pools\": [ ],\n"
" \"pd-pools\": [ ],\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"require-client-classes\": [ \"foo\", \"bar\" ]\n"
"} ]\n";
runToElementTest(expected, cfg);
}
@@ -517,6 +520,7 @@ TEST(CfgSubnets6Test, unparsePool) {
pool1->setContext(ctx1);
data::ElementPtr ctx2 = data::Element::fromJSON("{ \"foo\": \"bar\" }");
pool2->setContext(ctx2);
+ pool2->requireClientClass("foo");
subnet->addPool(pool1);
subnet->addPool(pool2);
@@ -542,9 +546,10 @@ TEST(CfgSubnets6Test, unparsePool) {
" \"option-data\": [ ]\n"
" },{\n"
" \"pool\": \"2001:db8:1:1::/64\",\n"
- " \"client-class\": \"bar\",\n"
" \"user-context\": { \"foo\": \"bar\" },\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"client-class\": \"bar\",\n"
+ " \"require-client-classes\": [ \"foo\" ]\n"
" }\n"
" ],\n"
" \"pd-pools\": [ ],\n"
@@ -569,6 +574,8 @@ TEST(CfgSubnets6Test, unparsePdPool) {
data::ElementPtr ctx1 = data::Element::fromJSON("{ \"foo\": [ \"bar\" ] }");
pdpool1->setContext(ctx1);
+ pdpool1->requireClientClass("bar");
+ pdpool2->allowClientClass("bar");
subnet->addPool(pdpool1);
subnet->addPool(pdpool2);
@@ -593,7 +600,8 @@ TEST(CfgSubnets6Test, unparsePdPool) {
" \"prefix-len\": 48,\n"
" \"delegated-len\": 64,\n"
" \"user-context\": { \"foo\": [ \"bar\" ] },\n"
- " \"option-data\": [ ]\n"
+ " \"option-data\": [ ],\n"
+ " \"require-client-classes\": [ \"bar\" ]\n"
" },{\n"
" \"prefix\": \"2001:db8:3::\",\n"
" \"prefix-len\": 48,\n"
diff --git a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
index 5083400c50..d3af6cf043 100644
--- a/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/client_class_def_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -117,10 +117,10 @@ protected:
parser.parse(dictionary, config_element, family);
// If we didn't throw, then return the first and only class
- ClientClassDefMapPtr classes = dictionary->getClasses();
- ClientClassDefMap::iterator it = classes->begin();
- if (it != classes->end()) {
- return (*it).second;
+ ClientClassDefListPtr classes = dictionary->getClasses();
+ ClientClassDefList::const_iterator it = classes->cbegin();
+ if (it != classes->cend()) {
+ return (*it);
}
// Return NULL if for some reason the class doesn't exist.
@@ -885,5 +885,89 @@ TEST_F(ClientClassDefParserTest, filenameBogus) {
EXPECT_THROW(parseClientClassDef(cfg_too_long, AF_INET), DhcpConfigError);
}
+// Verifies that backward and built-in dependencies will parse.
+TEST_F(ClientClassDefListParserTest, dependentList) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('VENDOR_CLASS_foo')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"two\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"three\", \n"
+ " \"test\": \"member('two')\" \n"
+ " } \n"
+ "] \n";
+
+ // Parsing the list should succeed.
+ ClientClassDictionaryPtr dictionary;
+ ASSERT_NO_THROW(dictionary = parseClientClassDefList(cfg_text, AF_INET));
+ ASSERT_TRUE(dictionary);
+
+ // We should have three classes in the dictionary.
+ EXPECT_EQ(3, dictionary->getClasses()->size());
+
+ // Make sure we can find all three.
+ ClientClassDefPtr cclass;
+ ASSERT_NO_THROW(cclass = dictionary->findClass("one"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("one", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("two"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("two", cclass->getName());
+
+ ASSERT_NO_THROW(cclass = dictionary->findClass("three"));
+ ASSERT_TRUE(cclass);
+ EXPECT_EQ("three", cclass->getName());
+}
+
+// Verifies that not defined dependencies will not parse.
+TEST_F(ClientClassDefListParserTest, dependentNotDefined) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
+// Verifies that forward dependencies will not parse.
+TEST_F(ClientClassDefListParserTest, dependentForwardError) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"foo\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_THROW(parseClientClassDefList(cfg_text, AF_INET6), DhcpConfigError);
+}
+
+// Verifies that backward dependencies will parse.
+TEST_F(ClientClassDefListParserTest, dependentBackward) {
+ std::string cfg_text =
+ "[ \n"
+ " { \n"
+ " \"name\": \"foo\" \n"
+ " }, \n"
+ " { \n"
+ " \"name\": \"one\", \n"
+ " \"test\": \"member('foo')\" \n"
+ " } \n"
+ "] \n";
+
+ EXPECT_NO_THROW(parseClientClassDefList(cfg_text, AF_INET6));
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
index ed16d27d8b..c29fc45d30 100644
--- a/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
+++ b/src/lib/dhcpsrv/tests/client_class_def_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -142,6 +142,13 @@ TEST(ClientClassDef, copyAndEquality) {
EXPECT_TRUE(*cclass == *cclass2);
EXPECT_FALSE(*cclass != *cclass2);
+ // Verify the required flag is enough to make classes not equal.
+ EXPECT_FALSE(cclass->getRequired());
+ cclass2->setRequired(true);
+ EXPECT_TRUE(cclass2->getRequired());
+ EXPECT_FALSE(*cclass == *cclass2);
+ EXPECT_TRUE(*cclass != *cclass2);
+
// Make a class that differs from the first class only by name and
// verify that the equality tools reflect that the classes are not equal.
ASSERT_NO_THROW(cclass2.reset(new ClientClassDef("class_two", expr,
@@ -218,23 +225,24 @@ TEST(ClientClassDictionary, basics) {
// Verify constructor doesn't throw
ASSERT_NO_THROW(dictionary.reset(new ClientClassDictionary()));
- // Verify we can fetch a pointer the map of classes and
+ // Verify we can fetch a pointer the list of classes and
// that we start with no classes defined
- const ClientClassDefMapPtr classes = dictionary->getClasses();
+ const ClientClassDefListPtr classes = dictionary->getClasses();
ASSERT_TRUE(classes);
EXPECT_EQ(0, classes->size());
// Verify that we can add classes with both addClass variants
// First addClass(name, expression, cfg_option)
- ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", cfg_option));
- ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc1", expr, "", false, cfg_option));
+ ASSERT_NO_THROW(dictionary->addClass("cc2", expr, "", false, cfg_option));
// Verify duplicate add attempt throws
- ASSERT_THROW(dictionary->addClass("cc2", expr, "", cfg_option),
+ ASSERT_THROW(dictionary->addClass("cc2", expr, "", false, cfg_option),
DuplicateClientClassDef);
// Verify that you cannot add a class with no name.
- ASSERT_THROW(dictionary->addClass("", expr, "", cfg_option), BadValue);
+ ASSERT_THROW(dictionary->addClass("", expr, "", false, cfg_option),
+ BadValue);
// Now with addClass(class pointer)
ASSERT_NO_THROW(cclass.reset(new ClientClassDef("cc3", expr, cfg_option)));
@@ -290,14 +298,14 @@ TEST(ClientClassDictionary, copyAndEquality) {
CfgOptionPtr options;
dictionary.reset(new ClientClassDictionary());
- ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false, options));
// Copy constructor should succeed.
ASSERT_NO_THROW(dictionary2.reset(new ClientClassDictionary(*dictionary)));
- // Allocated class map pointers should not be equal
+ // Allocated class list pointers should not be equal
EXPECT_NE(dictionary->getClasses().get(), dictionary2->getClasses().get());
// Equality tools should reflect that the dictionaries are equal.
@@ -338,6 +346,7 @@ TEST(ClientClassDef, fixedFieldsDefaults) {
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
// Let's checks that it doesn't return any nonsense
+ EXPECT_FALSE(cclass->getRequired());
EXPECT_FALSE(cclass->getCfgOptionDef());
string empty;
ASSERT_EQ(IOAddress("0.0.0.0"), cclass->getNextServer());
@@ -360,6 +369,7 @@ TEST(ClientClassDef, fixedFieldsBasics) {
// Verify we can create a class with a name, expression, and no cfg_option
ASSERT_NO_THROW(cclass.reset(new ClientClassDef(name, expr)));
+ cclass->setRequired(true);
string sname = "This is a very long string that can be a server name";
string filename = "this-is-a-slightly-longish-name-of-a-file.txt";
@@ -369,7 +379,8 @@ TEST(ClientClassDef, fixedFieldsBasics) {
cclass->setFilename(filename);
// Let's checks that it doesn't return any nonsense
- ASSERT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
+ EXPECT_TRUE(cclass->getRequired());
+ EXPECT_EQ(IOAddress("1.2.3.4"), cclass->getNextServer());
EXPECT_EQ(sname, cclass->getSname());
EXPECT_EQ(filename, cclass->getFilename());
}
@@ -390,6 +401,7 @@ TEST(ClientClassDef, unparseDef) {
std::string user_context = "{ \"comment\": \"" + comment + "\", ";
user_context += "\"bar\": 1 }";
cclass->setContext(isc::data::Element::fromJSON(user_context));
+ cclass->setRequired(true);
std::string next_server = "1.2.3.4";
cclass->setNextServer(IOAddress(next_server));
std::string sname = "my-server.example.com";
@@ -402,6 +414,7 @@ TEST(ClientClassDef, unparseDef) {
"\"comment\": \"" + comment + "\",\n"
"\"name\": \"" + name + "\",\n"
"\"test\": \"" + test + "\",\n"
+ "\"only-if-required\": true,\n"
"\"next-server\": \"" + next_server + "\",\n"
"\"server-hostname\": \"" + sname + "\",\n"
"\"boot-file-name\": \"" + filename + "\",\n"
@@ -419,9 +432,9 @@ TEST(ClientClassDictionary, unparseDict) {
// Get a client class dictionary and fill it
dictionary.reset(new ClientClassDictionary());
- ASSERT_NO_THROW(dictionary->addClass("one", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("two", expr, "", options));
- ASSERT_NO_THROW(dictionary->addClass("three", expr, "", options));
+ ASSERT_NO_THROW(dictionary->addClass("one", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("two", expr, "", false, options));
+ ASSERT_NO_THROW(dictionary->addClass("three", expr, "", false, options));
// Unparse it
auto add_defaults =
diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
index 5d5934a6ff..7f8681b23d 100644
--- a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
@@ -410,8 +410,8 @@ TEST_F(HostReservationParserTest, dhcp4ClientClasses) {
const ClientClasses& classes = hosts[0]->getClientClasses4();
ASSERT_EQ(2, classes.size());
- EXPECT_EQ(1, classes.count("foo"));
- EXPECT_EQ(1, classes.count("bar"));
+ EXPECT_TRUE(classes.contains("foo"));
+ EXPECT_TRUE(classes.contains("bar"));
CfgMgr::instance().setFamily(AF_INET);
CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
@@ -881,8 +881,8 @@ TEST_F(HostReservationParserTest, dhcp6ClientClasses) {
const ClientClasses& classes = hosts[0]->getClientClasses6();
ASSERT_EQ(2, classes.size());
- EXPECT_EQ(1, classes.count("foo"));
- EXPECT_EQ(1, classes.count("bar"));
+ EXPECT_TRUE(classes.contains("foo"));
+ EXPECT_TRUE(classes.contains("bar"));
// lower duid value
boost::algorithm::to_lower(config);
diff --git a/src/lib/dhcpsrv/tests/host_unittest.cc b/src/lib/dhcpsrv/tests/host_unittest.cc
index 88216ed548..79871774db 100644
--- a/src/lib/dhcpsrv/tests/host_unittest.cc
+++ b/src/lib/dhcpsrv/tests/host_unittest.cc
@@ -1026,13 +1026,14 @@ TEST_F(HostTest, toText) {
host->addClientClass6("hub");
host->addClientClass6("device");
+ // Note that now classes are in insert order.
EXPECT_EQ("duid=1112131415 hostname=myhost ipv4_reservation=(no)"
" siaddr=(no)"
" sname=(empty)"
" file=(empty)"
" ipv6_reservations=(none)"
" dhcp4_class0=modem dhcp4_class1=router"
- " dhcp6_class0=device dhcp6_class1=hub",
+ " dhcp6_class0=hub dhcp6_class1=device",
host->toText());
}
diff --git a/src/lib/dhcpsrv/tests/pool_unittest.cc b/src/lib/dhcpsrv/tests/pool_unittest.cc
index 27960e0254..acf5b690d2 100644
--- a/src/lib/dhcpsrv/tests/pool_unittest.cc
+++ b/src/lib/dhcpsrv/tests/pool_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -214,7 +214,7 @@ TEST(Pool4Test, clientClass) {
three_classes.insert("baz");
// No class restrictions defined, any client should be supported
- EXPECT_EQ(0, pool->getClientClasses().size());
+ EXPECT_TRUE(pool->getClientClass().empty());
EXPECT_TRUE(pool->clientSupported(no_class));
EXPECT_TRUE(pool->clientSupported(foo_class));
EXPECT_TRUE(pool->clientSupported(bar_class));
@@ -222,7 +222,7 @@ TEST(Pool4Test, clientClass) {
// Let's allow only clients belonging to "bar" class.
pool->allowClientClass("bar");
- EXPECT_EQ(1, pool->getClientClasses().size());
+ EXPECT_EQ("bar", pool->getClientClass());
EXPECT_FALSE(pool->clientSupported(no_class));
EXPECT_FALSE(pool->clientSupported(foo_class));
@@ -268,6 +268,35 @@ TEST(Pool4Test, clientClasses) {
EXPECT_TRUE(pool->clientSupported(bar_class));
}
+// This test checks that handling for require-client-classes is valid.
+TEST(Pool4Test, requiredClasses) {
+ // Create a pool.
+ Pool4Ptr pool(new Pool4(IOAddress("192.0.2.0"),
+ IOAddress("192.0.2.255")));
+
+ // This client starts with no required classes.
+ EXPECT_TRUE(pool->getRequiredClasses().empty());
+
+ // Add the first class
+ pool->requireClientClass("router");
+ EXPECT_EQ(1, pool->getRequiredClasses().size());
+
+ // Add a second class
+ pool->requireClientClass("modem");
+ EXPECT_EQ(2, pool->getRequiredClasses().size());
+ EXPECT_TRUE(pool->getRequiredClasses().contains("router"));
+ EXPECT_TRUE(pool->getRequiredClasses().contains("modem"));
+ EXPECT_FALSE(pool->getRequiredClasses().contains("foo"));
+
+ // Check that it's ok to add the same class repeatedly
+ EXPECT_NO_THROW(pool->requireClientClass("foo"));
+ EXPECT_NO_THROW(pool->requireClientClass("foo"));
+ EXPECT_NO_THROW(pool->requireClientClass("foo"));
+
+ // Check that 'foo' is marked for required evaluation
+ EXPECT_TRUE(pool->getRequiredClasses().contains("foo"));
+}
+
// This test checks that handling for last allocated address/prefix is valid.
TEST(Pool4Test, lastAllocated) {
// Create a pool.
@@ -618,7 +647,7 @@ TEST(Pool6Test, clientClass) {
three_classes.insert("baz");
// No class restrictions defined, any client should be supported
- EXPECT_EQ(0, pool.getClientClasses().size());
+ EXPECT_TRUE(pool.getClientClass().empty());
EXPECT_TRUE(pool.clientSupported(no_class));
EXPECT_TRUE(pool.clientSupported(foo_class));
EXPECT_TRUE(pool.clientSupported(bar_class));
@@ -626,7 +655,7 @@ TEST(Pool6Test, clientClass) {
// Let's allow only clients belonging to "bar" class.
pool.allowClientClass("bar");
- EXPECT_EQ(1, pool.getClientClasses().size());
+ EXPECT_EQ("bar", pool.getClientClass());
EXPECT_FALSE(pool.clientSupported(no_class));
EXPECT_FALSE(pool.clientSupported(foo_class));
@@ -672,6 +701,35 @@ TEST(Pool6Test, clientClasses) {
EXPECT_TRUE(pool.clientSupported(bar_class));
}
+// This test checks that handling for require-client-classes is valid.
+TEST(Pool6Test, requiredClasses) {
+ // Create a pool.
+ Pool6 pool(Lease::TYPE_NA, IOAddress("2001:db8::1"),
+ IOAddress("2001:db8::2"));
+
+ // This client starts with no required classes.
+ EXPECT_TRUE(pool.getRequiredClasses().empty());
+
+ // Add the first class
+ pool.requireClientClass("router");
+ EXPECT_EQ(1, pool.getRequiredClasses().size());
+
+ // Add a second class
+ pool.requireClientClass("modem");
+ EXPECT_EQ(2, pool.getRequiredClasses().size());
+ EXPECT_TRUE(pool.getRequiredClasses().contains("router"));
+ EXPECT_TRUE(pool.getRequiredClasses().contains("modem"));
+ EXPECT_FALSE(pool.getRequiredClasses().contains("foo"));
+
+ // Check that it's ok to add the same class repeatedly
+ EXPECT_NO_THROW(pool.requireClientClass("foo"));
+ EXPECT_NO_THROW(pool.requireClientClass("foo"));
+ EXPECT_NO_THROW(pool.requireClientClass("foo"));
+
+ // Check that 'foo' is marked for required evaluation
+ EXPECT_TRUE(pool.getRequiredClasses().contains("foo"));
+}
+
// This test checks that handling for last allocated address/prefix is valid.
TEST(Pool6Test, lastAllocated) {
// Create a pool.
diff --git a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
index 9644fc95e4..d54ffe8b63 100644
--- a/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
+++ b/src/lib/dhcpsrv/tests/shared_network_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2017-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -53,6 +53,7 @@ public:
" \"server-hostname\": \"\","
" \"boot-file-name\": \"\","
" \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"4o6-interface\": \"\","
" \"4o6-interface-id\": \"\","
@@ -73,6 +74,7 @@ public:
" \"server-hostname\": \"\","
" \"boot-file-name\": \"\","
" \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"4o6-interface\": \"\","
" \"4o6-interface-id\": \"\","
@@ -165,9 +167,7 @@ TEST_F(SharedNetwork4ParserTest, clientClassMatchClientId) {
network = parser.parse(config_element);
ASSERT_TRUE(network);
- const ClientClasses classes = network->getClientClasses();
- ASSERT_EQ(1, classes.size());
- EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_EQ("alpha", network->getClientClass());
EXPECT_FALSE(network->getMatchClientId());
}
@@ -201,6 +201,7 @@ public:
" \"preferred-lifetime\": 300,"
" \"valid-lifetime\": 400,"
" \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"decline-probation-period\": 86400,"
" \"dhcp4o6-port\": 0,"
@@ -216,6 +217,7 @@ public:
" \"preferred-lifetime\": 30,"
" \"valid-lifetime\": 40,"
" \"client-class\": \"\","
+ " \"require-client-classes\": []\n,"
" \"reservation-mode\": \"all\","
" \"decline-probation-period\": 86400,"
" \"dhcp4o6-port\": 0,"
@@ -290,9 +292,58 @@ TEST_F(SharedNetwork6ParserTest, clientClass) {
network = parser.parse(config_element);
ASSERT_TRUE(network);
- const ClientClasses classes = network->getClientClasses();
- ASSERT_EQ(1, classes.size());
- EXPECT_TRUE(classes.contains("alpha"));
+ EXPECT_EQ("alpha", network->getClientClass());
+}
+
+// This test verifies that it's possible to specify require-client-classes
+// on shared-network level.
+TEST_F(SharedNetwork6ParserTest, evalClientClasses) {
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ ElementPtr class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create("beta"));
+ config_element->set("require-client-classes", class_list);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ network = parser.parse(config_element);
+ ASSERT_TRUE(network);
+
+ const ClientClasses& classes = network->getRequiredClasses();
+ EXPECT_EQ(2, classes.size());
+ EXPECT_EQ("alpha, beta", classes.toText());
+}
+
+// This test verifies that bad require-client-classes configs raise
+// expected errors.
+TEST_F(SharedNetwork6ParserTest, badEvalClientClasses) {
+ std::string config = getWorkingConfig();
+ ElementPtr config_element = Element::fromJSON(config);
+
+ // Element of the list must be strings.
+ ElementPtr class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create(1234));
+ config_element->set("require-client-classes", class_list);
+
+ // Parse configuration specified above.
+ SharedNetwork6Parser parser;
+ SharedNetwork6Ptr network;
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+
+ // Empty class name is forbidden.
+ class_list = Element::createList();
+ class_list->add(Element::create("alpha"));
+ class_list->add(Element::create(""));
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
+
+ // And of course the list must be a list even the parser can only
+ // trigger the previous error case...
+ class_list = Element::createMap();
+ EXPECT_THROW(network = parser.parse(config_element), DhcpConfigError);
}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/shared_network_unittest.cc b/src/lib/dhcpsrv/tests/shared_network_unittest.cc
index c3933477a0..2649289b8f 100644
--- a/src/lib/dhcpsrv/tests/shared_network_unittest.cc
+++ b/src/lib/dhcpsrv/tests/shared_network_unittest.cc
@@ -280,6 +280,7 @@ TEST(SharedNetwork4Test, unparse) {
std::string uc = "{ \"comment\": \"bar\", \"foo\": 1}";
data::ElementPtr ctx = data::Element::fromJSON(uc);
network->setContext(ctx);
+ network->requireClientClass("foo");
// Add several subnets.
Subnet4Ptr subnet1(new Subnet4(IOAddress("10.0.0.0"), 8, 10, 20, 30,
@@ -300,6 +301,7 @@ TEST(SharedNetwork4Test, unparse) {
" \"ip-address\": \"0.0.0.0\"\n"
" },\n"
" \"renew-timer\": 100,\n"
+ " \"require-client-classes\": [ \"foo\" ],\n"
" \"reservation-mode\": \"all\","
" \"subnet4\": [\n"
" {\n"
@@ -665,9 +667,11 @@ TEST(SharedNetwork6Test, unparse) {
network->setPreferred(200);
network->setValid(300);
network->setRapidCommit(true);
+ network->requireClientClass("foo");
data::ElementPtr ctx = data::Element::fromJSON("{ \"foo\": \"bar\" }");
network->setContext(ctx);
+ network->requireClientClass("foo");
// Add several subnets.
Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 64, 10, 20, 30,
@@ -688,6 +692,7 @@ TEST(SharedNetwork6Test, unparse) {
" \"ip-address\": \"::\"\n"
" },\n"
" \"renew-timer\": 100,\n"
+ " \"require-client-classes\": [ \"foo\" ],\n"
" \"reservation-mode\": \"all\","
" \"subnet6\": [\n"
" {\n"
diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc
index 1258164015..7f5a176fc5 100644
--- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc
+++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc
@@ -66,9 +66,12 @@ public:
}
// Build our reference dictionary of client classes
- ref_dictionary_->addClass("cc1", ExpressionPtr(), "", CfgOptionPtr());
- ref_dictionary_->addClass("cc2", ExpressionPtr(), "", CfgOptionPtr());
- ref_dictionary_->addClass("cc3", ExpressionPtr(), "", CfgOptionPtr());
+ ref_dictionary_->addClass("cc1", ExpressionPtr(),
+ "", false, CfgOptionPtr());
+ ref_dictionary_->addClass("cc2", ExpressionPtr(),
+ "", false, CfgOptionPtr());
+ ref_dictionary_->addClass("cc3", ExpressionPtr(),
+ "", false, CfgOptionPtr());
}
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 4226bc2eea..979d4e04b9 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -392,7 +392,7 @@ TEST(Subnet4Test, pool4Checks) {
// Tests whether Subnet4 object is able to store and process properly
// information about allowed client class (a single class).
-TEST(Subnet4Test, clientClasses) {
+TEST(Subnet4Test, clientClass) {
// Create the V4 subnet.
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
@@ -421,7 +421,7 @@ TEST(Subnet4Test, clientClasses) {
four_classes.insert("network");
// No class restrictions defined, any client should be supported
- EXPECT_EQ(0, subnet->getClientClasses().size());
+ EXPECT_TRUE(subnet->getClientClass().empty());
EXPECT_TRUE(subnet->clientSupported(no_class));
EXPECT_TRUE(subnet->clientSupported(foo_class));
EXPECT_TRUE(subnet->clientSupported(bar_class));
@@ -429,7 +429,7 @@ TEST(Subnet4Test, clientClasses) {
// Let's allow only clients belonging to "bar" class.
subnet->allowClientClass("bar");
- EXPECT_EQ(1, subnet->getClientClasses().size());
+ EXPECT_EQ("bar", subnet->getClientClass());
EXPECT_FALSE(subnet->clientSupported(no_class));
EXPECT_FALSE(subnet->clientSupported(foo_class));
@@ -451,44 +451,6 @@ TEST(Subnet4Test, clientClasses) {
EXPECT_TRUE(subnet->clientSupported(four_classes));
}
-// Tests whether Subnet4 object is able to store and process properly
-// information about allowed client classes (multiple classes allowed).
-TEST(Subnet4Test, clientClassesMultiple) {
- // Create the V4 subnet.
- Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
-
- // This client does not belong to any class.
- isc::dhcp::ClientClasses no_class;
-
- // This client belongs to foo only.
- isc::dhcp::ClientClasses foo_class;
- foo_class.insert("foo");
-
- // This client belongs to bar only. I like that client.
- isc::dhcp::ClientClasses bar_class;
- bar_class.insert("bar");
-
- // No class restrictions defined, any client should be supported
- EXPECT_EQ(0, subnet->getClientClasses().size());
- EXPECT_TRUE(subnet->clientSupported(no_class));
- EXPECT_TRUE(subnet->clientSupported(foo_class));
- EXPECT_TRUE(subnet->clientSupported(bar_class));
-
- // Let's allow clients belonging to "bar" or "foo" class.
- subnet->allowClientClass("bar");
- subnet->allowClientClass("foo");
- EXPECT_EQ(2, subnet->getClientClasses().size());
-
- // Class-less clients are to be rejected.
- EXPECT_FALSE(subnet->clientSupported(no_class));
-
- // Clients in foo class should be accepted.
- EXPECT_TRUE(subnet->clientSupported(foo_class));
-
- // Clients in bar class should be accepted as well.
- EXPECT_TRUE(subnet->clientSupported(bar_class));
-}
-
TEST(Subnet4Test, addInvalidOption) {
// Create the V4 subnet.
Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.2.0"), 8, 1, 2, 3));
@@ -788,18 +750,6 @@ TEST(Subnet6Test, Pool6getCapacity) {
subnet->getPoolCapacity(Lease::TYPE_NA, bar_class));
EXPECT_EQ(uint64_t(4294967296ull + 4294967296ull + 65536),
subnet->getPoolCapacity(Lease::TYPE_NA, three_classes));
-
- // This is 2^64 prefixes. We're overflown uint64_t.
- PoolPtr pool4(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:4::"), 64));
- subnet->addPool(pool4);
- EXPECT_EQ(std::numeric_limits::max(),
- subnet->getPoolCapacity(Lease::TYPE_NA));
-
- PoolPtr pool5(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1:5::"), 64));
- subnet->addPool(pool5);
- EXPECT_EQ(std::numeric_limits::max(),
- subnet->getPoolCapacity(Lease::TYPE_NA));
-
}
// Test checks whether the number of prefixes available in the pools are
@@ -978,7 +928,7 @@ TEST(Subnet6Test, poolTypes) {
// Tests whether Subnet6 object is able to store and process properly
// information about allowed client class (a single class).
-TEST(Subnet6Test, clientClasses) {
+TEST(Subnet6Test, clientClass) {
// Create the V6 subnet.
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
@@ -1007,7 +957,7 @@ TEST(Subnet6Test, clientClasses) {
four_classes.insert("network");
// No class restrictions defined, any client should be supported
- EXPECT_EQ(0, subnet->getClientClasses().size());
+ EXPECT_TRUE(subnet->getClientClass().empty());
EXPECT_TRUE(subnet->clientSupported(no_class));
EXPECT_TRUE(subnet->clientSupported(foo_class));
EXPECT_TRUE(subnet->clientSupported(bar_class));
@@ -1015,7 +965,7 @@ TEST(Subnet6Test, clientClasses) {
// Let's allow only clients belonging to "bar" class.
subnet->allowClientClass("bar");
- EXPECT_EQ(1, subnet->getClientClasses().size());
+ EXPECT_EQ("bar", subnet->getClientClass());
EXPECT_FALSE(subnet->clientSupported(no_class));
EXPECT_FALSE(subnet->clientSupported(foo_class));
@@ -1037,44 +987,6 @@ TEST(Subnet6Test, clientClasses) {
EXPECT_TRUE(subnet->clientSupported(four_classes));
}
-// Tests whether Subnet6 object is able to store and process properly
-// information about allowed client class (multiple classes allowed).
-TEST(Subnet6Test, clientClassesMultiple) {
- // Create the V6 subnet.
- Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
-
- // This client does not belong to any class.
- isc::dhcp::ClientClasses no_class;
-
- // This client belongs to foo only.
- isc::dhcp::ClientClasses foo_class;
- foo_class.insert("foo");
-
- // This client belongs to bar only. I like that client.
- isc::dhcp::ClientClasses bar_class;
- bar_class.insert("bar");
-
- // No class restrictions defined, any client should be supported
- EXPECT_EQ(0, subnet->getClientClasses().size());
- EXPECT_TRUE(subnet->clientSupported(no_class));
- EXPECT_TRUE(subnet->clientSupported(foo_class));
- EXPECT_TRUE(subnet->clientSupported(bar_class));
-
- // Let's allow only clients belonging to "foo" or "bar" class.
- subnet->allowClientClass("foo");
- subnet->allowClientClass("bar");
- EXPECT_EQ(2, subnet->getClientClasses().size());
-
- // Class-less clients are to be rejected.
- EXPECT_FALSE(subnet->clientSupported(no_class));
-
- // Clients in foo class should be accepted.
- EXPECT_TRUE(subnet->clientSupported(foo_class));
-
- // Clients in bar class should be accepted as well.
- EXPECT_TRUE(subnet->clientSupported(bar_class));
-}
-
// Checks that it is not allowed to add invalid pools.
TEST(Subnet6Test, pool6Checks) {
diff --git a/src/lib/eval/eval.dox b/src/lib/eval/eval.dox
index 80e7c61078..4f7d814273 100644
--- a/src/lib/eval/eval.dox
+++ b/src/lib/eval/eval.dox
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -21,6 +21,13 @@
tokens that are stored in Reverse Polish Notation in
EvalContext::expression.
+ Parameters to the @ref isc::eval::EvalContext class constructor are
+ the universe to choose between DHCPv4 and DHCPv6 for DHCP version
+ dependent expressions, and a function used
+ by the parser to accept only already defined or built-in client
+ class names in client class membership expressions. This function defaults
+ to accept all client class names.
+
Internally, the parser code is generated by flex and bison. These two
tools convert lexer.ll and parser.yy files into a number of .cc and .hh files.
To avoid a build of Kea depending on the presence of flex and bison, the
diff --git a/src/lib/eval/eval_context.cc b/src/lib/eval/eval_context.cc
index 06ba3abe7c..8fd263ac3c 100644
--- a/src/lib/eval/eval_context.cc
+++ b/src/lib/eval/eval_context.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -18,9 +18,10 @@
#include
#include
-EvalContext::EvalContext(const Option::Universe& option_universe)
- : trace_scanning_(false), trace_parsing_(false),
- option_universe_(option_universe)
+EvalContext::EvalContext(const Option::Universe& option_universe,
+ CheckDefined check_defined)
+ : trace_scanning_(false), trace_parsing_(false),
+ option_universe_(option_universe), check_defined_(check_defined)
{
}
@@ -28,6 +29,11 @@ EvalContext::~EvalContext()
{
}
+bool
+EvalContext::acceptAll(const ClientClass&) {
+ return (true);
+}
+
bool
EvalContext::parseString(const std::string& str, ParserType type)
{
@@ -185,6 +191,11 @@ EvalContext::fromUint32(const uint32_t integer) {
return (tmp);
}
+bool
+EvalContext::isClientClassDefined(const ClientClass& client_class) {
+ return (check_defined_(client_class));
+}
+
void
EvalContext::fatal (const std::string& what)
{
diff --git a/src/lib/eval/eval_context.h b/src/lib/eval/eval_context.h
index 96c683604c..bd47ae791e 100644
--- a/src/lib/eval/eval_context.h
+++ b/src/lib/eval/eval_context.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -41,17 +41,29 @@ public:
PARSER_STRING ///< expression is expected to evaluate to string
} ParserType;
+ /// @brief Type of the check defined function.
+ typedef std::function CheckDefined;
/// @brief Default constructor.
///
/// @param option_universe Option universe: DHCPv4 or DHCPv6. This is used
/// by the parser to determine which option definitions set should be used
/// to map option names to option codes.
- EvalContext(const Option::Universe& option_universe);
+ /// @param check_defined A function called to check if a client class
+ /// used for membership is already defined. If it is not the parser
+ /// will fail: only backward or built-in references are accepted.
+ EvalContext(const Option::Universe& option_universe,
+ CheckDefined check_defined = acceptAll);
/// @brief destructor
virtual ~EvalContext();
+ /// @brief Accept all client class names
+ ///
+ /// @param client_class (unused)
+ /// @return true
+ static bool acceptAll(const ClientClass& client_class);
+
/// @brief Parsed expression (output tokens are stored here)
isc::dhcp::Expression expression;
@@ -169,7 +181,13 @@ public:
return (option_universe_);
}
-private:
+ /// @brief Check if a client class is already defined
+ ///
+ /// @param client_class the client class name to check
+ /// @return true if the client class is defined, false if not
+ bool isClientClassDefined(const ClientClass& client_class);
+
+ private:
/// @brief Flag determining scanner debugging.
bool trace_scanning_;
@@ -182,6 +200,9 @@ private:
/// set should be used to map option name to option code.
Option::Universe option_universe_;
+ /// @brief Function to check if a client class is already defined.
+ CheckDefined check_defined_;
+
};
}; // end of isc::eval namespace
diff --git a/src/lib/eval/eval_messages.mes b/src/lib/eval/eval_messages.mes
index 2a43435ec2..c70b7aeb3f 100644
--- a/src/lib/eval/eval_messages.mes
+++ b/src/lib/eval/eval_messages.mes
@@ -53,6 +53,12 @@ This debug message indicates that the given binary string is being pushed
onto the value stack. This represents either an IPv4 or IPv6 address.
The string is displayed in hex.
+# For use with TokenMember
+
+% EVAL_DEBUG_MEMBER Checking membership of '%1', pushing result %2
+This debug message indicates that the membership of the packet for
+the client class was checked.
+
# For use with TokenNot
% EVAL_DEBUG_NOT Popping %1 pushing %2
diff --git a/src/lib/eval/lexer.cc b/src/lib/eval/lexer.cc
index 80653ce429..515ce74408 100644
--- a/src/lib/eval/lexer.cc
+++ b/src/lib/eval/lexer.cc
@@ -712,8 +712,8 @@ static void yynoreturn yy_fatal_error ( const char* msg );
/* %% [3.0] code to copy yytext_ptr to yytext[] goes here, if %array \ */\
(yy_c_buf_p) = yy_cp;
/* %% [4.0] data tables for the DFA and the user's section 1 definitions go here */
-#define YY_NUM_RULES 52
-#define YY_END_OF_BUFFER 53
+#define YY_NUM_RULES 53
+#define YY_END_OF_BUFFER 54
/* This struct is not used in this scanner,
but its presence is necessary. */
struct yy_trans_info
@@ -721,42 +721,42 @@ struct yy_trans_info
flex_int32_t yy_verify;
flex_int32_t yy_nxt;
};
-static const flex_int16_t yy_acclist[285] =
+static const flex_int16_t yy_acclist[291] =
{ 0,
- 53, 51, 52, 1, 51, 52, 2, 52, 51, 52,
- 45, 51, 52, 46, 51, 52, 50, 51, 52, 49,
- 51, 52, 51, 52, 44, 51, 52, 5, 51, 52,
- 5, 51, 52, 51, 52, 51, 52, 51, 52,16390,
- 51, 52,16390, 47, 51, 52, 48, 51, 52, 51,
- 52,16390, 51, 52,16390, 51, 52,16390, 51, 52,
- 16390, 51, 52,16390, 51, 52,16390, 51, 52,16390,
- 51, 52,16390, 51, 52,16390, 51, 52,16390, 51,
- 52,16390, 51, 52,16390, 51, 52,16390, 51, 52,
- 16390, 51, 52,16390, 51, 52,16390, 51, 52,16390,
+ 54, 52, 53, 1, 52, 53, 2, 53, 52, 53,
+ 46, 52, 53, 47, 52, 53, 51, 52, 53, 50,
+ 52, 53, 52, 53, 45, 52, 53, 5, 52, 53,
+ 5, 52, 53, 52, 53, 52, 53, 52, 53,16390,
+ 52, 53,16390, 48, 52, 53, 49, 52, 53, 52,
+ 53,16390, 52, 53,16390, 52, 53,16390, 52, 53,
+ 16390, 52, 53,16390, 52, 53,16390, 52, 53,16390,
+ 52, 53,16390, 52, 53,16390, 52, 53,16390, 52,
+ 53,16390, 52, 53,16390, 52, 53,16390, 52, 53,
+ 16390, 52, 53,16390, 52, 53,16390, 52, 53,16390,
1, 2, 3, 5, 5, 7, 8,16390,16390, 8198,
16390,16390,16390,16390,16390,16390,16390,16390,16390,16390,
- 16390,16390,16390,16390,16390,16390,16390,16390,16390, 43,
16390,16390,16390,16390,16390,16390,16390,16390,16390,16390,
- 16390, 4, 7, 38,16390, 42,16390,16390,16390,16390,
- 20,16390,16390,16390,16390, 15,16390,16390,16390,16390,
- 16390, 21,16390,16390, 23,16390,16390, 41,16390,16390,
- 16390, 17,16390,16390,16390, 19,16390,16390,16390,16390,
- 16390,16390,16390,16390, 35,16390,16390,16390,16390, 24,
- 16390,16390,16390,16390,16390,16390,16390,16390, 22,16390,
+ 43,16390,16390,16390,16390,16390,16390,16390,16390,16390,
+ 16390,16390, 4, 7, 38,16390, 42,16390,16390,16390,
+ 16390, 20,16390,16390,16390,16390, 15,16390,16390,16390,
+ 16390,16390, 21,16390,16390, 23,16390,16390,16390, 41,
+ 16390,16390,16390, 17,16390,16390,16390, 19,16390,16390,
+ 16390,16390,16390,16390,16390,16390, 35,16390,16390,16390,
+ 16390, 24,16390,16390,16390,16390,16390,16390,16390,16390,
- 30,16390,16390,16390,16390, 14,16390,16390,16390,16390,
- 16390,16390,16390,16390,16390, 25,16390, 18,16390,16390,
- 16390,16390,16390,16390,16390,16390,16390,16390,16390,16390,
- 26,16390, 39,16390,16390, 16,16390, 27,16390, 40,
- 16390,16390,16390, 9,16390,16390, 10,16390, 11,16390,
- 29,16390,16390,16390, 33,16390, 28,16390, 7,16390,
- 16390, 31,16390,16390,16390, 32,16390,16390, 13,16390,
- 12,16390,16390,16390,16390, 37,16390,16390, 36,16390,
- 16390,16390, 34,16390
+ 16390, 22,16390, 30,16390,16390,16390,16390, 14,16390,
+ 16390,16390,16390,16390,16390,16390,16390,16390, 25,16390,
+ 18,16390,16390,16390,16390,16390,16390,16390,16390,16390,
+ 16390,16390,16390,16390, 26,16390, 39,16390,16390, 16,
+ 16390, 27,16390, 40,16390,16390, 44,16390,16390, 9,
+ 16390,16390, 10,16390, 11,16390, 29,16390,16390,16390,
+ 33,16390, 28,16390, 7,16390,16390, 31,16390,16390,
+ 16390, 32,16390,16390, 13,16390, 12,16390,16390,16390,
+ 16390, 37,16390,16390, 36,16390,16390,16390, 34,16390
} ;
-static const flex_int16_t yy_accept[203] =
+static const flex_int16_t yy_accept[208] =
{ 0,
1, 1, 1, 2, 4, 7, 9, 11, 14, 17,
20, 23, 25, 28, 31, 34, 36, 38, 41, 44,
@@ -765,22 +765,22 @@ static const flex_int16_t yy_accept[203] =
103, 103, 104, 105, 105, 106, 106, 106, 106, 106,
107, 108, 108, 108, 109, 110, 111, 112, 113, 114,
115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
- 125, 126, 127, 128, 129, 130, 132, 133, 134, 135,
- 136, 137, 138, 139, 140, 141, 142, 142, 143, 144,
- 146, 148, 149, 150, 151, 153, 154, 155, 156, 158,
+ 125, 126, 127, 128, 129, 130, 131, 133, 134, 135,
+ 136, 137, 138, 139, 140, 141, 142, 143, 143, 144,
+ 145, 147, 149, 150, 151, 152, 154, 155, 156, 157,
- 159, 160, 161, 162, 164, 165, 167, 168, 170, 171,
- 172, 174, 175, 176, 178, 179, 180, 181, 182, 183,
- 183, 184, 185, 187, 188, 189, 190, 192, 193, 194,
- 195, 196, 197, 198, 199, 201, 203, 204, 205, 206,
- 208, 209, 210, 211, 211, 212, 213, 214, 215, 216,
- 218, 220, 221, 222, 223, 224, 225, 226, 227, 228,
- 229, 230, 231, 231, 233, 235, 236, 238, 240, 242,
- 243, 244, 246, 247, 249, 251, 253, 254, 255, 257,
- 259, 260, 261, 262, 264, 265, 266, 268, 268, 269,
- 271, 273, 274, 275, 276, 278, 279, 281, 282, 283,
+ 159, 160, 161, 162, 163, 165, 166, 168, 169, 170,
+ 172, 173, 174, 176, 177, 178, 180, 181, 182, 183,
+ 184, 185, 185, 186, 187, 189, 190, 191, 192, 194,
+ 195, 196, 197, 198, 199, 200, 201, 202, 204, 206,
+ 207, 208, 209, 211, 212, 213, 214, 214, 215, 216,
+ 217, 218, 219, 221, 223, 224, 225, 226, 227, 228,
+ 229, 230, 231, 232, 233, 234, 235, 235, 237, 239,
+ 240, 242, 244, 246, 247, 249, 250, 252, 253, 255,
+ 257, 259, 260, 261, 263, 265, 266, 267, 268, 270,
+ 271, 272, 274, 274, 275, 277, 279, 280, 281, 282,
- 285, 285
+ 284, 285, 287, 288, 289, 291, 291
} ;
static const YY_CHAR yy_ec[256] =
@@ -824,104 +824,107 @@ static const YY_CHAR yy_meta[45] =
1, 1, 1, 1
} ;
-static const flex_int16_t yy_base[207] =
+static const flex_int16_t yy_base[212] =
{ 0,
- 0, 0, 314, 315, 311, 309, 307, 315, 315, 315,
- 315, 34, 315, 39, 36, 295, 293, 81, 115, 315,
- 315, 24, 37, 37, 26, 277, 45, 279, 43, 48,
- 270, 43, 59, 278, 106, 50, 277, 272, 300, 298,
- 296, 315, 122, 137, 112, 284, 283, 0, 282, 0,
- 315, 143, 150, 0, 0, 315, 263, 269, 271, 258,
- 252, 251, 250, 258, 265, 244, 259, 241, 74, 249,
- 248, 257, 252, 240, 239, 0, 251, 237, 243, 252,
- 249, 249, 229, 248, 235, 246, 146, 0, 0, 0,
- 0, 242, 242, 243, 0, 238, 225, 237, 0, 227,
+ 0, 0, 319, 320, 316, 314, 312, 320, 320, 320,
+ 320, 34, 320, 39, 36, 300, 298, 81, 115, 320,
+ 320, 24, 37, 37, 26, 282, 45, 284, 43, 48,
+ 275, 43, 59, 283, 106, 50, 282, 277, 305, 303,
+ 301, 320, 122, 137, 112, 289, 288, 0, 287, 0,
+ 320, 143, 150, 0, 0, 320, 268, 274, 276, 263,
+ 257, 256, 255, 263, 270, 249, 264, 246, 74, 254,
+ 253, 262, 252, 256, 244, 243, 0, 255, 241, 247,
+ 256, 253, 253, 233, 252, 239, 250, 146, 0, 0,
+ 0, 0, 246, 246, 247, 0, 242, 229, 241, 0,
- 224, 235, 226, 0, 226, 0, 217, 0, 225, 217,
- 148, 231, 227, 0, 213, 211, 215, 223, 222, 154,
- 221, 223, 0, 207, 204, 217, 0, 215, 214, 201,
- 216, 194, 201, 213, 0, 0, 191, 208, 193, 0,
- 193, 195, 204, 162, 191, 188, 190, 187, 187, 0,
- 0, 197, 197, 185, 186, 184, 156, 169, 168, 174,
- 165, 164, 166, 0, 0, 163, 0, 0, 0, 174,
- 172, 0, 172, 0, 0, 0, 166, 170, 186, 0,
- 170, 163, 155, 0, 154, 156, 0, 183, 151, 0,
- 0, 160, 155, 160, 0, 140, 0, 115, 50, 0,
+ 231, 228, 239, 230, 0, 230, 0, 237, 220, 0,
+ 228, 220, 148, 234, 230, 0, 216, 214, 218, 226,
+ 225, 154, 224, 226, 0, 210, 207, 220, 0, 218,
+ 217, 204, 219, 214, 196, 203, 215, 0, 0, 193,
+ 210, 195, 0, 195, 197, 206, 162, 193, 190, 192,
+ 189, 189, 0, 0, 199, 199, 186, 186, 187, 195,
+ 156, 172, 169, 175, 167, 165, 166, 0, 0, 164,
+ 0, 0, 0, 175, 0, 173, 0, 173, 0, 0,
+ 0, 167, 171, 187, 0, 170, 164, 156, 0, 155,
+ 157, 0, 183, 152, 0, 0, 161, 156, 161, 0,
- 315, 208, 210, 212, 71, 215
+ 164, 0, 124, 115, 0, 320, 208, 210, 212, 85,
+ 215
} ;
-static const flex_int16_t yy_def[207] =
+static const flex_int16_t yy_def[212] =
{ 0,
- 201, 1, 201, 201, 201, 201, 202, 201, 201, 201,
- 201, 201, 201, 201, 14, 203, 201, 201, 18, 201,
- 201, 18, 18, 18, 18, 19, 19, 19, 19, 19,
- 19, 19, 19, 19, 19, 19, 19, 19, 201, 201,
- 202, 201, 201, 201, 14, 203, 204, 205, 203, 206,
- 201, 201, 19, 18, 19, 201, 19, 19, 19, 19,
+ 206, 1, 206, 206, 206, 206, 207, 206, 206, 206,
+ 206, 206, 206, 206, 14, 208, 206, 206, 18, 206,
+ 206, 18, 18, 18, 18, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 19, 19, 206, 206,
+ 207, 206, 206, 206, 14, 208, 209, 210, 208, 211,
+ 206, 206, 19, 18, 19, 206, 19, 19, 19, 19,
18, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 19, 19, 19, 19, 19, 19, 201, 205, 206, 19,
+ 19, 19, 19, 19, 19, 19, 19, 206, 210, 211,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 19, 19, 19, 19, 19, 19, 19, 19, 19, 201,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 206, 19, 19, 19, 19, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 19, 19, 19, 201, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 206, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 19, 19, 201, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 19, 206, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 201, 19, 19, 19, 19, 19, 19, 19, 19, 19,
+ 19, 19, 19, 19, 19, 206, 19, 19, 19, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19,
- 0, 201, 201, 201, 201, 201
+ 19, 19, 19, 19, 19, 0, 206, 206, 206, 206,
+ 206
} ;
-static const flex_int16_t yy_nxt[360] =
+static const flex_int16_t yy_nxt[365] =
{ 0,
4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
14, 15, 15, 15, 16, 17, 18, 19, 19, 20,
21, 4, 22, 18, 23, 24, 25, 18, 26, 27,
28, 19, 29, 30, 31, 32, 33, 34, 35, 36,
19, 37, 19, 38, 43, 43, 43, 43, 44, 45,
- 45, 45, 45, 46, 201, 47, 57, 48, 58, 61,
+ 45, 45, 45, 46, 206, 47, 57, 48, 58, 61,
63, 47, 47, 47, 47, 47, 47, 59, 64, 70,
- 72, 66, 60, 71, 88, 62, 83, 67, 201, 75,
- 76, 48, 52, 52, 68, 77, 73, 84, 200, 53,
- 78, 54, 54, 54, 54, 46, 102, 54, 55, 55,
+ 72, 66, 60, 71, 73, 62, 84, 67, 206, 76,
+ 77, 48, 52, 52, 68, 78, 74, 85, 89, 53,
+ 79, 54, 54, 54, 54, 46, 103, 54, 55, 55,
- 103, 56, 53, 54, 54, 54, 54, 54, 54, 55,
+ 104, 56, 53, 54, 54, 54, 54, 54, 54, 55,
55, 55, 55, 55, 55, 55, 55, 55, 55, 55,
- 55, 55, 55, 55, 55, 55, 55, 55, 55, 201,
- 201, 55, 43, 43, 43, 43, 80, 55, 55, 55,
- 55, 55, 55, 81, 52, 52, 82, 87, 87, 87,
- 87, 201, 201, 199, 201, 120, 87, 87, 87, 87,
- 135, 136, 198, 56, 144, 144, 144, 144, 174, 175,
- 201, 163, 144, 144, 144, 144, 181, 181, 181, 181,
- 181, 181, 181, 181, 201, 201, 197, 196, 195, 194,
- 192, 191, 190, 189, 188, 187, 186, 185, 184, 183,
+ 55, 55, 55, 55, 55, 55, 55, 55, 55, 206,
+ 206, 55, 43, 43, 43, 43, 81, 55, 55, 55,
+ 55, 55, 55, 82, 52, 52, 83, 88, 88, 88,
+ 88, 206, 206, 205, 206, 122, 88, 88, 88, 88,
+ 138, 139, 204, 56, 147, 147, 147, 147, 179, 180,
+ 206, 167, 147, 147, 147, 147, 186, 186, 186, 186,
+ 186, 186, 186, 186, 206, 206, 203, 202, 201, 200,
+ 199, 197, 196, 195, 194, 193, 192, 191, 190, 189,
- 182, 180, 179, 201, 178, 177, 176, 193, 41, 173,
- 41, 41, 41, 49, 49, 47, 47, 89, 89, 89,
- 172, 171, 170, 169, 168, 167, 166, 165, 164, 162,
- 161, 160, 159, 158, 157, 156, 155, 154, 153, 152,
- 151, 150, 149, 148, 147, 146, 145, 143, 142, 141,
- 140, 139, 138, 137, 134, 133, 132, 131, 130, 129,
- 128, 127, 126, 125, 124, 123, 122, 121, 119, 118,
- 117, 116, 115, 114, 113, 112, 111, 110, 109, 108,
- 107, 106, 105, 104, 101, 100, 99, 98, 97, 96,
- 95, 94, 93, 92, 91, 90, 50, 46, 50, 42,
+ 188, 187, 185, 206, 184, 183, 182, 198, 41, 181,
+ 41, 41, 41, 49, 49, 47, 47, 90, 90, 90,
+ 178, 177, 176, 175, 174, 173, 172, 171, 170, 169,
+ 168, 166, 165, 164, 163, 162, 161, 160, 159, 158,
+ 157, 156, 155, 154, 153, 152, 151, 150, 149, 148,
+ 146, 145, 144, 143, 142, 141, 140, 137, 136, 135,
+ 134, 133, 132, 131, 130, 129, 128, 127, 126, 125,
+ 124, 123, 121, 120, 119, 118, 117, 116, 115, 114,
+ 113, 112, 111, 110, 109, 108, 107, 106, 105, 102,
+ 101, 100, 99, 98, 97, 96, 95, 94, 93, 92,
- 40, 39, 86, 85, 79, 74, 69, 65, 51, 50,
- 42, 40, 39, 201, 3, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201
+ 91, 50, 46, 50, 42, 40, 39, 87, 86, 80,
+ 75, 69, 65, 51, 50, 42, 40, 39, 206, 3,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206
} ;
-static const flex_int16_t yy_chk[360] =
+static const flex_int16_t yy_chk[365] =
{ 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -930,8 +933,8 @@ static const flex_int16_t yy_chk[360] =
1, 1, 1, 1, 12, 12, 12, 12, 14, 14,
14, 14, 14, 14, 15, 14, 22, 14, 22, 24,
25, 14, 14, 14, 14, 14, 14, 23, 25, 29,
- 30, 27, 23, 29, 205, 24, 36, 27, 15, 32,
- 32, 14, 18, 18, 27, 33, 30, 36, 199, 18,
+ 30, 27, 23, 29, 30, 24, 36, 27, 15, 32,
+ 32, 14, 18, 18, 27, 33, 30, 36, 210, 18,
33, 18, 18, 18, 18, 18, 69, 18, 18, 18,
69, 18, 18, 18, 18, 18, 18, 18, 18, 18,
@@ -939,49 +942,50 @@ static const flex_int16_t yy_chk[360] =
18, 18, 18, 18, 18, 19, 19, 19, 19, 19,
45, 19, 43, 43, 43, 43, 35, 19, 19, 19,
19, 19, 19, 35, 52, 52, 35, 44, 44, 44,
- 44, 53, 53, 198, 45, 87, 87, 87, 87, 87,
- 111, 111, 196, 52, 120, 120, 120, 120, 157, 157,
- 53, 144, 144, 144, 144, 144, 163, 163, 163, 163,
- 181, 181, 181, 181, 188, 188, 194, 193, 192, 189,
- 186, 185, 183, 182, 179, 178, 177, 173, 171, 170,
+ 44, 53, 53, 204, 45, 88, 88, 88, 88, 88,
+ 113, 113, 203, 52, 122, 122, 122, 122, 161, 161,
+ 53, 147, 147, 147, 147, 147, 167, 167, 167, 167,
+ 186, 186, 186, 186, 193, 193, 201, 199, 198, 197,
+ 194, 191, 190, 188, 187, 184, 183, 182, 178, 176,
- 166, 162, 161, 188, 160, 159, 158, 188, 202, 156,
- 202, 202, 202, 203, 203, 204, 204, 206, 206, 206,
- 155, 154, 153, 152, 149, 148, 147, 146, 145, 143,
- 142, 141, 139, 138, 137, 134, 133, 132, 131, 130,
- 129, 128, 126, 125, 124, 122, 121, 119, 118, 117,
- 116, 115, 113, 112, 110, 109, 107, 105, 103, 102,
- 101, 100, 98, 97, 96, 94, 93, 92, 86, 85,
- 84, 83, 82, 81, 80, 79, 78, 77, 75, 74,
- 73, 72, 71, 70, 68, 67, 66, 65, 64, 63,
- 62, 61, 60, 59, 58, 57, 49, 47, 46, 41,
+ 174, 170, 166, 193, 165, 164, 163, 193, 207, 162,
+ 207, 207, 207, 208, 208, 209, 209, 211, 211, 211,
+ 160, 159, 158, 157, 156, 155, 152, 151, 150, 149,
+ 148, 146, 145, 144, 142, 141, 140, 137, 136, 135,
+ 134, 133, 132, 131, 130, 128, 127, 126, 124, 123,
+ 121, 120, 119, 118, 117, 115, 114, 112, 111, 109,
+ 108, 106, 104, 103, 102, 101, 99, 98, 97, 95,
+ 94, 93, 87, 86, 85, 84, 83, 82, 81, 80,
+ 79, 78, 76, 75, 74, 73, 72, 71, 70, 68,
+ 67, 66, 65, 64, 63, 62, 61, 60, 59, 58,
- 40, 39, 38, 37, 34, 31, 28, 26, 17, 16,
- 7, 6, 5, 3, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201, 201,
- 201, 201, 201, 201, 201, 201, 201, 201, 201
+ 57, 49, 47, 46, 41, 40, 39, 38, 37, 34,
+ 31, 28, 26, 17, 16, 7, 6, 5, 3, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206, 206, 206, 206, 206, 206, 206,
+ 206, 206, 206, 206
} ;
/* Table of booleans, true if rule could match eol. */
-static const flex_int32_t yy_rule_can_match_eol[53] =
+static const flex_int32_t yy_rule_can_match_eol[54] =
{ 0,
0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
extern int yy_flex_debug;
int yy_flex_debug = 1;
-static const flex_int16_t yy_rule_linenum[52] =
+static const flex_int16_t yy_rule_linenum[53] =
{ 0,
106, 111, 117, 127, 133, 151, 158, 172, 173, 174,
175, 176, 177, 178, 179, 180, 181, 182, 183, 184,
185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
195, 196, 197, 198, 199, 200, 201, 202, 203, 204,
205, 206, 207, 208, 209, 210, 211, 212, 213, 214,
- 215
+ 215, 216
} ;
static yy_state_type *yy_state_buf=0, *yy_state_ptr=0;
@@ -1047,7 +1051,7 @@ namespace {
/* To avoid the call to exit... oops! */
#define YY_FATAL_ERROR(msg) isc::eval::EvalContext::fatal(msg)
-#line 1048 "lexer.cc"
+#line 1052 "lexer.cc"
/* noyywrap disables automatic rewinding for the next file to parse. Since we
always parse only a single string, there's no need to do any wraps. And
using yywrap requires linking with -lfl, which provides the default yywrap
@@ -1072,8 +1076,8 @@ namespace {
by moving it ahead by yyleng bytes. yyleng specifies the length of the
currently matched token. */
#define YY_USER_ACTION loc.columns(evalleng);
-#line 1073 "lexer.cc"
-#line 1074 "lexer.cc"
+#line 1077 "lexer.cc"
+#line 1078 "lexer.cc"
#define INITIAL 0
@@ -1378,7 +1382,7 @@ YY_DECL
-#line 1379 "lexer.cc"
+#line 1383 "lexer.cc"
while ( /*CONSTCOND*/1 ) /* loops until end-of-file is reached */
{
@@ -1406,14 +1410,14 @@ yy_match:
while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
{
yy_current_state = (int) yy_def[yy_current_state];
- if ( yy_current_state >= 202 )
+ if ( yy_current_state >= 207 )
yy_c = yy_meta[yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
*(yy_state_ptr)++ = yy_current_state;
++yy_cp;
}
- while ( yy_current_state != 201 );
+ while ( yy_current_state != 206 );
yy_find_action:
/* %% [10.0] code to find the action number goes here */
@@ -1476,13 +1480,13 @@ do_action: /* This label is used only to access EOF actions. */
{
if ( yy_act == 0 )
fprintf( stderr, "--scanner backing up\n" );
- else if ( yy_act < 52 )
+ else if ( yy_act < 53 )
fprintf( stderr, "--accepting rule at line %ld (\"%s\")\n",
(long)yy_rule_linenum[yy_act], yytext );
- else if ( yy_act == 52 )
+ else if ( yy_act == 53 )
fprintf( stderr, "--accepting default rule (\"%s\")\n",
yytext );
- else if ( yy_act == 53 )
+ else if ( yy_act == 54 )
fprintf( stderr, "--(end of buffer or a NUL)\n" );
else
fprintf( stderr, "--EOF (start condition %d)\n", YY_START );
@@ -1763,53 +1767,58 @@ return isc::eval::EvalParser::make_OR(loc);
case 44:
YY_RULE_SETUP
#line 208 "lexer.ll"
-return isc::eval::EvalParser::make_DOT(loc);
+return isc::eval::EvalParser::make_MEMBER(loc);
YY_BREAK
case 45:
YY_RULE_SETUP
#line 209 "lexer.ll"
-return isc::eval::EvalParser::make_LPAREN(loc);
+return isc::eval::EvalParser::make_DOT(loc);
YY_BREAK
case 46:
YY_RULE_SETUP
#line 210 "lexer.ll"
-return isc::eval::EvalParser::make_RPAREN(loc);
+return isc::eval::EvalParser::make_LPAREN(loc);
YY_BREAK
case 47:
YY_RULE_SETUP
#line 211 "lexer.ll"
-return isc::eval::EvalParser::make_LBRACKET(loc);
+return isc::eval::EvalParser::make_RPAREN(loc);
YY_BREAK
case 48:
YY_RULE_SETUP
#line 212 "lexer.ll"
-return isc::eval::EvalParser::make_RBRACKET(loc);
+return isc::eval::EvalParser::make_LBRACKET(loc);
YY_BREAK
case 49:
YY_RULE_SETUP
#line 213 "lexer.ll"
-return isc::eval::EvalParser::make_COMA(loc);
+return isc::eval::EvalParser::make_RBRACKET(loc);
YY_BREAK
case 50:
YY_RULE_SETUP
#line 214 "lexer.ll"
-return isc::eval::EvalParser::make_ANY(loc);
+return isc::eval::EvalParser::make_COMA(loc);
YY_BREAK
case 51:
YY_RULE_SETUP
#line 215 "lexer.ll"
-driver.error (loc, "Invalid character: " + std::string(evaltext));
- YY_BREAK
-case YY_STATE_EOF(INITIAL):
-#line 216 "lexer.ll"
-return isc::eval::EvalParser::make_END(loc);
+return isc::eval::EvalParser::make_ANY(loc);
YY_BREAK
case 52:
YY_RULE_SETUP
+#line 216 "lexer.ll"
+driver.error (loc, "Invalid character: " + std::string(evaltext));
+ YY_BREAK
+case YY_STATE_EOF(INITIAL):
#line 217 "lexer.ll"
+return isc::eval::EvalParser::make_END(loc);
+ YY_BREAK
+case 53:
+YY_RULE_SETUP
+#line 218 "lexer.ll"
ECHO;
YY_BREAK
-#line 1810 "lexer.cc"
+#line 1819 "lexer.cc"
case YY_END_OF_BUFFER:
{
@@ -2095,7 +2104,7 @@ static int yy_get_next_buffer (void)
while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
{
yy_current_state = (int) yy_def[yy_current_state];
- if ( yy_current_state >= 202 )
+ if ( yy_current_state >= 207 )
yy_c = yy_meta[yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
@@ -2123,11 +2132,11 @@ static int yy_get_next_buffer (void)
while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
{
yy_current_state = (int) yy_def[yy_current_state];
- if ( yy_current_state >= 202 )
+ if ( yy_current_state >= 207 )
yy_c = yy_meta[yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + yy_c];
- yy_is_jam = (yy_current_state == 201);
+ yy_is_jam = (yy_current_state == 206);
if ( ! yy_is_jam )
*(yy_state_ptr)++ = yy_current_state;
@@ -2893,7 +2902,7 @@ void yyfree (void * ptr )
/* %ok-for-header */
-#line 217 "lexer.ll"
+#line 218 "lexer.ll"
using namespace isc::eval;
diff --git a/src/lib/eval/lexer.ll b/src/lib/eval/lexer.ll
index b7447ea2bc..d990b013eb 100644
--- a/src/lib/eval/lexer.ll
+++ b/src/lib/eval/lexer.ll
@@ -204,6 +204,7 @@ addr6 [0-9a-fA-F]*\:[0-9a-fA-F]*\:[0-9a-fA-F:.]*
"not" return isc::eval::EvalParser::make_NOT(loc);
"and" return isc::eval::EvalParser::make_AND(loc);
"or" return isc::eval::EvalParser::make_OR(loc);
+"member" return isc::eval::EvalParser::make_MEMBER(loc);
"." return isc::eval::EvalParser::make_DOT(loc);
"(" return isc::eval::EvalParser::make_LPAREN(loc);
")" return isc::eval::EvalParser::make_RPAREN(loc);
diff --git a/src/lib/eval/location.hh b/src/lib/eval/location.hh
index 6f14287ee0..22ec0b9db3 100644
--- a/src/lib/eval/location.hh
+++ b/src/lib/eval/location.hh
@@ -1,4 +1,4 @@
-// Generated 201710061616
+// Generated 201804071317
// A Bison parser, made by GNU Bison 3.0.4.
// Locations for Bison parsers in C++
diff --git a/src/lib/eval/parser.cc b/src/lib/eval/parser.cc
index 2e73eaa1c1..639e76d223 100644
--- a/src/lib/eval/parser.cc
+++ b/src/lib/eval/parser.cc
@@ -255,44 +255,44 @@ namespace isc { namespace eval {
{
switch (that.type_get ())
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
value.move< TokenOption::RepresentationType > (that.value);
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
value.move< TokenPkt4::FieldType > (that.value);
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
value.move< TokenPkt6::FieldType > (that.value);
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
value.move< TokenPkt::MetadataType > (that.value);
break;
- case 66: // relay6_field
+ case 67: // relay6_field
value.move< TokenRelay6Field::FieldType > (that.value);
break;
- case 61: // nest_level
+ case 62: // nest_level
value.move< int8_t > (that.value);
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
value.move< std::string > (that.value);
break;
- case 59: // option_code
+ case 60: // option_code
value.move< uint16_t > (that.value);
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
value.move< uint32_t > (that.value);
break;
@@ -311,44 +311,44 @@ namespace isc { namespace eval {
state = that.state;
switch (that.type_get ())
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
value.copy< TokenOption::RepresentationType > (that.value);
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
value.copy< TokenPkt4::FieldType > (that.value);
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
value.copy< TokenPkt6::FieldType > (that.value);
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
value.copy< TokenPkt::MetadataType > (that.value);
break;
- case 66: // relay6_field
+ case 67: // relay6_field
value.copy< TokenRelay6Field::FieldType > (that.value);
break;
- case 61: // nest_level
+ case 62: // nest_level
value.copy< int8_t > (that.value);
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
value.copy< std::string > (that.value);
break;
- case 59: // option_code
+ case 60: // option_code
value.copy< uint16_t > (that.value);
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
value.copy< uint32_t > (that.value);
break;
@@ -388,100 +388,100 @@ namespace isc { namespace eval {
<< yysym.location << ": ";
switch (yytype)
{
- case 48: // "constant string"
+ case 49: // "constant string"
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 394 "parser.cc" // lalr1.cc:636
break;
- case 49: // "integer"
+ case 50: // "integer"
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 401 "parser.cc" // lalr1.cc:636
break;
- case 50: // "constant hexstring"
+ case 51: // "constant hexstring"
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 408 "parser.cc" // lalr1.cc:636
break;
- case 51: // "option name"
+ case 52: // "option name"
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 415 "parser.cc" // lalr1.cc:636
break;
- case 52: // "ip address"
+ case 53: // "ip address"
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< std::string > (); }
#line 422 "parser.cc" // lalr1.cc:636
break;
- case 58: // integer_expr
+ case 59: // integer_expr
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< uint32_t > (); }
#line 429 "parser.cc" // lalr1.cc:636
break;
- case 59: // option_code
+ case 60: // option_code
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< uint16_t > (); }
#line 436 "parser.cc" // lalr1.cc:636
break;
- case 60: // option_repr_type
+ case 61: // option_repr_type
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenOption::RepresentationType > (); }
#line 443 "parser.cc" // lalr1.cc:636
break;
- case 61: // nest_level
+ case 62: // nest_level
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< int8_t > (); }
#line 450 "parser.cc" // lalr1.cc:636
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenPkt::MetadataType > (); }
#line 457 "parser.cc" // lalr1.cc:636
break;
- case 63: // enterprise_id
+ case 64: // enterprise_id
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< uint32_t > (); }
#line 464 "parser.cc" // lalr1.cc:636
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenPkt4::FieldType > (); }
#line 471 "parser.cc" // lalr1.cc:636
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenPkt6::FieldType > (); }
#line 478 "parser.cc" // lalr1.cc:636
break;
- case 66: // relay6_field
+ case 67: // relay6_field
-#line 109 "parser.yy" // lalr1.cc:636
+#line 110 "parser.yy" // lalr1.cc:636
{ yyoutput << yysym.value.template as< TokenRelay6Field::FieldType > (); }
#line 485 "parser.cc" // lalr1.cc:636
break;
@@ -683,44 +683,44 @@ namespace isc { namespace eval {
when using variants. */
switch (yyr1_[yyn])
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
yylhs.value.build< TokenOption::RepresentationType > ();
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
yylhs.value.build< TokenPkt4::FieldType > ();
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
yylhs.value.build< TokenPkt6::FieldType > ();
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
yylhs.value.build< TokenPkt::MetadataType > ();
break;
- case 66: // relay6_field
+ case 67: // relay6_field
yylhs.value.build< TokenRelay6Field::FieldType > ();
break;
- case 61: // nest_level
+ case 62: // nest_level
yylhs.value.build< int8_t > ();
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
yylhs.value.build< std::string > ();
break;
- case 59: // option_code
+ case 60: // option_code
yylhs.value.build< uint16_t > ();
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
yylhs.value.build< uint32_t > ();
break;
@@ -742,7 +742,7 @@ namespace isc { namespace eval {
switch (yyn)
{
case 6:
-#line 129 "parser.yy" // lalr1.cc:859
+#line 130 "parser.yy" // lalr1.cc:859
{
TokenPtr neg(new TokenNot());
ctx.expression.push_back(neg);
@@ -751,7 +751,7 @@ namespace isc { namespace eval {
break;
case 7:
-#line 134 "parser.yy" // lalr1.cc:859
+#line 135 "parser.yy" // lalr1.cc:859
{
TokenPtr neg(new TokenAnd());
ctx.expression.push_back(neg);
@@ -760,7 +760,7 @@ namespace isc { namespace eval {
break;
case 8:
-#line 139 "parser.yy" // lalr1.cc:859
+#line 140 "parser.yy" // lalr1.cc:859
{
TokenPtr neg(new TokenOr());
ctx.expression.push_back(neg);
@@ -769,7 +769,7 @@ namespace isc { namespace eval {
break;
case 9:
-#line 144 "parser.yy" // lalr1.cc:859
+#line 145 "parser.yy" // lalr1.cc:859
{
TokenPtr eq(new TokenEqual());
ctx.expression.push_back(eq);
@@ -778,7 +778,7 @@ namespace isc { namespace eval {
break;
case 10:
-#line 149 "parser.yy" // lalr1.cc:859
+#line 150 "parser.yy" // lalr1.cc:859
{
TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), TokenOption::EXISTS));
ctx.expression.push_back(opt);
@@ -787,7 +787,7 @@ namespace isc { namespace eval {
break;
case 11:
-#line 154 "parser.yy" // lalr1.cc:859
+#line 155 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V4:
@@ -811,7 +811,7 @@ namespace isc { namespace eval {
break;
case 12:
-#line 174 "parser.yy" // lalr1.cc:859
+#line 175 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
@@ -829,7 +829,7 @@ namespace isc { namespace eval {
break;
case 13:
-#line 188 "parser.yy" // lalr1.cc:859
+#line 189 "parser.yy" // lalr1.cc:859
{
// Expression: vendor-class[1234].exists
//
@@ -842,7 +842,7 @@ namespace isc { namespace eval {
break;
case 14:
-#line 197 "parser.yy" // lalr1.cc:859
+#line 198 "parser.yy" // lalr1.cc:859
{
// Expression: vendor[1234].exists
//
@@ -855,7 +855,7 @@ namespace isc { namespace eval {
break;
case 15:
-#line 206 "parser.yy" // lalr1.cc:859
+#line 207 "parser.yy" // lalr1.cc:859
{
// Expression vendor[1234].option[123].exists
//
@@ -869,43 +869,62 @@ namespace isc { namespace eval {
break;
case 16:
-#line 218 "parser.yy" // lalr1.cc:859
+#line 217 "parser.yy" // lalr1.cc:859
+ {
+ // Expression member('foo')
+ //
+ // This token will check if the packet is a member of
+ // the specified client class.
+ // To avoid loops at evaluation only already defined and
+ // built-in classes are allowed.
+ std::string cc = yystack_[1].value.as< std::string > ();
+ if (!ctx.isClientClassDefined(cc)) {
+ error(yystack_[1].location, "Not defined client class '" + cc + "'");
+ }
+ TokenPtr member(new TokenMember(cc));
+ ctx.expression.push_back(member);
+ }
+#line 886 "parser.cc" // lalr1.cc:859
+ break;
+
+ case 17:
+#line 234 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(str);
}
-#line 876 "parser.cc" // lalr1.cc:859
+#line 895 "parser.cc" // lalr1.cc:859
break;
- case 17:
-#line 223 "parser.yy" // lalr1.cc:859
+ case 18:
+#line 239 "parser.yy" // lalr1.cc:859
{
TokenPtr hex(new TokenHexString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(hex);
}
-#line 885 "parser.cc" // lalr1.cc:859
+#line 904 "parser.cc" // lalr1.cc:859
break;
- case 18:
-#line 228 "parser.yy" // lalr1.cc:859
+ case 19:
+#line 244 "parser.yy" // lalr1.cc:859
{
TokenPtr ip(new TokenIpAddress(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(ip);
}
-#line 894 "parser.cc" // lalr1.cc:859
+#line 913 "parser.cc" // lalr1.cc:859
break;
- case 19:
-#line 233 "parser.yy" // lalr1.cc:859
+ case 20:
+#line 249 "parser.yy" // lalr1.cc:859
{
TokenPtr opt(new TokenOption(yystack_[3].value.as< uint16_t > (), yystack_[0].value.as< TokenOption::RepresentationType > ()));
ctx.expression.push_back(opt);
}
-#line 903 "parser.cc" // lalr1.cc:859
+#line 922 "parser.cc" // lalr1.cc:859
break;
- case 20:
-#line 238 "parser.yy" // lalr1.cc:859
+ case 21:
+#line 254 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V4:
@@ -925,11 +944,11 @@ namespace isc { namespace eval {
error(yystack_[5].location, "relay4 can only be used in DHCPv4.");
}
}
-#line 927 "parser.cc" // lalr1.cc:859
+#line 946 "parser.cc" // lalr1.cc:859
break;
- case 21:
-#line 259 "parser.yy" // lalr1.cc:859
+ case 22:
+#line 275 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
@@ -943,20 +962,20 @@ namespace isc { namespace eval {
error(yystack_[10].location, "relay6 can only be used in DHCPv6.");
}
}
-#line 945 "parser.cc" // lalr1.cc:859
+#line 964 "parser.cc" // lalr1.cc:859
break;
- case 22:
-#line 274 "parser.yy" // lalr1.cc:859
+ case 23:
+#line 290 "parser.yy" // lalr1.cc:859
{
TokenPtr pkt_metadata(new TokenPkt(yystack_[0].value.as< TokenPkt::MetadataType > ()));
ctx.expression.push_back(pkt_metadata);
}
-#line 954 "parser.cc" // lalr1.cc:859
+#line 973 "parser.cc" // lalr1.cc:859
break;
- case 23:
-#line 279 "parser.yy" // lalr1.cc:859
+ case 24:
+#line 295 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V4:
@@ -970,11 +989,11 @@ namespace isc { namespace eval {
error(yystack_[2].location, "pkt4 can only be used in DHCPv4.");
}
}
-#line 972 "parser.cc" // lalr1.cc:859
+#line 991 "parser.cc" // lalr1.cc:859
break;
- case 24:
-#line 293 "parser.yy" // lalr1.cc:859
+ case 25:
+#line 309 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
@@ -988,11 +1007,11 @@ namespace isc { namespace eval {
error(yystack_[2].location, "pkt6 can only be used in DHCPv6.");
}
}
-#line 990 "parser.cc" // lalr1.cc:859
+#line 1009 "parser.cc" // lalr1.cc:859
break;
- case 25:
-#line 307 "parser.yy" // lalr1.cc:859
+ case 26:
+#line 323 "parser.yy" // lalr1.cc:859
{
switch (ctx.getUniverse()) {
case Option::V6:
@@ -1006,38 +1025,38 @@ namespace isc { namespace eval {
error(yystack_[5].location, "relay6 can only be used in DHCPv6.");
}
}
-#line 1008 "parser.cc" // lalr1.cc:859
+#line 1027 "parser.cc" // lalr1.cc:859
break;
- case 26:
-#line 322 "parser.yy" // lalr1.cc:859
+ case 27:
+#line 338 "parser.yy" // lalr1.cc:859
{
TokenPtr sub(new TokenSubstring());
ctx.expression.push_back(sub);
}
-#line 1017 "parser.cc" // lalr1.cc:859
+#line 1036 "parser.cc" // lalr1.cc:859
break;
- case 27:
-#line 327 "parser.yy" // lalr1.cc:859
+ case 28:
+#line 343 "parser.yy" // lalr1.cc:859
{
TokenPtr conc(new TokenConcat());
ctx.expression.push_back(conc);
}
-#line 1026 "parser.cc" // lalr1.cc:859
+#line 1045 "parser.cc" // lalr1.cc:859
break;
- case 28:
-#line 332 "parser.yy" // lalr1.cc:859
+ case 29:
+#line 348 "parser.yy" // lalr1.cc:859
{
TokenPtr cond(new TokenIfElse());
ctx.expression.push_back(cond);
}
-#line 1035 "parser.cc" // lalr1.cc:859
+#line 1054 "parser.cc" // lalr1.cc:859
break;
- case 29:
-#line 337 "parser.yy" // lalr1.cc:859
+ case 30:
+#line 353 "parser.yy" // lalr1.cc:859
{
// expression: vendor.enterprise
//
@@ -1046,11 +1065,11 @@ namespace isc { namespace eval {
TokenPtr vendor(new TokenVendor(ctx.getUniverse(), 0, TokenVendor::ENTERPRISE_ID));
ctx.expression.push_back(vendor);
}
-#line 1048 "parser.cc" // lalr1.cc:859
+#line 1067 "parser.cc" // lalr1.cc:859
break;
- case 30:
-#line 346 "parser.yy" // lalr1.cc:859
+ case 31:
+#line 362 "parser.yy" // lalr1.cc:859
{
// expression: vendor-class.enterprise
//
@@ -1060,11 +1079,11 @@ namespace isc { namespace eval {
TokenVendor::ENTERPRISE_ID));
ctx.expression.push_back(vendor);
}
-#line 1062 "parser.cc" // lalr1.cc:859
+#line 1081 "parser.cc" // lalr1.cc:859
break;
- case 31:
-#line 356 "parser.yy" // lalr1.cc:859
+ case 32:
+#line 372 "parser.yy" // lalr1.cc:859
{
// This token will search for vendor option with
// specified enterprise-id. If found, will search
@@ -1073,11 +1092,11 @@ namespace isc { namespace eval {
TokenPtr opt(new TokenVendor(ctx.getUniverse(), yystack_[8].value.as< uint32_t > (), yystack_[0].value.as< TokenOption::RepresentationType > (), yystack_[3].value.as< uint16_t > ()));
ctx.expression.push_back(opt);
}
-#line 1075 "parser.cc" // lalr1.cc:859
+#line 1094 "parser.cc" // lalr1.cc:859
break;
- case 32:
-#line 365 "parser.yy" // lalr1.cc:859
+ case 33:
+#line 381 "parser.yy" // lalr1.cc:859
{
// expression: vendor-class[1234].data
//
@@ -1090,11 +1109,11 @@ namespace isc { namespace eval {
TokenVendor::DATA, 0));
ctx.expression.push_back(vendor_class);
}
-#line 1092 "parser.cc" // lalr1.cc:859
+#line 1111 "parser.cc" // lalr1.cc:859
break;
- case 33:
-#line 378 "parser.yy" // lalr1.cc:859
+ case 34:
+#line 394 "parser.yy" // lalr1.cc:859
{
// expression: vendor-class[1234].data[5]
//
@@ -1107,247 +1126,247 @@ namespace isc { namespace eval {
TokenVendor::DATA, index));
ctx.expression.push_back(vendor_class);
}
-#line 1109 "parser.cc" // lalr1.cc:859
+#line 1128 "parser.cc" // lalr1.cc:859
break;
- case 34:
-#line 391 "parser.yy" // lalr1.cc:859
+ case 35:
+#line 407 "parser.yy" // lalr1.cc:859
{
TokenPtr integer(new TokenInteger(yystack_[0].value.as< uint32_t > ()));
ctx.expression.push_back(integer);
}
-#line 1118 "parser.cc" // lalr1.cc:859
- break;
-
- case 35:
-#line 398 "parser.yy" // lalr1.cc:859
- {
- yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location);
- }
-#line 1126 "parser.cc" // lalr1.cc:859
+#line 1137 "parser.cc" // lalr1.cc:859
break;
case 36:
-#line 404 "parser.yy" // lalr1.cc:859
+#line 414 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as< std::string > (), yystack_[0].location);
+ yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
-#line 1134 "parser.cc" // lalr1.cc:859
+#line 1145 "parser.cc" // lalr1.cc:859
break;
case 37:
-#line 408 "parser.yy" // lalr1.cc:859
+#line 420 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< uint16_t > () = ctx.convertOptionName(yystack_[0].value.as< std::string > (), yystack_[0].location);
+ yylhs.value.as< uint16_t > () = ctx.convertOptionCode(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
-#line 1142 "parser.cc" // lalr1.cc:859
+#line 1153 "parser.cc" // lalr1.cc:859
break;
case 38:
-#line 414 "parser.yy" // lalr1.cc:859
+#line 424 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::TEXTUAL;
- }
-#line 1150 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< uint16_t > () = ctx.convertOptionName(yystack_[0].value.as< std::string > (), yystack_[0].location);
+ }
+#line 1161 "parser.cc" // lalr1.cc:859
break;
case 39:
-#line 418 "parser.yy" // lalr1.cc:859
+#line 430 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL;
+ yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::TEXTUAL;
}
-#line 1158 "parser.cc" // lalr1.cc:859
+#line 1169 "parser.cc" // lalr1.cc:859
break;
case 40:
-#line 424 "parser.yy" // lalr1.cc:859
+#line 434 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< int8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as< std::string > (), yystack_[0].location);
- }
-#line 1166 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< TokenOption::RepresentationType > () = TokenOption::HEXADECIMAL;
+ }
+#line 1177 "parser.cc" // lalr1.cc:859
break;
case 41:
-#line 433 "parser.yy" // lalr1.cc:859
+#line 440 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::IFACE;
- }
-#line 1174 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< int8_t > () = ctx.convertNestLevelNumber(yystack_[0].value.as< std::string > (), yystack_[0].location);
+ }
+#line 1185 "parser.cc" // lalr1.cc:859
break;
case 42:
-#line 437 "parser.yy" // lalr1.cc:859
+#line 449 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::SRC;
+ yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::IFACE;
}
-#line 1182 "parser.cc" // lalr1.cc:859
+#line 1193 "parser.cc" // lalr1.cc:859
break;
case 43:
-#line 441 "parser.yy" // lalr1.cc:859
+#line 453 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::DST;
+ yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::SRC;
}
-#line 1190 "parser.cc" // lalr1.cc:859
+#line 1201 "parser.cc" // lalr1.cc:859
break;
case 44:
-#line 445 "parser.yy" // lalr1.cc:859
+#line 457 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::LEN;
+ yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::DST;
}
-#line 1198 "parser.cc" // lalr1.cc:859
+#line 1209 "parser.cc" // lalr1.cc:859
break;
case 45:
-#line 451 "parser.yy" // lalr1.cc:859
+#line 461 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location);
- }
-#line 1206 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< TokenPkt::MetadataType > () = TokenPkt::LEN;
+ }
+#line 1217 "parser.cc" // lalr1.cc:859
break;
case 46:
-#line 455 "parser.yy" // lalr1.cc:859
+#line 467 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< uint32_t > () = 0;
+ yylhs.value.as< uint32_t > () = ctx.convertUint32(yystack_[0].value.as< std::string > (), yystack_[0].location);
}
-#line 1214 "parser.cc" // lalr1.cc:859
+#line 1225 "parser.cc" // lalr1.cc:859
break;
case 47:
-#line 461 "parser.yy" // lalr1.cc:859
+#line 471 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CHADDR;
- }
-#line 1222 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< uint32_t > () = 0;
+ }
+#line 1233 "parser.cc" // lalr1.cc:859
break;
case 48:
-#line 465 "parser.yy" // lalr1.cc:859
+#line 477 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HLEN;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CHADDR;
}
-#line 1230 "parser.cc" // lalr1.cc:859
+#line 1241 "parser.cc" // lalr1.cc:859
break;
case 49:
-#line 469 "parser.yy" // lalr1.cc:859
+#line 481 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HTYPE;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HLEN;
}
-#line 1238 "parser.cc" // lalr1.cc:859
+#line 1249 "parser.cc" // lalr1.cc:859
break;
case 50:
-#line 473 "parser.yy" // lalr1.cc:859
+#line 485 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CIADDR;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::HTYPE;
}
-#line 1246 "parser.cc" // lalr1.cc:859
+#line 1257 "parser.cc" // lalr1.cc:859
break;
case 51:
-#line 477 "parser.yy" // lalr1.cc:859
+#line 489 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::GIADDR;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::CIADDR;
}
-#line 1254 "parser.cc" // lalr1.cc:859
+#line 1265 "parser.cc" // lalr1.cc:859
break;
case 52:
-#line 481 "parser.yy" // lalr1.cc:859
+#line 493 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::YIADDR;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::GIADDR;
}
-#line 1262 "parser.cc" // lalr1.cc:859
+#line 1273 "parser.cc" // lalr1.cc:859
break;
case 53:
-#line 485 "parser.yy" // lalr1.cc:859
+#line 497 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::SIADDR;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::YIADDR;
}
-#line 1270 "parser.cc" // lalr1.cc:859
+#line 1281 "parser.cc" // lalr1.cc:859
break;
case 54:
-#line 489 "parser.yy" // lalr1.cc:859
+#line 501 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::MSGTYPE;
- }
-#line 1278 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::SIADDR;
+ }
+#line 1289 "parser.cc" // lalr1.cc:859
break;
case 55:
-#line 493 "parser.yy" // lalr1.cc:859
+#line 505 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::TRANSID;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::MSGTYPE;
}
-#line 1286 "parser.cc" // lalr1.cc:859
+#line 1297 "parser.cc" // lalr1.cc:859
break;
case 56:
-#line 499 "parser.yy" // lalr1.cc:859
+#line 509 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::MSGTYPE;
+ yylhs.value.as< TokenPkt4::FieldType > () = TokenPkt4::TRANSID;
}
-#line 1294 "parser.cc" // lalr1.cc:859
+#line 1305 "parser.cc" // lalr1.cc:859
break;
case 57:
-#line 503 "parser.yy" // lalr1.cc:859
+#line 515 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::TRANSID;
+ yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::MSGTYPE;
}
-#line 1302 "parser.cc" // lalr1.cc:859
+#line 1313 "parser.cc" // lalr1.cc:859
break;
case 58:
-#line 509 "parser.yy" // lalr1.cc:859
+#line 519 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::PEERADDR;
- }
-#line 1310 "parser.cc" // lalr1.cc:859
+ yylhs.value.as< TokenPkt6::FieldType > () = TokenPkt6::TRANSID;
+ }
+#line 1321 "parser.cc" // lalr1.cc:859
break;
case 59:
-#line 513 "parser.yy" // lalr1.cc:859
+#line 525 "parser.yy" // lalr1.cc:859
{
- yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::LINKADDR;
+ yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::PEERADDR;
}
-#line 1318 "parser.cc" // lalr1.cc:859
+#line 1329 "parser.cc" // lalr1.cc:859
break;
case 60:
-#line 519 "parser.yy" // lalr1.cc:859
+#line 529 "parser.yy" // lalr1.cc:859
+ {
+ yylhs.value.as< TokenRelay6Field::FieldType > () = TokenRelay6Field::LINKADDR;
+ }
+#line 1337 "parser.cc" // lalr1.cc:859
+ break;
+
+ case 61:
+#line 535 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(str);
}
-#line 1327 "parser.cc" // lalr1.cc:859
+#line 1346 "parser.cc" // lalr1.cc:859
break;
- case 61:
-#line 526 "parser.yy" // lalr1.cc:859
+ case 62:
+#line 542 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString(yystack_[0].value.as< std::string > ()));
ctx.expression.push_back(str);
}
-#line 1336 "parser.cc" // lalr1.cc:859
+#line 1355 "parser.cc" // lalr1.cc:859
break;
- case 62:
-#line 531 "parser.yy" // lalr1.cc:859
+ case 63:
+#line 547 "parser.yy" // lalr1.cc:859
{
TokenPtr str(new TokenString("all"));
ctx.expression.push_back(str);
}
-#line 1345 "parser.cc" // lalr1.cc:859
+#line 1364 "parser.cc" // lalr1.cc:859
break;
-#line 1349 "parser.cc" // lalr1.cc:859
+#line 1368 "parser.cc" // lalr1.cc:859
default:
break;
}
@@ -1602,163 +1621,165 @@ namespace isc { namespace eval {
}
- const signed char EvalParser::yypact_ninf_ = -110;
+ const signed char EvalParser::yypact_ninf_ = -114;
const signed char EvalParser::yytable_ninf_ = -1;
const short int
EvalParser::yypact_[] =
{
- 32, 39, 87, 29, 39, 39, 40, 51, 76, 30,
- 44, 92, 102, 111, 106, 90, 94, -110, -110, -110,
- -110, -110, 67, 35, -110, 104, 116, 117, 97, 105,
- -110, -110, 27, -110, -37, -37, 83, 47, -4, 87,
- 87, 39, 46, -36, 88, -36, 93, 39, 39, 87,
- -37, -37, 83, -36, -36, -110, -110, -110, 125, 126,
- -110, 127, -110, -110, -110, -110, -110, -110, -110, -110,
- -110, -110, -110, -110, -110, -110, -110, 108, 110, -3,
- -110, -110, -110, -110, -110, 129, -110, 131, -110, -110,
- 141, -110, 133, 134, 135, 136, 137, 138, 139, 140,
- 112, 87, 87, 142, 143, 144, 146, 147, 148, 149,
- 22, 65, 6, -110, 118, 153, 132, -14, 1, 109,
- 109, 54, 122, 159, -110, -110, -110, -110, -110, -110,
- 155, -110, -110, -110, -28, -110, 87, -110, 156, 157,
- -110, 158, 160, -37, -110, -110, 169, 171, 128, -37,
- -37, -37, 161, -110, -110, 163, 164, 165, 166, 167,
- -110, 168, 170, 172, 75, 84, 109, 109, -110, -110,
- -110, -110
+ 18, 25, 72, 32, 25, 25, 14, 26, 37, 70,
+ 80, 84, 99, 105, 110, 100, 33, 90, -114, -114,
+ -114, -114, -114, 38, 116, -114, 101, 121, 122, 117,
+ 118, -114, -114, 73, -114, -38, -38, 89, 91, 47,
+ 59, 72, 72, 25, 20, -31, 95, -31, 96, 25,
+ 25, 72, -38, -38, 89, -31, -31, -114, -114, -114,
+ 127, 128, -114, 129, 142, -114, -114, -114, -114, -114,
+ -114, -114, -114, -114, -114, -114, -114, -114, -114, -114,
+ 112, 113, -3, -114, -114, -114, -114, -114, 131, -114,
+ 134, -114, -114, 145, -114, 136, 137, 138, 139, 140,
+ 141, 143, 144, -114, 107, 72, 72, 146, 147, 148,
+ 149, 150, 151, 152, 5, 76, 13, -114, 123, 158,
+ 135, -14, 1, 66, 66, 29, 125, 163, -114, -114,
+ -114, -114, -114, -114, 159, -114, -114, -114, -30, -114,
+ 72, -114, 160, 161, -114, 162, 164, -38, -114, -114,
+ 169, 174, 130, -38, -38, -38, 165, -114, -114, 166,
+ 167, 168, 170, 171, -114, 172, 173, 175, 108, 111,
+ 66, 66, -114, -114, -114, -114
};
const unsigned char
EvalParser::yydefact_[] =
{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 16, 35, 17,
- 18, 2, 4, 0, 34, 0, 0, 0, 0, 0,
- 3, 1, 0, 6, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 17, 36,
+ 18, 19, 2, 4, 0, 35, 0, 0, 0, 0,
+ 0, 3, 1, 0, 6, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 5, 36, 37, 0, 0,
- 40, 0, 41, 42, 43, 44, 22, 47, 48, 49,
- 50, 51, 52, 53, 54, 55, 23, 0, 0, 0,
- 56, 57, 24, 46, 45, 0, 30, 0, 29, 7,
- 8, 9, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 60, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 38, 39, 10, 19, 11, 20,
- 0, 58, 59, 25, 0, 27, 0, 13, 32, 0,
- 14, 0, 0, 0, 62, 61, 0, 0, 0, 0,
- 0, 0, 0, 26, 28, 0, 0, 0, 0, 0,
- 33, 0, 0, 0, 0, 0, 0, 0, 12, 21,
- 15, 31
+ 0, 0, 0, 0, 0, 0, 0, 5, 37, 38,
+ 0, 0, 41, 0, 0, 42, 43, 44, 45, 23,
+ 48, 49, 50, 51, 52, 53, 54, 55, 56, 24,
+ 0, 0, 0, 57, 58, 25, 47, 46, 0, 31,
+ 0, 30, 7, 8, 9, 0, 0, 0, 0, 0,
+ 0, 0, 0, 16, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 61, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 39, 40,
+ 10, 20, 11, 21, 0, 59, 60, 26, 0, 28,
+ 0, 13, 33, 0, 14, 0, 0, 0, 63, 62,
+ 0, 0, 0, 0, 0, 0, 0, 27, 29, 0,
+ 0, 0, 0, 0, 34, 0, 0, 0, 0, 0,
+ 0, 0, 12, 22, 15, 32
};
const short int
EvalParser::yypgoto_[] =
{
- -110, -110, -110, 4, -2, -110, -34, -109, 130, -110,
- 8, -110, -110, -110, -110, -110
+ -114, -114, -114, 4, -2, -114, -35, -113, 133, -114,
+ -40, -114, -114, -114, -114, -114
};
const short int
EvalParser::yydefgoto_[] =
{
- -1, 3, 21, 22, 23, 24, 58, 127, 61, 66,
- 85, 76, 82, 133, 114, 146
+ -1, 3, 22, 23, 24, 25, 60, 131, 63, 69,
+ 88, 79, 85, 137, 118, 150
};
const unsigned char
EvalParser::yytable_[] =
{
- 30, 59, 129, 47, 48, 137, 144, 83, 32, 33,
- 139, 129, 56, 84, 57, 130, 92, 93, 131, 132,
- 140, 145, 67, 68, 69, 70, 71, 72, 73, 31,
- 138, 55, 102, 47, 48, 74, 75, 77, 78, 124,
- 125, 126, 4, 49, 5, 79, 37, 91, 6, 7,
- 8, 89, 90, 87, 34, 169, 171, 169, 171, 9,
- 38, 95, 96, 141, 10, 35, 131, 132, 62, 63,
- 64, 65, 11, 47, 48, 12, 13, 14, 1, 2,
- 15, 16, 124, 125, 128, 80, 81, 17, 18, 19,
- 36, 20, 124, 125, 168, 39, 25, 26, 27, 115,
- 116, 124, 125, 170, 43, 40, 44, 9, 45, 152,
- 46, 53, 10, 44, 41, 156, 157, 158, 50, 54,
- 11, 46, 42, 12, 13, 14, 124, 125, 28, 29,
- 51, 52, 60, 86, 147, 17, 18, 19, 88, 20,
- 97, 98, 99, 100, 103, 101, 104, 47, 105, 106,
- 107, 108, 109, 134, 110, 111, 112, 135, 117, 118,
- 119, 113, 120, 121, 122, 123, 138, 136, 142, 143,
- 148, 149, 150, 153, 151, 154, 159, 155, 160, 161,
- 162, 163, 94, 164, 165, 0, 166, 0, 167
+ 31, 61, 133, 49, 50, 148, 141, 90, 33, 34,
+ 143, 133, 58, 86, 59, 98, 99, 95, 96, 87,
+ 149, 144, 134, 128, 129, 130, 135, 136, 4, 35,
+ 5, 142, 32, 106, 6, 7, 8, 9, 145, 80,
+ 81, 36, 135, 136, 49, 50, 10, 82, 45, 94,
+ 46, 11, 37, 92, 93, 173, 175, 173, 175, 12,
+ 83, 84, 13, 14, 15, 1, 2, 16, 17, 65,
+ 66, 67, 68, 38, 18, 19, 20, 57, 21, 49,
+ 50, 26, 27, 28, 128, 129, 70, 71, 72, 73,
+ 74, 75, 76, 10, 128, 129, 132, 39, 11, 77,
+ 78, 40, 41, 119, 120, 47, 12, 48, 42, 13,
+ 14, 15, 156, 43, 29, 30, 52, 44, 160, 161,
+ 162, 18, 19, 20, 51, 21, 128, 129, 172, 128,
+ 129, 174, 55, 56, 46, 48, 53, 54, 151, 62,
+ 64, 89, 91, 100, 101, 102, 103, 107, 104, 105,
+ 108, 49, 109, 110, 111, 112, 113, 117, 114, 138,
+ 115, 116, 139, 121, 122, 123, 124, 125, 126, 127,
+ 142, 140, 146, 157, 147, 152, 153, 154, 158, 155,
+ 159, 163, 164, 165, 166, 0, 167, 97, 168, 169,
+ 170, 0, 171
};
const short int
EvalParser::yycheck_[] =
{
- 2, 35, 111, 6, 7, 19, 34, 43, 4, 5,
- 9, 120, 49, 49, 51, 9, 50, 51, 12, 13,
- 19, 49, 26, 27, 28, 29, 30, 31, 32, 0,
- 44, 4, 35, 6, 7, 39, 40, 39, 40, 17,
- 18, 19, 3, 8, 5, 41, 16, 49, 9, 10,
- 11, 47, 48, 45, 14, 164, 165, 166, 167, 20,
- 16, 53, 54, 9, 25, 14, 12, 13, 21, 22,
- 23, 24, 33, 6, 7, 36, 37, 38, 46, 47,
- 41, 42, 17, 18, 19, 39, 40, 48, 49, 50,
- 14, 52, 17, 18, 19, 3, 9, 10, 11, 101,
- 102, 17, 18, 19, 14, 3, 16, 20, 14, 143,
- 16, 14, 25, 16, 3, 149, 150, 151, 14, 14,
- 33, 16, 16, 36, 37, 38, 17, 18, 41, 42,
- 14, 14, 49, 45, 136, 48, 49, 50, 45, 52,
- 15, 15, 15, 35, 15, 35, 15, 6, 15, 15,
- 15, 15, 15, 35, 16, 16, 16, 4, 16, 16,
- 16, 49, 16, 16, 16, 16, 44, 35, 9, 14,
- 14, 14, 14, 4, 14, 4, 15, 49, 15, 15,
- 15, 15, 52, 16, 16, -1, 16, -1, 16
+ 2, 36, 115, 6, 7, 35, 20, 47, 4, 5,
+ 9, 124, 50, 44, 52, 55, 56, 52, 53, 50,
+ 50, 20, 9, 18, 19, 20, 13, 14, 3, 15,
+ 5, 45, 0, 36, 9, 10, 11, 12, 9, 41,
+ 42, 15, 13, 14, 6, 7, 21, 43, 15, 51,
+ 17, 26, 15, 49, 50, 168, 169, 170, 171, 34,
+ 40, 41, 37, 38, 39, 47, 48, 42, 43, 22,
+ 23, 24, 25, 3, 49, 50, 51, 4, 53, 6,
+ 7, 9, 10, 11, 18, 19, 27, 28, 29, 30,
+ 31, 32, 33, 21, 18, 19, 20, 17, 26, 40,
+ 41, 17, 3, 105, 106, 15, 34, 17, 3, 37,
+ 38, 39, 147, 3, 42, 43, 15, 17, 153, 154,
+ 155, 49, 50, 51, 8, 53, 18, 19, 20, 18,
+ 19, 20, 15, 15, 17, 17, 15, 15, 140, 50,
+ 49, 46, 46, 16, 16, 16, 4, 16, 36, 36,
+ 16, 6, 16, 16, 16, 16, 16, 50, 17, 36,
+ 17, 17, 4, 17, 17, 17, 17, 17, 17, 17,
+ 45, 36, 9, 4, 15, 15, 15, 15, 4, 15,
+ 50, 16, 16, 16, 16, -1, 16, 54, 17, 17,
+ 17, -1, 17
};
const unsigned char
EvalParser::yystos_[] =
{
- 0, 46, 47, 54, 3, 5, 9, 10, 11, 20,
- 25, 33, 36, 37, 38, 41, 42, 48, 49, 50,
- 52, 55, 56, 57, 58, 9, 10, 11, 41, 42,
- 57, 0, 56, 56, 14, 14, 14, 16, 16, 3,
- 3, 3, 16, 14, 16, 14, 16, 6, 7, 8,
- 14, 14, 14, 14, 14, 4, 49, 51, 59, 59,
- 49, 61, 21, 22, 23, 24, 62, 26, 27, 28,
- 29, 30, 31, 32, 39, 40, 64, 57, 57, 56,
- 39, 40, 65, 43, 49, 63, 45, 63, 45, 56,
- 56, 57, 59, 59, 61, 63, 63, 15, 15, 15,
- 35, 35, 35, 15, 15, 15, 15, 15, 15, 15,
- 16, 16, 16, 49, 67, 57, 57, 16, 16, 16,
- 16, 16, 16, 16, 17, 18, 19, 60, 19, 60,
- 9, 12, 13, 66, 35, 4, 35, 19, 44, 9,
- 19, 9, 9, 14, 34, 49, 68, 57, 14, 14,
- 14, 14, 59, 4, 4, 49, 59, 59, 59, 15,
- 15, 15, 15, 15, 16, 16, 16, 16, 19, 60,
- 19, 60
+ 0, 47, 48, 55, 3, 5, 9, 10, 11, 12,
+ 21, 26, 34, 37, 38, 39, 42, 43, 49, 50,
+ 51, 53, 56, 57, 58, 59, 9, 10, 11, 42,
+ 43, 58, 0, 57, 57, 15, 15, 15, 3, 17,
+ 17, 3, 3, 3, 17, 15, 17, 15, 17, 6,
+ 7, 8, 15, 15, 15, 15, 15, 4, 50, 52,
+ 60, 60, 50, 62, 49, 22, 23, 24, 25, 63,
+ 27, 28, 29, 30, 31, 32, 33, 40, 41, 65,
+ 58, 58, 57, 40, 41, 66, 44, 50, 64, 46,
+ 64, 46, 57, 57, 58, 60, 60, 62, 64, 64,
+ 16, 16, 16, 4, 36, 36, 36, 16, 16, 16,
+ 16, 16, 16, 16, 17, 17, 17, 50, 68, 58,
+ 58, 17, 17, 17, 17, 17, 17, 17, 18, 19,
+ 20, 61, 20, 61, 9, 13, 14, 67, 36, 4,
+ 36, 20, 45, 9, 20, 9, 9, 15, 35, 50,
+ 69, 58, 15, 15, 15, 15, 60, 4, 4, 50,
+ 60, 60, 60, 16, 16, 16, 16, 16, 17, 17,
+ 17, 17, 20, 61, 20, 61
};
const unsigned char
EvalParser::yyr1_[] =
{
- 0, 53, 54, 54, 55, 56, 56, 56, 56, 56,
- 56, 56, 56, 56, 56, 56, 57, 57, 57, 57,
- 57, 57, 57, 57, 57, 57, 57, 57, 57, 57,
- 57, 57, 57, 57, 57, 58, 59, 59, 60, 60,
- 61, 62, 62, 62, 62, 63, 63, 64, 64, 64,
- 64, 64, 64, 64, 64, 64, 65, 65, 66, 66,
- 67, 68, 68
+ 0, 54, 55, 55, 56, 57, 57, 57, 57, 57,
+ 57, 57, 57, 57, 57, 57, 57, 58, 58, 58,
+ 58, 58, 58, 58, 58, 58, 58, 58, 58, 58,
+ 58, 58, 58, 58, 58, 58, 59, 60, 60, 61,
+ 61, 62, 63, 63, 63, 63, 64, 64, 65, 65,
+ 65, 65, 65, 65, 65, 65, 65, 66, 66, 67,
+ 67, 68, 69, 69
};
const unsigned char
EvalParser::yyr2_[] =
{
0, 2, 2, 2, 1, 3, 2, 3, 3, 3,
- 6, 6, 11, 6, 6, 11, 1, 1, 1, 6,
- 6, 11, 3, 3, 3, 6, 8, 6, 8, 3,
- 3, 11, 6, 9, 1, 1, 1, 1, 1, 1,
+ 6, 6, 11, 6, 6, 11, 4, 1, 1, 1,
+ 6, 6, 11, 3, 3, 3, 6, 8, 6, 8,
+ 3, 3, 11, 6, 9, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1
+ 1, 1, 1, 1
};
@@ -1770,31 +1791,32 @@ namespace isc { namespace eval {
{
"\"end of file\"", "error", "$undefined", "\"(\"", "\")\"", "\"not\"",
"\"and\"", "\"or\"", "\"==\"", "\"option\"", "\"relay4\"", "\"relay6\"",
- "\"peeraddr\"", "\"linkaddr\"", "\"[\"", "\"]\"", "\".\"", "\"text\"",
- "\"hex\"", "\"exists\"", "\"pkt\"", "\"iface\"", "\"src\"", "\"dst\"",
- "\"len\"", "\"pkt4\"", "\"mac\"", "\"hlen\"", "\"htype\"", "\"ciaddr\"",
- "\"giaddr\"", "\"yiaddr\"", "\"siaddr\"", "\"substring\"", "\"all\"",
- "\",\"", "\"concat\"", "\"ifelse\"", "\"pkt6\"", "\"msgtype\"",
- "\"transid\"", "\"vendor-class\"", "\"vendor\"", "\"*\"", "\"data\"",
- "\"enterprise\"", "\"top-level bool\"", "\"top-level string\"",
- "\"constant string\"", "\"integer\"", "\"constant hexstring\"",
- "\"option name\"", "\"ip address\"", "$accept", "start", "expression",
- "bool_expr", "string_expr", "integer_expr", "option_code",
- "option_repr_type", "nest_level", "pkt_metadata", "enterprise_id",
- "pkt4_field", "pkt6_field", "relay6_field", "start_expr", "length_expr", YY_NULLPTR
+ "\"member\"", "\"peeraddr\"", "\"linkaddr\"", "\"[\"", "\"]\"", "\".\"",
+ "\"text\"", "\"hex\"", "\"exists\"", "\"pkt\"", "\"iface\"", "\"src\"",
+ "\"dst\"", "\"len\"", "\"pkt4\"", "\"mac\"", "\"hlen\"", "\"htype\"",
+ "\"ciaddr\"", "\"giaddr\"", "\"yiaddr\"", "\"siaddr\"", "\"substring\"",
+ "\"all\"", "\",\"", "\"concat\"", "\"ifelse\"", "\"pkt6\"",
+ "\"msgtype\"", "\"transid\"", "\"vendor-class\"", "\"vendor\"", "\"*\"",
+ "\"data\"", "\"enterprise\"", "\"top-level bool\"",
+ "\"top-level string\"", "\"constant string\"", "\"integer\"",
+ "\"constant hexstring\"", "\"option name\"", "\"ip address\"", "$accept",
+ "start", "expression", "bool_expr", "string_expr", "integer_expr",
+ "option_code", "option_repr_type", "nest_level", "pkt_metadata",
+ "enterprise_id", "pkt4_field", "pkt6_field", "relay6_field",
+ "start_expr", "length_expr", YY_NULLPTR
};
#if EVALDEBUG
const unsigned short int
EvalParser::yyrline_[] =
{
- 0, 118, 118, 119, 124, 127, 128, 133, 138, 143,
- 148, 153, 173, 187, 196, 205, 217, 222, 227, 232,
- 237, 258, 273, 278, 292, 306, 321, 326, 331, 336,
- 345, 355, 364, 377, 390, 397, 403, 407, 413, 417,
- 423, 432, 436, 440, 444, 450, 454, 460, 464, 468,
- 472, 476, 480, 484, 488, 492, 498, 502, 508, 512,
- 518, 525, 530
+ 0, 119, 119, 120, 125, 128, 129, 134, 139, 144,
+ 149, 154, 174, 188, 197, 206, 216, 233, 238, 243,
+ 248, 253, 274, 289, 294, 308, 322, 337, 342, 347,
+ 352, 361, 371, 380, 393, 406, 413, 419, 423, 429,
+ 433, 439, 448, 452, 456, 460, 466, 470, 476, 480,
+ 484, 488, 492, 496, 500, 504, 508, 514, 518, 524,
+ 528, 534, 541, 546
};
// Print the state stack on the debug stream.
@@ -1829,8 +1851,8 @@ namespace isc { namespace eval {
#line 14 "parser.yy" // lalr1.cc:1167
} } // isc::eval
-#line 1831 "parser.cc" // lalr1.cc:1167
-#line 537 "parser.yy" // lalr1.cc:1168
+#line 1853 "parser.cc" // lalr1.cc:1167
+#line 553 "parser.yy" // lalr1.cc:1168
void
isc::eval::EvalParser::error(const location_type& loc,
diff --git a/src/lib/eval/parser.h b/src/lib/eval/parser.h
index 5f2042f2bf..7b8614a2ad 100644
--- a/src/lib/eval/parser.h
+++ b/src/lib/eval/parser.h
@@ -366,47 +366,48 @@ namespace isc { namespace eval {
TOKEN_OPTION = 264,
TOKEN_RELAY4 = 265,
TOKEN_RELAY6 = 266,
- TOKEN_PEERADDR = 267,
- TOKEN_LINKADDR = 268,
- TOKEN_LBRACKET = 269,
- TOKEN_RBRACKET = 270,
- TOKEN_DOT = 271,
- TOKEN_TEXT = 272,
- TOKEN_HEX = 273,
- TOKEN_EXISTS = 274,
- TOKEN_PKT = 275,
- TOKEN_IFACE = 276,
- TOKEN_SRC = 277,
- TOKEN_DST = 278,
- TOKEN_LEN = 279,
- TOKEN_PKT4 = 280,
- TOKEN_CHADDR = 281,
- TOKEN_HLEN = 282,
- TOKEN_HTYPE = 283,
- TOKEN_CIADDR = 284,
- TOKEN_GIADDR = 285,
- TOKEN_YIADDR = 286,
- TOKEN_SIADDR = 287,
- TOKEN_SUBSTRING = 288,
- TOKEN_ALL = 289,
- TOKEN_COMA = 290,
- TOKEN_CONCAT = 291,
- TOKEN_IFELSE = 292,
- TOKEN_PKT6 = 293,
- TOKEN_MSGTYPE = 294,
- TOKEN_TRANSID = 295,
- TOKEN_VENDOR_CLASS = 296,
- TOKEN_VENDOR = 297,
- TOKEN_ANY = 298,
- TOKEN_DATA = 299,
- TOKEN_ENTERPRISE = 300,
- TOKEN_TOPLEVEL_BOOL = 301,
- TOKEN_TOPLEVEL_STRING = 302,
- TOKEN_STRING = 303,
- TOKEN_INTEGER = 304,
- TOKEN_HEXSTRING = 305,
- TOKEN_OPTION_NAME = 306,
- TOKEN_IP_ADDRESS = 307
+ TOKEN_MEMBER = 267,
+ TOKEN_PEERADDR = 268,
+ TOKEN_LINKADDR = 269,
+ TOKEN_LBRACKET = 270,
+ TOKEN_RBRACKET = 271,
+ TOKEN_DOT = 272,
+ TOKEN_TEXT = 273,
+ TOKEN_HEX = 274,
+ TOKEN_EXISTS = 275,
+ TOKEN_PKT = 276,
+ TOKEN_IFACE = 277,
+ TOKEN_SRC = 278,
+ TOKEN_DST = 279,
+ TOKEN_LEN = 280,
+ TOKEN_PKT4 = 281,
+ TOKEN_CHADDR = 282,
+ TOKEN_HLEN = 283,
+ TOKEN_HTYPE = 284,
+ TOKEN_CIADDR = 285,
+ TOKEN_GIADDR = 286,
+ TOKEN_YIADDR = 287,
+ TOKEN_SIADDR = 288,
+ TOKEN_SUBSTRING = 289,
+ TOKEN_ALL = 290,
+ TOKEN_COMA = 291,
+ TOKEN_CONCAT = 292,
+ TOKEN_IFELSE = 293,
+ TOKEN_PKT6 = 294,
+ TOKEN_MSGTYPE = 295,
+ TOKEN_TRANSID = 296,
+ TOKEN_VENDOR_CLASS = 297,
+ TOKEN_VENDOR = 298,
+ TOKEN_ANY = 299,
+ TOKEN_DATA = 300,
+ TOKEN_ENTERPRISE = 301,
+ TOKEN_TOPLEVEL_BOOL = 302,
+ TOKEN_TOPLEVEL_STRING = 303,
+ TOKEN_STRING = 304,
+ TOKEN_INTEGER = 305,
+ TOKEN_HEXSTRING = 306,
+ TOKEN_OPTION_NAME = 307,
+ TOKEN_IP_ADDRESS = 308
};
};
@@ -569,6 +570,10 @@ namespace isc { namespace eval {
symbol_type
make_RELAY6 (const location_type& l);
+ static inline
+ symbol_type
+ make_MEMBER (const location_type& l);
+
static inline
symbol_type
make_PEERADDR (const location_type& l);
@@ -938,12 +943,12 @@ namespace isc { namespace eval {
enum
{
yyeof_ = 0,
- yylast_ = 188, ///< Last index in yytable_.
+ yylast_ = 192, ///< Last index in yytable_.
yynnts_ = 16, ///< Number of nonterminal symbols.
- yyfinal_ = 31, ///< Termination state number.
+ yyfinal_ = 32, ///< Termination state number.
yyterror_ = 1,
yyerrcode_ = 256,
- yyntokens_ = 53 ///< Number of tokens.
+ yyntokens_ = 54 ///< Number of tokens.
};
@@ -990,9 +995,9 @@ namespace isc { namespace eval {
15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
- 45, 46, 47, 48, 49, 50, 51, 52
+ 45, 46, 47, 48, 49, 50, 51, 52, 53
};
- const unsigned int user_token_number_max_ = 307;
+ const unsigned int user_token_number_max_ = 308;
const token_number_type undef_token_ = 2;
if (static_cast(t) <= yyeof_)
@@ -1025,44 +1030,44 @@ namespace isc { namespace eval {
{
switch (other.type_get ())
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
value.copy< TokenOption::RepresentationType > (other.value);
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
value.copy< TokenPkt4::FieldType > (other.value);
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
value.copy< TokenPkt6::FieldType > (other.value);
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
value.copy< TokenPkt::MetadataType > (other.value);
break;
- case 66: // relay6_field
+ case 67: // relay6_field
value.copy< TokenRelay6Field::FieldType > (other.value);
break;
- case 61: // nest_level
+ case 62: // nest_level
value.copy< int8_t > (other.value);
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
value.copy< std::string > (other.value);
break;
- case 59: // option_code
+ case 60: // option_code
value.copy< uint16_t > (other.value);
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
value.copy< uint32_t > (other.value);
break;
@@ -1083,44 +1088,44 @@ namespace isc { namespace eval {
(void) v;
switch (this->type_get ())
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
value.copy< TokenOption::RepresentationType > (v);
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
value.copy< TokenPkt4::FieldType > (v);
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
value.copy< TokenPkt6::FieldType > (v);
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
value.copy< TokenPkt::MetadataType > (v);
break;
- case 66: // relay6_field
+ case 67: // relay6_field
value.copy< TokenRelay6Field::FieldType > (v);
break;
- case 61: // nest_level
+ case 62: // nest_level
value.copy< int8_t > (v);
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
value.copy< std::string > (v);
break;
- case 59: // option_code
+ case 60: // option_code
value.copy< uint16_t > (v);
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
value.copy< uint32_t > (v);
break;
@@ -1228,44 +1233,44 @@ namespace isc { namespace eval {
// Type destructor.
switch (yytype)
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
value.template destroy< TokenOption::RepresentationType > ();
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
value.template destroy< TokenPkt4::FieldType > ();
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
value.template destroy< TokenPkt6::FieldType > ();
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
value.template destroy< TokenPkt::MetadataType > ();
break;
- case 66: // relay6_field
+ case 67: // relay6_field
value.template destroy< TokenRelay6Field::FieldType > ();
break;
- case 61: // nest_level
+ case 62: // nest_level
value.template destroy< int8_t > ();
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
value.template destroy< std::string > ();
break;
- case 59: // option_code
+ case 60: // option_code
value.template destroy< uint16_t > ();
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
value.template destroy< uint32_t > ();
break;
@@ -1292,44 +1297,44 @@ namespace isc { namespace eval {
super_type::move(s);
switch (this->type_get ())
{
- case 60: // option_repr_type
+ case 61: // option_repr_type
value.move< TokenOption::RepresentationType > (s.value);
break;
- case 64: // pkt4_field
+ case 65: // pkt4_field
value.move< TokenPkt4::FieldType > (s.value);
break;
- case 65: // pkt6_field
+ case 66: // pkt6_field
value.move< TokenPkt6::FieldType > (s.value);
break;
- case 62: // pkt_metadata
+ case 63: // pkt_metadata
value.move< TokenPkt::MetadataType > (s.value);
break;
- case 66: // relay6_field
+ case 67: // relay6_field
value.move< TokenRelay6Field::FieldType > (s.value);
break;
- case 61: // nest_level
+ case 62: // nest_level
value.move< int8_t > (s.value);
break;
- case 48: // "constant string"
- case 49: // "integer"
- case 50: // "constant hexstring"
- case 51: // "option name"
- case 52: // "ip address"
+ case 49: // "constant string"
+ case 50: // "integer"
+ case 51: // "constant hexstring"
+ case 52: // "option name"
+ case 53: // "ip address"
value.move< std::string > (s.value);
break;
- case 59: // option_code
+ case 60: // option_code
value.move< uint16_t > (s.value);
break;
- case 58: // integer_expr
- case 63: // enterprise_id
+ case 59: // integer_expr
+ case 64: // enterprise_id
value.move< uint32_t > (s.value);
break;
@@ -1393,7 +1398,7 @@ namespace isc { namespace eval {
275, 276, 277, 278, 279, 280, 281, 282, 283, 284,
285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
- 305, 306, 307
+ 305, 306, 307, 308
};
return static_cast (yytoken_number_[type]);
}
@@ -1458,6 +1463,12 @@ namespace isc { namespace eval {
return symbol_type (token::TOKEN_RELAY6, l);
}
+ EvalParser::symbol_type
+ EvalParser::make_MEMBER (const location_type& l)
+ {
+ return symbol_type (token::TOKEN_MEMBER, l);
+ }
+
EvalParser::symbol_type
EvalParser::make_PEERADDR (const location_type& l)
{
@@ -1707,7 +1718,7 @@ namespace isc { namespace eval {
#line 14 "parser.yy" // lalr1.cc:377
} } // isc::eval
-#line 1711 "parser.h" // lalr1.cc:377
+#line 1722 "parser.h" // lalr1.cc:377
diff --git a/src/lib/eval/parser.yy b/src/lib/eval/parser.yy
index 96f8c52668..f9f0261627 100644
--- a/src/lib/eval/parser.yy
+++ b/src/lib/eval/parser.yy
@@ -1,4 +1,4 @@
-/* Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+/* Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -47,6 +47,7 @@ using namespace isc::eval;
OPTION "option"
RELAY4 "relay4"
RELAY6 "relay6"
+ MEMBER "member"
PEERADDR "peeraddr"
LINKADDR "linkaddr"
LBRACKET "["
@@ -212,6 +213,21 @@ bool_expr : "(" bool_expr ")"
TokenPtr exist(new TokenVendor(ctx.getUniverse(), $3, TokenOption::EXISTS, $8));
ctx.expression.push_back(exist);
}
+ | MEMBER "(" STRING ")"
+ {
+ // Expression member('foo')
+ //
+ // This token will check if the packet is a member of
+ // the specified client class.
+ // To avoid loops at evaluation only already defined and
+ // built-in classes are allowed.
+ std::string cc = $3;
+ if (!ctx.isClientClassDefined(cc)) {
+ error(@3, "Not defined client class '" + cc + "'");
+ }
+ TokenPtr member(new TokenMember(cc));
+ ctx.expression.push_back(member);
+ }
;
string_expr : STRING
diff --git a/src/lib/eval/position.hh b/src/lib/eval/position.hh
index 29b1748dfa..6ded477bdc 100644
--- a/src/lib/eval/position.hh
+++ b/src/lib/eval/position.hh
@@ -1,4 +1,4 @@
-// Generated 201710061616
+// Generated 201804071317
// A Bison parser, made by GNU Bison 3.0.4.
// Positions for Bison parsers in C++
diff --git a/src/lib/eval/stack.hh b/src/lib/eval/stack.hh
index fc91799529..e6755364e8 100644
--- a/src/lib/eval/stack.hh
+++ b/src/lib/eval/stack.hh
@@ -1,4 +1,4 @@
-// Generated 201710061616
+// Generated 201804071317
// A Bison parser, made by GNU Bison 3.0.4.
// Stack handling for Bison parsers in C++
diff --git a/src/lib/eval/tests/context_unittest.cc b/src/lib/eval/tests/context_unittest.cc
index fffc27f979..ab487cc138 100644
--- a/src/lib/eval/tests/context_unittest.cc
+++ b/src/lib/eval/tests/context_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2015-2017 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -390,6 +390,54 @@ public:
checkTokenRelay6Field(eval.expression.at(0), exp_level, exp_type);
}
+ /// @brief checks if the given token is a TokenMember with the
+ /// correct client class name.
+ /// @param token token to be checked
+ /// @param expected_client_class expected client class name
+ void checkTokenMember(const TokenPtr& token,
+ const std::string& expected_client_class) {
+ ASSERT_TRUE(token);
+ boost::shared_ptr member =
+ boost::dynamic_pointer_cast(token);
+ ASSERT_TRUE(member);
+
+ EXPECT_EQ(expected_client_class, member->getClientClass());
+ }
+
+ /// @rief This tests attempts to parse the expression then checks
+ /// if the number of tokens is correct and the TokenMember is as
+ /// expected.
+ ///
+ /// @param expr expression to be parsed
+ /// @param check_defined closure checking if the client class is defined
+ /// @param exp_client_class expected client class name to be parsed
+ /// @param exp_tokens expected number of tokens
+ void testMember(const std::string& expr,
+ EvalContext::CheckDefined check_defined,
+ const std::string& exp_client_class,
+ int exp_tokens) {
+ EvalContext eval(Option::V6, check_defined);
+
+ // parse the expression
+ try {
+ parsed_ = eval.parseString(expr);
+ }
+ catch (const EvalParseError& ex) {
+ FAIL() <<"Exception thrown: " << ex.what();
+ return;
+ }
+
+ // Parsing should succeed and return a token.
+ EXPECT_TRUE(parsed_);
+
+ // There should be the expected number of tokens.
+ ASSERT_EQ(exp_tokens, eval.expression.size());
+
+ // checked that the first token is TokenRelay6Field and that
+ // is has the correct attributes
+ checkTokenMember(eval.expression.at(0), exp_client_class);
+ }
+
/// @brief checks if the given token is a substring operator
void checkTokenSubstring(const TokenPtr& token) {
ASSERT_TRUE(token);
@@ -1071,6 +1119,31 @@ TEST_F(EvalContextTest, relay6FieldError) {
":1.8: Nest level invalid for DHCPv4 packets");
}
+// Tests parsing of member with defined class
+TEST_F(EvalContextTest, member) {
+ auto check_defined = [](const ClientClass& cc) { return (cc == "foo"); };
+ testMember("member('foo')", check_defined, "foo", 1);
+}
+
+// Test parsing of member with not defined class
+TEST_F(EvalContextTest, memberError) {
+ auto check_defined = [](const ClientClass& cc) { return (cc == "foo"); };
+ EvalContext eval(Option::V6, check_defined);
+ parsed_ = false;
+ try {
+ parsed_ = eval.parseString("member('bar')");
+ FAIL() << "Expected EvalParseError but nothing was raised";
+ }
+ catch (const EvalParseError& ex) {
+ EXPECT_EQ(":1.8-12: Not defined client class 'bar'",
+ std::string(ex.what()));
+ EXPECT_FALSE(parsed_);
+ }
+ catch (...) {
+ FAIL() << "Expected EvalParseError but something else was raised";
+ }
+}
+
// Test parsing of logical operators
TEST_F(EvalContextTest, logicalOps) {
// option.exists
@@ -1249,11 +1322,11 @@ TEST_F(EvalContextTest, scanErrors) {
// Typo should be handled as well.
checkError("subtring", ":1.1: Invalid character: s");
-
checkError("foo", ":1.1: Invalid character: f");
checkError(" bar", ":1.2: Invalid character: b");
checkError("relay[12].hex == 'foo'", ":1.1: Invalid character: r");
checkError("pkt4.ziaddr", ":1.6: Invalid character: z");
+ checkError("members('foo'", ":1.7: Invalid character: s");
}
// Tests some scanner/parser error cases
@@ -1428,6 +1501,9 @@ TEST_F(EvalContextTest, typeErrors) {
"expecting \",\"");
checkError("ifelse('foo'=='bar','foo','bar'=='bar')",
":1.32-33: syntax error, unexpected ==, expecting )");
+
+ // Member uses quotes around the client class name.
+ checkError("member(foo)", ":1.8: Invalid character: f");
}
diff --git a/src/lib/eval/tests/token_unittest.cc b/src/lib/eval/tests/token_unittest.cc
index f9fee2ccff..f74c13d3b5 100644
--- a/src/lib/eval/tests/token_unittest.cc
+++ b/src/lib/eval/tests/token_unittest.cc
@@ -2193,6 +2193,36 @@ TEST_F(TokenTest, operatorOrTrue) {
EXPECT_TRUE(checkFile());
}
+// This test verifies client class membership
+TEST_F(TokenTest, member) {
+
+ ASSERT_NO_THROW(t_.reset(new TokenMember("foo")));
+
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // the packet has no classes so false was left on the stack
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+ values_.pop();
+
+ // Add bar and retry
+ pkt4_->addClass("bar");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // the packet has a class but it is not foo
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("false", values_.top());
+ values_.pop();
+
+ // Add foo and retry
+ pkt4_->addClass("foo");
+ EXPECT_NO_THROW(t_->evaluate(*pkt4_, values_));
+
+ // Now the packet is in the foo class
+ ASSERT_EQ(1, values_.size());
+ EXPECT_EQ("true", values_.top());
+}
+
// This test verifies if expression vendor[4491].exists works properly in DHCPv4.
TEST_F(TokenTest, vendor4SpecificVendorExists) {
// Case 1: no option, should evaluate to false
diff --git a/src/lib/eval/token.cc b/src/lib/eval/token.cc
index 21fdbd1f6e..7225d4bb1f 100644
--- a/src/lib/eval/token.cc
+++ b/src/lib/eval/token.cc
@@ -708,6 +708,20 @@ TokenOr::evaluate(Pkt& /*pkt*/, ValueStack& values) {
.arg('\'' + values.top() + '\'');
}
+void
+TokenMember::evaluate(Pkt& pkt, ValueStack& values) {
+ if (pkt.inClass(client_class_)) {
+ values.push("true");
+ } else {
+ values.push("false");
+ }
+
+ // Log what we pushed
+ LOG_DEBUG(eval_logger, EVAL_DBG_STACK, EVAL_DEBUG_MEMBER)
+ .arg(client_class_)
+ .arg('\'' + values.top() + '\'');
+}
+
TokenVendor::TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr,
uint16_t option_code)
:TokenOption(option_code, repr), universe_(u), vendor_id_(vendor_id),
diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h
index 08c267e5b5..330bc60dfb 100644
--- a/src/lib/eval/token.h
+++ b/src/lib/eval/token.h
@@ -800,6 +800,40 @@ public:
void evaluate(Pkt& pkt, ValueStack& values);
};
+/// @brief Token that represents client class membership
+///
+/// For example "not member('foo')" is the complement of class foo
+class TokenMember : public Token {
+public:
+ /// @brief Constructor
+ ///
+ /// @param client_class client class name
+ TokenMember(const std::string& client_class)
+ :client_class_(client_class){
+ }
+
+ /// @brief Token evaluation (check if client_class_ was added to
+ /// packet client classes)
+ ///
+ /// @param pkt the class name will be check from this packet's client classes
+ /// @param values true (if found) or false (if not found) will be pushed here
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns client class name
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenMember with correct parameters.
+ ///
+ /// @return client class name the token expects to check membership.
+ const ClientClass& getClientClass() const {
+ return (client_class_);
+ }
+
+protected:
+ /// @brief The client class name
+ ClientClass client_class_;
+};
+
/// @brief Token that represents vendor options in DHCPv4 and DHCPv6.
///
/// It covers vendor independent vendor information option (125, DHCPv4)