Client Classification

Client classification chapter from the Kea Administratior Guide.

Client Classification
Client Classification Overview In certain cases it is useful to differentiate between different types of clients and treat them accordingly. Common reasons include: The clients represent different pieces of topology, e.g. a cable modem is different to the clients behind that modem. The clients have different behavior, e.g. a smart phone behaves differently to a laptop. The clients require different values for some options, e.g. a docsis3.0 cable modem requires different settings to docsis2.0 cable modem. Conversely, different clients can be grouped into a client class to get a common option. 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: 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/UNKNOWN builtin classes) 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 following its client class or global (or for option 43 last resort) definition. 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. 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 class and all classes of the host reservation. If a reservation is not found, the packet is assigned to UNKNOWN class. Classes with matching expressions using directly or indirectly the KNOWN/UNKNOWN builtin classes 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. The determination whether there is a reservation for a given client is made after a subnet is selected. As such, it is not possible to use KNOWN/UNKNOWN classes to select a shared network or a subnet. 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. 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. Care should be taken with client classification as it is easy for clients that do not meet class criteria to be denied any service altogether.
Builtin Client Classes 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". The "HA_" prefix is used by the High Availability hooks library to designate certain servers to process DHCP packets as a result of load balancing. The class name is constructed by prepending the "HA_" prefix to the name of the server which should process the DHCP packet. This server will use appropriate pool or subnet to allocate IP addresses (and/or prefixes) from, based on the assigned client classes. The details can be found in . 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, KNOWN and UNKNOWN, and prefixes VENDOR_CLASS_, HA_, 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. 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. Other issues, for example the starting position of a substring being outside of the substring or an option not existing in the packet, result in the operator returning an empty string. Expressions are a work in progress and the supported operators and values are limited. The expectation is that additional operators and values will be added over time, however the basic mechanisms will 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 or UNKNOWN classes. List of Classification Values Name Example expression Example value Description String literal 'example' 'example' A string Hexadecimal string literal 0x5a7d 'Z}' A hexadecimal string IP address literal 10.0.0.1 0x0a000001 An IP address Integer literal 123 '123' A 32 bit unsigned integer value Binary content of the option option[123].hex '(content of the option)' The value of the option with given code from the packet as hex Option existence option[123].exists 'true' 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 host reservation for the client "false" else "true" DHCPv4 relay agent sub-option relay4[123].hex '(content of the RAI sub-option)' The value of sub-option with given code from the DHCPv4 Relay Agent Information option (option 82) DHCPv6 Relay Options relay6[nest].option[code].hex (value of the option) The value of the option with code "code" from the relay encapsulation "nest" DHCPv6 Relay Peer Address relay6[nest].peeraddr 2001:DB8::1 The value of the peer address field from the relay encapsulation "nest" DHCPv6 Relay Link Address relay6[nest].linkaddr 2001:DB8::1 The value of the link address field from the relay encapsulation "nest" Interface name of packet pkt.iface eth0 The name of the incoming interface of a DHCP packet. Source address of packet pkt.src 10.1.2.3 The IP source address of a DHCP packet. Destination address of packet pkt.dst 10.1.2.3 The IP destination address of a DHCP packet. Length of packet pkt.len 513 The length of a DHCP packet (UDP header field), expressed as a 32 bit unsigned integer. Hardware address in DHCPv4 packet pkt4.mac 0x010203040506 The value of the chaddr field of the DHCPv4 packet, hlen (0 to 16) bytes Hardware length in DHCPv4 packet pkt4.hlen 6 The value of the hlen field of the DHCPv4 packet padded to 4 bytes Hardware type in DHCPv4 packet pkt4.htype 6 The value of the htype field of the DHCPv4 packet padded to 4 bytes ciaddr field in DHCPv4 packet pkt4.ciaddr 192.0.2.1 The value of the ciaddr field of the DHCPv4 packet (IPv4 address, 4 bytes) giaddr field in DHCPv4 packet pkt4.giaddr 192.0.2.1 The value of the giaddr field of the DHCPv4 packet (IPv4 address, 4 bytes) yiaddr field in DHCPv4 packet pkt4.yiaddr 192.0.2.1 The value of the yiaddr field of the DHCPv4 packet (IPv4 address, 4 bytes) siaddr field in DHCPv4 packet pkt4.siaddr 192.0.2.1 The value of the siaddr field of the DHCPv4 packet (IPv4 address, 4 bytes) Message Type in DHCPv4 packet pkt4.msgtype 1 The value of the message type field in the DHCPv4 packet (expressed as a 32 bit unsigned integer). Transaction ID (xid) in DHCPv4 packet pkt4.transid 12345 The value of the transaction id in the DHCPv4 packet (expressed as a 32 bit unsigned integer). Message Type in DHCPv6 packet pkt6.msgtype 1 The value of the message type field in the DHCPv6 packet (expressed as a 32 bit unsigned integer). Transaction ID in DHCPv6 packet pkt6.transid 12345 The value of the transaction id in the DHCPv6 packet (expressed as a 32 bit unsigned integer). Vendor option existence (any vendor) vendor[*].exists true Returns whether a vendor option from any vendor is present ('true') or absent ('false'). Vendor option existence (specific vendor) vendor[4491].exists true Returns whether a vendor option from specified vendor (determined by its enterprise-id) is present ('true') or absent ('false'). Enterprise-id from vendor option vendor.enterprise 4491 If the vendor option is present, it returns the value of the enterprise-id field padded to 4 bytes. Returns "" otherwise. Vendor sub-option existence vendor[4491].option[1].exists true Returns 'true' if there is vendor option with specified enterprise-id and given sub-option is present. Returns 'false' otherwise. Vendor sub-option content vendor[4491].option[1].hex docsis3.0 Returns content of the specified sub-option of a vendor option with specified enterprise id. Returns '' if no such option or sub-option is present. Vendor class option existence (any vendor) vendor-class[*].exists true Returns whether a vendor class option from any vendor is present ('true') or absent ('false'). Vendor class option existence (specific vendor) vendor-class[4491].exists true Returns whether a vendor class option from specified vendor (determined by its enterprise-id) is present ('true') or absent ('false'). Enterprise-id from vendor class option vendor-class.enterprise 4491 If the vendor option is present, it returns the value of the enterprise-id field padded to 4 bytes. Returns "" otherwise. First data chunk from vendor class option vendor-class[4491].data docsis3.0 Returns content of the first data chunk from the vendor class option with specified enterprise-id. Returns "" if missing. Specific data chunk from vendor class option vendor-class[4491].data[3] docsis3.0 Returns content of the specified data chunk of a vendor class option with specified enterprise id. Returns '' if no such option or data chunk is present.
Notes:
Hexadecimal strings are converted into a string as expected. The starting "0X" or "0x" is removed and if the string is an odd number of characters a "0" is prepended to it. IP addresses are converted into strings of length 4 or 16. IPv4, IPv6, and IPv4 embedded IPv6 (e.g., IPv4 mapped IPv6) addresses are supported. Integers in an expression are converted to 32 bit unsigned integers and are represented as four-byte strings. For example 123 is represented as 0x0000007b. All expressions that return numeric values use 32-bit unsigned integers, even if the field in the packet is smaller. In general it is easier to use decimal notation to represent integers, but it is also possible to use hex notation. When using hex notation to represent an integer care should be taken to make sure the value is represented as 32 bits, e.g. use 0x00000001 instead of 0x1 or 0x01. Also, make sure the value is specified in network order, e.g. 1 is represented as 0x00000001. "option[code].hex" extracts the value of the option with the code "code" from the incoming packet. If the packet doesn't contain the option, it returns the empty string. The string is presented as a byte string of the option payload without the type code or length fields. "option[code].exists" checks if an option with the code "code" is present 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", "UNKNOWN"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" or "UNKNOWN" partition 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 (82) option. If the packet doesn't contain a RAI option, or the RAI option doesn't contain the requested sub-option, the expression returns an empty string. The string is presented as a byte string of the option payload without the type code or length fields. This expression is allowed in DHCPv4 only. "relay4" shares the same representation types as "option", for instance "relay4[code].exists" is supported. "relay6[nest]" allows access to the encapsulations used by any DHCPv6 relays that forwarded the packet. The "nest" level specifies the relay from which to extract the information, with a value of 0 indicating the relay closest to the DHCPv6 server. Negative values allow to specify relays counted from the DHCPv6 client, -1 indicating the relay closest to the client. In general negative "nest" level is the same as the number of relays + "nest" level. If the requested encapsulation doesn't exist an empty string "" is returned. This expression is allowed in DHCPv6 only. "relay6[nest].option[code]" shares the same representation types as "option", for instance "relay6[nest].option[code].exists" is supported. Expressions starting with "pkt4" can be used only in DHCPv4. They allows access to DHCPv4 message fields. "pkt6" refers to information from the client request. To access any information from an intermediate relay use "relay6". "pkt6.msgtype" and "pkt6.transid" output a 4 byte binary string for the message type or transaction id. For example the message type SOLICIT will be "0x00000001" or simply 1 as in "pkt6.msgtype == 1". Vendor option means Vendor-Identifying Vendor-specific Information option in DHCPv4 (code 125, see Section 4 of RFC 3925) and Vendor-specific Information Option in DHCPv6 (code 17, defined in Section 22.17 of RFC 3315). Vendor class option means Vendor-Identifying Vendor Class Option in DHCPv4 (code 124, see Section 3 of RFC 3925) in DHCPv4 and Class Option in DHCPv6 (code 16, see Section 22.16 of RFC 3315). Vendor options may have sub-options that are referenced by their codes. Vendor class options do not have sub-options, but rather data chunks, which are referenced by index value. Index 0 means the first data chunk, Index 1 is for the second data chunk (if present), etc. In the vendor and vendor-class constructs Asterisk (*) or 0 can be used to specify a wildcard enterprise-id value, i.e. it will match any enterprise-id value. Vendor Class Identifier (option 60 in DHCPv4) can be accessed using option[60] expression. RFC3925 and RFC3315 allow for multiple instances of vendor options to appear in a single message. The client classification code currently examines the first instance if more than one appear. For vendor.enterprise and vendor-class.enterprise expressions, the value from the first instance is returned. Please submit a feature request on Kea website if you need support for multiple instances. List of Classification Expressions Name Example Description Equal 'foo' == 'bar'Compare the two values and return "true" or "false"Not not ('foo' == 'bar')Logical negationAnd ('foo' == 'bar') and ('bar' == 'foo')Logical andOr ('foo' == 'bar') or ('bar' == 'foo')Logical orSubstringsubstring('foobar',0,3)Return the requested substringConcatconcat('foo','bar')Return the concatenation of the stringsIfelseifelse('foo' == 'bar','us','them')Return the branch value according to the conditionHexstringhexstring('foo', '-')Converts the value to a hexadecimal string, e.g. 0a:1b:2c:3e
Logical operators The Not, And and Or logical operators are the common operators. Not has the highest precedence and Or the lowest. And and Or are (left) associative, parentheses around a logical expression can be used to enforce a specific grouping, for instance in "A and (B or C)" (without parentheses "A and B or C" means "(A and B) or C").
Substring The substring operator "substring(value, start, length)" accepts both positive and negative values for the starting position and the length. For "start", a value of 0 is the first byte in the string while -1 is the last byte. If the starting point is outside of the original string an empty string is returned. "length" is the number of bytes to extract. A negative number means to count towards the beginning of the string but doesn't include the byte pointed to by "start". The special value "all" means to return all bytes from start to the end of the string. If length is longer than the remaining portion of the string then the entire remaining portion is returned. Some examples may be helpful: substring('foobar', 0, 6) == 'foobar' substring('foobar', 3, 3) == 'bar' substring('foobar', 3, all) == 'bar' substring('foobar', 1, 4) == 'ooba' substring('foobar', -5, 4) == 'ooba' substring('foobar', -1, -3) == 'oba' substring('foobar', 4, -2) == 'ob' substring('foobar', 10, 2) == ''
Concat The concat function "concat(string1, string2)" returns the concatenation of its two arguments. For instance: concat('foo', 'bar') == 'foobar'
Ifelse The ifelse function "ifelse(cond, iftrue, ifelse)" returns the "iftrue" or "ifelse" branch value following the boolean condition "cond". For instance: ifelse(option[230].exists, option[230].hex, 'none')
Hexstring The hexstring function "hexstring(binary, separator)" returns the binary value as its hexadecimal string representation: pairs of hexadecimal digits separated by the separator, e.g ':', '-', '' (empty separator). hexstring(pkt4.mac, ':')
The expression for each class is executed on each packet received. If the expressions are overly complex, the time taken to execute them may impact the performance of the server. If you need complex or time consuming expressions you should write a hook to perform the necessary work.
Configuring Classes 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, option data and definition, and only-if-required flag are optional. The test expression is a string containing the logical expression used to determine membership in the class. The entire expression is in double quotes. The option data is a list which defines any options that should be assigned to members of this class. 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. "Dhcp4": { "client-classes": [ { "name": "Client_foo", "test": "substring(option[61].hex,0,3) == 'foo'", "option-data": [ { "name": "domain-name-servers", "code": 6, "space": "dhcp4", "csv-format": true, "data": "192.0.2.1, 192.0.2.2" } ] }, ... ], ... } This example shows a client class being defined for use by the DHCPv6 server. In it the class named "Client_enterprise" is defined. It is comprised of all clients who's client identifiers start with the given hex string (which would indicate a DUID based on an enterprise id of 0xAABBCCDD). Members of this class will be given an 2001:db8:0::1 and 2001:db8:2::1 as their domain name servers. "Dhcp6": { "client-classes": [ { "name": "Client_enterprise", "test": "substring(option[1].hex,0,6) == 0x0002AABBCCDD'", "option-data": [ { "name": "dns-servers", "code": 23, "space": "dhcp6", "csv-format": true, "data": "2001:db8:0::1, 2001:db8:2::1" } ] }, ... ], ... }
Using Static Host Reservations In Classification Classes can be statically assigned to the clients using techniques described in and .
Configuring Subnets With Class Information In certain cases it beneficial to restrict access to certain subnets only to clients that belong to a given class, using the "client-class" keyword when defining the subnet. Let's assume that the server is connected to a network segment that uses the 192.0.2.0/24 prefix. The Administrator of that network has decided that addresses from range 192.0.2.10 to 192.0.2.20 are going to be managed by the DHCP4 server. Only clients belonging to client class Client_foo are allowed to use this subnet. Such a configuration can be achieved in the following way: "Dhcp4": { "client-classes": [ { "name": "Client_foo", "test": "substring(option[61].hex,0,3) == 'foo'", "option-data": [ { "name": "domain-name-servers", "code": 6, "space": "dhcp4", "csv-format": true, "data": "192.0.2.1, 192.0.2.2" } ] }, ... ], "subnet4": [ { "subnet": "192.0.2.0/24", "pools": [ { "pool": "192.0.2.10 - 192.0.2.20" } ], "client-class": "Client_foo" }, ... ],, ... } The following example shows restricting access to a DHCPv6 subnet. This configuration will restrict use of the addresses 2001:db8:1::1 to 2001:db8:1::FFFF to members of the "Client_enterprise" class. "Dhcp6": { "client-classes": [ { "name": "Client_enterprise", "test": "substring(option[1].hex,0,6) == 0x0002AABBCCDD'", "option-data": [ { "name": "dns-servers", "code": 23, "space": "dhcp6", "csv-format": true, "data": "2001:db8:0::1, 2001:db8:2::1" } ] }, ... ], "subnet6": [ { "subnet": "2001:db8:1::/64", "pools": [ { "pool": "2001:db8:1::-2001:db8:1::ffff" } ], "client-class": "Client_enterprise" } ], ... }
Configuring Pools With Class Information 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 that uses the 192.0.2.0/24 prefix. The Administrator of that network has decided that addresses from range 192.0.2.10 to 192.0.2.20 are going to be managed by the DHCP4 server. Only clients belonging to client class Client_foo are allowed to use this pool. Such a configuration can be achieved in the following way: "Dhcp4": { "client-classes": [ { "name": "Client_foo", "test": "substring(option[61].hex,0,3) == 'foo'", "option-data": [ { "name": "domain-name-servers", "code": 6, "space": "dhcp4", "csv-format": true, "data": "192.0.2.1, 192.0.2.2" } ] }, ... ], "subnet4": [ { "subnet": "192.0.2.0/24", "pools": [ { "pool": "192.0.2.10 - 192.0.2.20", "client-class": "Client_foo" } ] }, ... ],, } The following example shows restricting access to an address pool. This configuration will restrict use of the addresses 2001:db8:1::1 to 2001:db8:1::FFFF to members of the "Client_enterprise" class. "Dhcp6": { "client-classes": [ { "name": "Client_enterprise_", "test": "substring(option[1].hex,0,6) == 0x0002AABBCCDD'", "option-data": [ { "name": "dns-servers", "code": 23, "space": "dhcp6", "csv-format": true, "data": "2001:db8:0::1, 2001:db8:2::1" } ] }, ... ], "subnet6": [ { "subnet": "2001:db8:1::/64", "pools": [ { "pool": "2001:db8:1::-2001:db8:1::ffff", "client-class": "Client_foo" } ] }, ... ], ... }
Using Classes Currently classes can be used for two functions. They can supply options to the members of the class and they can be used to choose a subnet from which an address will be assigned to the class member. When supplying options, options defined as part of the class definition are considered "class globals". They will override any global options that may be defined and in turn will be overridden by any options defined for an individual subnet.
Classes and Hooks You may use a hook to classify your packets. This may be useful if the expression would either be complex or time consuming and be easier or better to write as code. Once the hook has added the proper class name to the packet the rest of the classification system will work as normal in choosing a subnet and selecting options. For a description of hooks see , for a description on configuring classes see and .
Debugging Expressions While you are constructing your classification expressions you may find it useful to enable logging see for a more complete description of the logging facility. To enable the debug statements in the classification system you will need to set the severity to "DEBUG" and the debug level to at least 55. The specific loggers are "kea-dhcp4.eval" and "kea-dhcp6.eval". In order to understand the logging statements, one must understand a bit about how expressions are evaluated; for a more complete description refer to the design document at https://gitlab.isc.org/isc-projects/kea/wikis/design%20documents. In brief there are two structures used during the evaluation of an expression: a list of tokens which represent the expressions and a value stack which represents the values being manipulated. The list of tokens is created when the configuration file is processed with most expressions and values being converted to a token. The list is organized in reverse Polish notation. During execution, the list will be traversed in order. As each token is executed it will be able to pop values from the top of the stack and eventually push its result on the top of the stack. Imagine the following expression: "test": "substring(option[61].hex,0,3) == 'foo'", This will result in the following tokens: option, number (0), number (3), substring, text ('foo'), equals In this example the first three tokens will simply push values onto the stack. The substring token will then remove those three values and compute a result that it places on the stack. The text option also places a value on the stack and finally the equals token removes the two tokens on the stack and places its result on the stack. When debug logging is enabled, each time a token is evaluated it will emit a log message indicating the values of any objects that were popped off of the value stack and any objects that were pushed onto the value stack. The values will be displayed as either text if the command is known to use text values or hexadecimal if the command either uses binary values or can manipulate either text or binary values. For expressions that pop multiple values off the stack, the values will be displayed in the order they were popped. For most expressions this won't matter but for the concat expression the values are displayed in reverse order from how they are written in the expression. Let us assume that the following test has been entered into the configuration. This example skips most of the configuration to concentrate on the test. "test": "substring(option[61].hex,0,3) == 'foo'", The logging might then resemble this: 2016-05-19 13:35:04.163 DEBUG [kea.eval/44478] EVAL_DEBUG_OPTION Pushing option 61 with value 0x666F6F626172 2016-05-19 13:35:04.164 DEBUG [kea.eval/44478] EVAL_DEBUG_STRING Pushing text string '0' 2016-05-19 13:35:04.165 DEBUG [kea.eval/44478] EVAL_DEBUG_STRING Pushing text string '3' 2016-05-19 13:35:04.166 DEBUG [kea.eval/44478] EVAL_DEBUG_SUBSTRING Popping length 3, start 0, string 0x666F6F626172 pushing result 0x666F6F 2016-05-19 13:35:04.167 DEBUG [kea.eval/44478] EVAL_DEBUG_STRING Pushing text string 'foo' 2016-05-19 13:35:04.168 DEBUG [kea.eval/44478] EVAL_DEBUG_EQUAL Popping 0x666F6F and 0x666F6F pushing result 'true' The debug logging may be quite verbose if you have a number of expressions to evaluate. It is intended as an aid in helping you create and debug your expressions. You should plan to disable debug logging when you have your expressions working correctly. You also may wish to include only one set of expressions at a time in the configuration file while debugging them in order to limit the log statements. For example when adding a new set of expressions you might find it more convenient to create a configuration file that only includes the new expressions until you have them working correctly and then add the new set to the main configuration file.