diff options
Diffstat (limited to 'doc/sphinx/arm/classify.rst')
-rw-r--r-- | doc/sphinx/arm/classify.rst | 1265 |
1 files changed, 1265 insertions, 0 deletions
diff --git a/doc/sphinx/arm/classify.rst b/doc/sphinx/arm/classify.rst new file mode 100644 index 0000000..7f011ec --- /dev/null +++ b/doc/sphinx/arm/classify.rst @@ -0,0 +1,1265 @@ +.. _classify: + +********************* +Client Classification +********************* + +Client Classification Overview +============================== + +In certain cases it is useful to differentiate among different types +of clients and treat them accordingly. Common reasons include: + +- The clients represent different pieces of topology, e.g. a cable + modem is not the same as the clients behind that modem. + +- The clients have different behavior, e.g. a smartphone behaves + differently from a laptop. + +- The clients require different values for some options, e.g. a + docsis3.0 cable modem requires different settings from a docsis2.0 + cable modem. + +To make management easier, different clients can be grouped into a +client class to receive common options. + +An incoming packet can be associated with a client class in several +ways: + +- Implicitly, using a vendor class option or another built-in condition. + +- Using an expression which evaluates to ``true``. + +- Using static host reservations, a shared network, a subnet, etc. + +- Using a hook. + +Client classification can be used to change the behavior of almost any +part of the DHCP message processing. There are currently nine +mechanisms that take advantage of client classification: + +- Dropping queries. + +- Subnet selection. + +- Pool selection. + +- Lease limiting. + +- Rate limiting. + +- DDNS tuning. + +- Defining DHCPv4 private (codes 224-254) and code 43 options. + +- Assigning different options. + +- Setting specific options for use with the TFTP server address + and the boot file field for DHCPv4 cable modems. + +.. _classify-classification-steps: + +Classification Steps +-------------------- + +The classification process is conducted in several steps: + +1. The ``ALL`` class is associated with the incoming packet. + +2. Vendor class options are processed. + +3. Classes with matching expressions and not marked for later evaluation ("on + request" or depending on the ``KNOWN``/``UNKNOWN`` built-in classes) + are processed in the order they are defined in the + configuration; the boolean expression is evaluated and, if it + returns ``true`` (a match), the incoming packet is associated with the + class. + +4. If a private or code 43 DHCPv4 option is received, it is decoded + following its client-class or global (or, for option 43, + last-resort) definition. + +5. When the incoming packet belongs to the special class ``DROP``, it is + dropped and an informational message is logged with the packet + information. + +.. note:: + + The ``pkt4_receive`` and ``pkt6_receive`` callouts are called here. + +6. When the ``early-global-reservations-lookup`` global parameter is + configured to ``true``, the process looks up global reservations and + partially performs steps 8, 9, and 10. The lookup is limited to + global reservations; if one is found the ``KNOWN`` class is set, + but if none is found the ``UNKNOWN`` class is **not** set. + +7. A subnet is chosen, possibly based on the class information when + some subnets are reserved. More precisely: when choosing a subnet, + the server iterates over all of the subnets that are feasible given + the information found in the packet (client address, relay address, + etc.). It uses the first subnet it finds that either has no + class associated with it, or has a class which matches one of the + packet's classes. + +.. note:: + + The ``subnet4_select`` and ``subnet6_select`` callouts are called here. + +8. The server looks for host reservations. 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 the ``UNKNOWN`` class. + +9. Classes with matching expressions - directly, or indirectly using the + ``KNOWN``/``UNKNOWN`` built-in classes and not marked for later evaluation + ("on request") - are processed in the order they are defined + in the configuration; the boolean expression is evaluated and, if it + returns ``true`` (a match), the incoming packet is associated with the + class. After a subnet is selected, the server determines whether + there is a reservation for a given client. Therefore, it is not + possible to use the ``UNKNOWN`` class to select a shared network or + a subnet. For the ``KNOWN`` class, only global reservations are used and the + ``early-global-reservations-lookup`` parameter must be configured to + ``true``. + +10. When the incoming packet belongs to the special class ``DROP``, it is + dropped and an informational message is logged with the packet + information. Since Kea version 1.9.8, it is permissible to make the ``DROP`` + class dependent on the ``KNOWN``/``UNKNOWN`` classes. + +11. If needed, addresses and prefixes from pools are assigned, possibly + based on the class information when some pools are reserved for + class members. + +.. note:: + + The ``lease4_select``, ``lease4_renew``, ``lease6_select``, ``lease6_renew``, and ``lease6_rebind`` + callouts are called here. + +12. Classes marked as "required" are evaluated in the order in which + they are listed: first the shared network, then the subnet, and + finally the pools that assigned resources belong to. + +13. Options are assigned, again possibly based on the class information + in the order that classes were associated with the incoming packet. + For DHCPv4 private and code 43 options, this includes option + definitions specified within classes. + +.. note:: + + Care should be taken with client classification, as it is easy for + clients that do not meet any class criteria to be denied service + altogether. + +.. _built-in-client-classes: + +Built-in Client Classes +======================= + +Some classes are built-in, so they do not need to be explicitly defined. They +can be defined if there is a need to associate lease lifetimes, option data, +etc. with them. + +Vendor class information is the primary example: 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 that packet. The content that is +considered is the whole class identifier for DHCPv4, and the first vendor class +data field for DHCPv6. The enterprise number and subsequent vendor class data +fields are not used for the purpose of classification. For example, modern cable +modems send such options with value ``docsis3.0``, so the packet belongs to +class ``VENDOR_CLASS_docsis3.0``. + +The ``HA_`` prefix is used by :ischooklib:`libdhcp_ha.so` 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 uses an appropriate pool or subnet to allocate IP addresses +(and/or prefixes), based on the assigned client classes. The details can +be found in :ref:`hooks-high-availability`. + +The ``SPAWN_`` prefix is used by template classes to generate spawned class +names at runtime. The spawned class name is constructed by prepending the +``SPAWN_`` prefix to the template class name and the evaluated value: +``SPAWN_<template-class-name>_<evaluated-value>``. +More details can be found in :ref:`classification-configuring`. + +The ``BOOTP`` class is used by :ischooklib:`libdhcp_bootp.so` to classify and +respond to inbound BOOTP queries. + +The ``SKIP_DDNS`` class is used by the DDNS-tuning hook library to suppress +DDNS updates on a per client basis. + +Other examples are the ``ALL`` class, to which all incoming packets belong, +and the ``KNOWN`` class, assigned when host reservations exist for a +particular client. By convention, the names of built-in classes begin with all +capital letters. + +Currently recognized built-in class names are ``ALL``, ``KNOWN`` and ``UNKNOWN``, +and the prefixes ``VENDOR_CLASS_``, ``HA_``, ``AFTER_``, ``EXTERNAL_``, +``SKIP_DDNS``. Although the ``AFTER_`` prefix is a provision for an +as-yet-unwritten hook, the ``EXTERNAL_`` prefix can be freely used; built-in +classes are implicitly defined so they never raise warnings if they do not +appear in the configuration. + +.. _classification-using-expressions: + +Using Expressions in Classification +=================================== + +The expression portion of a classification definition contains operators +and values. All values are currently strings; 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 type of argument to an +operator. The evaluation code also checks for this class of error and +generally throws an exception, though this should not occur in a +normally functioning system. + +Other issues, such as 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. + +Dependencies between classes are also checked. For instance, forward +dependencies are rejected when the configuration is parsed; an +expression can only depend on already-defined classes (including built-in +classes) which are evaluated in a previous or the same evaluation phase. +This does not apply to the ``KNOWN`` or ``UNKNOWN`` classes. + +.. table:: List of classification values + + +-----------------------+-------------------------------+-----------------------+ + | Name | Example expression | Example value | + +=======================+===============================+=======================+ + | String literal | 'example' | 'example' | + +-----------------------+-------------------------------+-----------------------+ + | Hexadecimal string | 0x5a7d | 'Z}' | + | literal | | | + +-----------------------+-------------------------------+-----------------------+ + | IP address literal | 10.0.0.1 | 0x0a000001 | + +-----------------------+-------------------------------+-----------------------+ + | Integer literal | 123 | '123' | + +-----------------------+-------------------------------+-----------------------+ + | Binary content of the | option[123].hex | '(content of the | + | option | | option)' | + +-----------------------+-------------------------------+-----------------------+ + | Option existence | option[123].exists | 'true' | + +-----------------------+-------------------------------+-----------------------+ + | Binary content of the | option[12].option[34].hex | '(content of the | + | sub-option | | sub-option)' | + +-----------------------+-------------------------------+-----------------------+ + | Sub-Option existence | option[12].option[34].exists | 'true' | + +-----------------------+-------------------------------+-----------------------+ + | Client class | member('foobar') | 'true' | + | membership | | | + +-----------------------+-------------------------------+-----------------------+ + | Known client | known | member('KNOWN') | + +-----------------------+-------------------------------+-----------------------+ + | Unknown client | unknown | not member('KNOWN') | + +-----------------------+-------------------------------+-----------------------+ + | DHCPv4 relay agent | relay4[123].hex | '(content of the RAI | + | sub-option | | sub-option)' | + +-----------------------+-------------------------------+-----------------------+ + | DHCPv6 Relay Options | relay6[nest].option[code].hex | (value of the option) | + +-----------------------+-------------------------------+-----------------------+ + | DHCPv6 Relay Peer | relay6[nest].peeraddr | 2001:DB8::1 | + | Address | | | + +-----------------------+-------------------------------+-----------------------+ + | DHCPv6 Relay Link | relay6[nest].linkaddr | 2001:DB8::1 | + | Address | | | + +-----------------------+-------------------------------+-----------------------+ + | Interface name of | pkt.iface | eth0 | + | packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Source address of | pkt.src | 10.1.2.3 | + | packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Destination address | pkt.dst | 10.1.2.3 | + | of packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Length of packet | pkt.len | 513 | + +-----------------------+-------------------------------+-----------------------+ + | Hardware address in | pkt4.mac | 0x010203040506 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Hardware length in | pkt4.hlen | 6 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Hardware type in | pkt4.htype | 6 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | ciaddr field in | pkt4.ciaddr | 192.0.2.1 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | giaddr field in | pkt4.giaddr | 192.0.2.1 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | yiaddr field in | pkt4.yiaddr | 192.0.2.1 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | siaddr field in | pkt4.siaddr | 192.0.2.1 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Message type in | pkt4.msgtype | 1 | + | DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Transaction ID (xid) | pkt4.transid | 12345 | + | in DHCPv4 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Message type in | pkt6.msgtype | 1 | + | DHCPv6 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Transaction ID in | pkt6.transid | 12345 | + | DHCPv6 packet | | | + +-----------------------+-------------------------------+-----------------------+ + | Vendor option | vendor[*].exists | 'true' | + | existence (any | | | + | vendor) | | | + +-----------------------+-------------------------------+-----------------------+ + | Vendor option | vendor[4491].exists | 'true' | + | existence (specific | | | + | vendor) | | | + +-----------------------+-------------------------------+-----------------------+ + | Enterprise-id from | vendor.enterprise | 4491 | + | vendor option | | | + +-----------------------+-------------------------------+-----------------------+ + | Vendor sub-option | vendor[4491].option[1].exists | 'true' | + | existence | | | + +-----------------------+-------------------------------+-----------------------+ + | Vendor sub-option | vendor[4491].option[1].hex | docsis3.0 | + | content | | | + +-----------------------+-------------------------------+-----------------------+ + | Vendor class option | vendor-class[*].exists | 'true' | + | existence (any | | | + | vendor) | | | + +-----------------------+-------------------------------+-----------------------+ + | Vendor class option | vendor-class[4491].exists | 'true' | + | existence (specific | | | + | vendor) | | | + +-----------------------+-------------------------------+-----------------------+ + | Enterprise-id from | vendor-class.enterprise | 4491 | + | vendor class option | | | + +-----------------------+-------------------------------+-----------------------+ + | First data chunk from | vendor-class[4491].data | docsis3.0 | + | vendor class option | | | + +-----------------------+-------------------------------+-----------------------+ + | Specific data chunk | vendor-class[4491].data[3] | docsis3.0 | + | from vendor class | | | + | option | | | + +-----------------------+-------------------------------+-----------------------+ + +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 hexadecimal + notation. When writing an integer in hexadecimal, 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 does not contain the + option, it returns an 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 whether an option with the code ``code`` + is present in the incoming packet. It can be used with empty options. + +- ``member('foobar')`` checks whether the packet belongs to the client + class ``foobar``. To avoid dependency loops, the configuration file + parser verifies whether client classes were already defined or are + built-in, i.e., beginning with ``VENDOR_CLASS_``, ``AFTER_`` (for the + to-come "after" hook) and ``EXTERNAL_`` or equal to ``ALL``, ``KNOWN``, + ``UNKNOWN``, etc. + + ``known`` and ``unknown`` are shorthand for ``member('KNOWN')`` and ``not + member('KNOWN')``. Note that the evaluation of any expression using + the ``KNOWN`` class (directly or indirectly) 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 does not contain a RAI option, or the RAI + option does not 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 relays to be specified counting from the DHCPv6 client, with -1 indicating + the relay closest to the client. If the requested + encapsulation does not 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 + allow 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`` is + ``0x00000001`` or simply 1, as in ``pkt6.msgtype == 1``. + +- "Vendor option" means the Vendor-Identifying Vendor-Specific Information + option in DHCPv4 (code 125; see `Section 4 of RFC + 3925 <https://tools.ietf.org/html/rfc3925#section-4>`__) and the + Vendor-Specific Information Option in DHCPv6 (code 17, defined in + `Section 21.17 of RFC + 8415 <https://tools.ietf.org/html/rfc8415#section-21.17>`__). "Vendor + class option" means the Vendor-Identifying Vendor Class Option in DHCPv4 + (code 124; see `Section 3 of RFC + 3925 <https://tools.ietf.org/html/rfc3925#section-3>`__) in DHCPv4 and + the Class Option in DHCPv6 (code 16; see `Section 21.16 of RFC + 8415 <https://tools.ietf.org/html/rfc8415#section-21.16>`__). 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 an 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 the + option[60] expression. + +- `RFC 3925 <https://tools.ietf.org/html/rfc3925>`__ and `RFC + 8415 <https://tools.ietf.org/html/rfc8415>`__ 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 the ``vendor.enterprise`` and ``vendor-class.enterprise`` + expressions, the value from the first instance is returned. Please + submit a feature request on the + `Kea GitLab site <https://gitlab.isc.org/isc-projects/kea>`__ to request + support for multiple instances. + +.. table:: List of classification expressions + + +-----------------------+-------------------------+-----------------------+ + | Name | Example | Description | + +=======================+=========================+=======================+ + | Equal | 'foo' == 'bar' | Compare the two | + | | | values and return | + | | | ``true`` or ``false`` | + +-----------------------+-------------------------+-----------------------+ + | Not | not ('foo' == 'bar') | Logical negation | + +-----------------------+-------------------------+-----------------------+ + | And | ('foo' == 'bar') and | Logical and | + | | ('bar' == 'foo') | | + +-----------------------+-------------------------+-----------------------+ + | Or | ('foo' == 'bar') or | Logical or | + | | ('bar' == 'foo') | | + +-----------------------+-------------------------+-----------------------+ + | Substring | substring('foobar',0,3) | Return the requested | + | | | substring | + +-----------------------+-------------------------+-----------------------+ + | Concat | concat('foo','bar') | Return the | + | | | concatenation of the | + | | | strings | + +-----------------------+-------------------------+-----------------------+ + | Concat (operator +) | 'foo' + 'bar' | Return the | + | | | concatenation of the | + | | | strings | + +-----------------------+-------------------------+-----------------------+ + | Ifelse | ifelse('foo' == | Return the branch | + | | 'bar','us','them') | value according to | + | | | the condition | + +-----------------------+-------------------------+-----------------------+ + | Hexstring | hexstring('foo', '-') | Converts the value to | + | | | a hexadecimal string, | + | | | e.g. 66-6F-6F | + +-----------------------+-------------------------+-----------------------+ + | Lcase | lcase('LoWeR') | Converts the value of | + | | | a string expression | + | | | to lower case e.g. | + | | | 'lower' | + +-----------------------+-------------------------+-----------------------+ + | Ucase | ucase('uPpEr') | Converts the value of | + | | | a string expression | + | | | to upper case e.g. | + | | | 'UPPER' | + +-----------------------+-------------------------+-----------------------+ + | Split | split('foo.bar', '.', 2)| Return the second | + | | | field, splitting on | + | | | dots. | + +-----------------------+-------------------------+-----------------------+ + +.. table:: List of conversion-to-text expressions + + +-----------------------+---------------------------+------------------------+ + | Name | Example | Description | + +=======================+===========================+========================+ + | AddressToText | addrtotext (192.10.0.1) | Represent the 4 bytes | + | | addrtotext (2003:db8::) | of an IPv4 address or | + | | | the 16 bytes of an | + | | | IPv6 address in human | + | | | readable format | + +-----------------------+---------------------------+------------------------+ + | Int8ToText | int8totext (-1) | Represents the 8-bit | + | | | signed integer in text | + | | | format | + +-----------------------+---------------------------+------------------------+ + | Int16ToText | int16totext (-1) | Represents the 16-bit | + | | | signed integer in text | + | | | format | + +-----------------------+---------------------------+------------------------+ + | Int32ToText | int32totext (-1) | Represents the 32-bit | + | | | signed integer in text | + | | | format | + +-----------------------+---------------------------+------------------------+ + | UInt8ToText | uint8totext (255) | Represents the 8-bit | + | | | unsigned integer in | + | | | text format | + +-----------------------+---------------------------+------------------------+ + | UInt16ToText | uint16totext (65535) | Represents the 16-bit | + | | | unsigned integer in | + | | | text format | + +-----------------------+---------------------------+------------------------+ + | UInt32ToText | uint32totext (4294967295) | Represents the 32-bit | + | | | unsigned integer in | + | | | text format | + +-----------------------+---------------------------+------------------------+ + +Notes: + +The conversion operators can be used to transform data from binary to the text +representation. The only requirement is that the input data type length matches +an expected value. + +The ``AddressToText`` token expects 4 bytes for IPv4 addresses or 16 bytes for IPv6 +addresses. The ``Int8ToText`` and ``UInt8ToText`` tokens expect 1 byte, the ``Int16ToText`` and +``UInt16ToText`` tokens expect 2 bytes, and ``Int32ToText`` and ``UInt32ToText`` expect 4 bytes. +For all conversion tokens, if the data length is 0, the result string is empty. + +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 +does not 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 the 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' + +For user convenience, Kea version 1.9.8 added an associative operator +version of the concat function. For instance: +:: + + 'abc' + 'def' + 'ghi' + 'jkl' + '...' + +is the same as: +:: + + concat(concat(concat(concat('abc', 'def'), 'ghi'), 'jkl'), '...') + +or: +:: + + concat('abc', concat('def', concat('ghi', concat('jkl', '...')))) + +or: +:: + + 'abcdefghijkl...' + +Split +--------- + +The split operator ``split(value, delimiters, field-number)`` accepts a list +of characters to use as delimiters and a positive field number of the +desired field when the value is split into fields separated by the delimiters. +Adjacent delimiters are not compressed out, rather they result in an empty +string for that field number. If value is an empty string, the result will be an +empty string. If the delimiters list is empty, the result will be the original +value. If the field-number is less than one or larger than the number of +fields, the result will be an empty string. Some examples follow: +:: + + split ('one.two..four', '.', 1) == 'one' + split ('one.two..four', '.', 2) == 'two' + split ('one.two..four', '.', 3) == '' + split ('one.two..four', '.', 4) == 'four' + split ('one.two..four', '.', 5) == '' + +.. note:: + + To use a hard-to-escape character as a delimiter, use its ASCII hex value. + For example, split by ``single quote`` using ``0x27``: + ``split(option[39].text, 0x27, 1)`` + +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, ':') + + +.. note:: + + 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. Administrators who need complex or + time-consuming expressions should consider writing a + :ref:`hook <hooks-libraries>` to perform the necessary work. + +.. _classification-configuring: + +Configuring Classes +=================== + +A client class definition can contain the following properties: + - The ``name`` parameter is mandatory and must be unique among all classes. + - The ``test`` expression is not mandatory and represents a string containing the + logical expression used to determine membership in the class. The entire + expression is included in double quotes (``"``). The result should evaluate + to a boolean value (``true`` or ``false``). + - The ``template-test`` expression is not mandatory and represents a string + containing the logical expression used to generate a spawning class. The + entire expression is included in double quotes (``"``). The result should + evaluate to a string value representing the variable part of the spawned + class name. If the resulting string is empty, no spawning class is generated. + The resulting spawned class has the following generated name format: + ``SPAWN_<template-class-name>_<evaluated-value>``. + After classes are evaluated and a spawned class is generated, the corresponding + template class name is also associated with the packet. + - The ``option-data`` list is not mandatory and contains options that should be + assigned to members of this class. In the case of a template class, these + options are assigned to the generated spawned class. + - The ``option-def`` list is not mandatory and is used to define custom options. + - The ``only-if-required`` flag is not mandatory; when its value is set to + ``false`` (the default), membership is determined during classification and is + available for subnet selection, for instance. When the value is set to + ``true``, membership is evaluated only when required and is usable only for + option configuration. + - The ``user-context`` is not mandatory and represents a map with user-defined data + and possibly configuration options for hook libraries. + - The ``next-server`` parameter is not mandatory and configures the ``siaddr`` field in + packets associated with this class. It is used in DHCPv4 only. + - The ``server-hostname`` is not mandatory and configures the ``sname`` field in + packets associated with this class. It is used in DHCPv4 only. + - The ``boot-file-name`` is not mandatory and configures the ``file`` field in + packets associated with this class. It is used in DHCPv4 only. + - The ``valid-lifetime``, ``min-valid-lifetime``, and ``max-valid-lifetime`` are + not mandatory and configure the valid lifetime fields for this client class. + - The ``preferred-lifetime``, ``min-preferred-lifetime``, and + ``max-preferred-lifetime`` are not mandatory and configure the preferred + lifetime fields for this client class. It is used in DHCPv6 only. + + +.. note:: + + ``test`` and ``template-test`` are mutually exclusive in a client class + definition. Use either one, or neither, but not both. If both are provided, + the configuration is rejected. + +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" + } + ] + }, + ... + ], + ... + } + +The next 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 whose client identifiers start with the given +hex string (which would indicate a DUID based on an enterprise ID of +``0x0002AABBCCDD``). Members of this class will be given 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" + } + ] + }, + ... + ], + ... + } + +It is also possible to have both left and right operands of the evaluated +expression processed at runtime. Expressions related to packets can appear in +the expression as many times as needed; there is no limit. However, each token +has a small impact on performance and excessively complex expressions may cause a +bottleneck. + +:: + + "Dhcp4": { + "client-classes": [ + { + "name": "Infrastructure", + "test": "option[82].option[2].hex == pkt4.mac", + ... + }, + ... + ], + ... + } + +.. _template-classes: + +Template Classes +---------------- + +The ``template-test`` parameter indicates that the class is a template class. + +:: + + "Dhcp4": { + "client-classes": [ + { + "name": "Client-ID", + "template-test": "substring(option[61].hex,0,3)", + ... + }, + ... + ], + ... + } + +If the received DHCPv4 packet contains option 61, then the first three bytes represent +the value ``foo`` in ASCII, and the spawned class uses the +``SPAWN_Client-ID_foo`` name. +Both the ``SPAWN_Client-ID_foo`` and ``Client-ID`` classes are associated with +the packet. + +.. note :: + + Template classes can also be used to spawn classes which match regular + classes, effectively associating the regular class to the packet. + To achieve this, the regular class must also contain the fixed part of the + spawned class name: + + ``SPAWN_<template-class-name-used-to-activate-this-regular-class>_<evaluated-value-filtering-this-regular-class>`` + +:: + + "Dhcp6": { + "client-classes": [ + { + "name": "SPAWN_Client-ID_foobar", + "test": "substring(option[1].hex,0,6) == 0x0002AABBCCDD", + ... + }, + { + "name": "Client-ID", + "template-test": "substring(option[1].hex,0,6)", + ... + }, + ... + ], + ... + } + +If the received DHCPv6 packet contains option 1 (client identifier) with hex +value ``0x0002AABBCCDD``, then the ``SPAWN_Client-ID_foobar`` is associated +with the packet. Moreover, if the first six bytes represent value ``foobar`` in +ASCII, then the spawned class uses the ``SPAWN_Client-ID_foobar`` name, +effectively associating the regular class to the packet. In this second case, +both the ``SPAWN_Client-ID_foobar`` and ``Client-ID`` classes are associated +with the packet. +The ``test`` expression on the regular class ``SPAWN_Client-ID_foobar`` is not +mandatory and can be omitted, but it is used here with a different match +expression for example purposes. + +Usually the ``test`` and ``template-test`` expressions are 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 has not +yet been done. For this purpose, the ``only-if-required`` flag, which is +``false`` by default, allows the evaluation of the ``test`` expression or the +``template-test`` expression only when it is 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 reverse-precedence +order of the option data, i.e. an option data item in a subnet takes precedence over +one in a shared network, but a 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 be set to ``true``. + +.. note :: + + The ``template-test`` expression can also be used to filter generated spawned + classes, so that they are created only when needed by using the ``ifelse`` + instruction. + +:: + + "Dhcp4": { + "client-classes": [ + { + "name": "Client-ID", + "template-test": "ifelse(substring(option[61].hex,4,3) == 'foo', substring(option[12].hex,0,12), '')", + ... + }, + ... + ], + ... + } + +.. note :: + + The template classes can be used to configure limits which, just like + options, are associated with the spawned class. This permits the configuration of + limits that apply to all packets associated with a class spawned at + runtime, according to the ``template-test`` expression in the parent template + class. For a more detailed description of how to configure limits using the + limits hook library, see :ref:`hooks-limits-configuration`. + For example, using the configuration below, ingress DHCPv6 packets that have + client ID values (in the format expressed by the Kea evaluator) ``foobar`` + and ``foofoo`` both amount to the same limit of 60 packets per day, while + other packets that have the first three hextets different than ``foo`` are put + in separate rate-limiting buckets. + +:: + + "Dhcp6": { + "client-classes": [ + { + "name": "Client-ID", + "template-test": "substring(option[1].hex,0,3)", + "user-context" : { + "limits": { + "rate-limit": "60 packets per day" + } + }, + ... + }, + ... + ], + ... + } + + +.. _classification-using-host-reservations: + +Using Static Host Reservations in Classification +================================================ + +Classes can be statically assigned to clients using techniques +described in :ref:`reservation4-client-classes` and +:ref:`reservation6-client-classes`. + +Subnet host reservations are searched after subnet selection. +Global host reservations are searched at the same time by default but +the ``early-global-reservations-lookup`` allows to change this behavior +into searching them before the subnet selection. + +Pool selection is performed after all host reservations lookups. + +.. _classification-subnets: + +Configuring Subnets With Class Information +========================================== + +In certain cases it is 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 the range 192.0.2.10 to 192.0.2.20 will be managed by the DHCPv4 +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": [ + { + "id": 1, + "subnet": "192.0.2.0/24", + "pools": [ { "pool": "192.0.2.10 - 192.0.2.20" } ], + "client-class": "Client_foo" + }, + ... + ], + ... + } + +The following example shows how to restrict access to a DHCPv6 subnet. This +configuration restricts use of the addresses in the range 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": [ + { + "id": 1, + "subnet": "2001:db8:1::/64", + "pools": [ { "pool": "2001:db8:1::-2001:db8:1::ffff" } ], + "client-class": "Client_enterprise" + } + ], + ... + } + +.. _classification-pools: + +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 the range 192.0.2.10 to 192.0.2.20 are going to be managed by the +DHCPv4 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": [ + { + "id": 1, + "subnet": "192.0.2.0/24", + "pools": [ + { + "pool": "192.0.2.10 - 192.0.2.20", + "client-class": "Client_foo" + } + ] + }, + ... + ], + ... + } + +The following example shows how to restrict access to an address pool. This +configuration restricts use of the addresses in the range 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": [ + { + "id": 1, + + "subnet": "2001:db8:1::/64", + + "pools": [ + { + "pool": "2001:db8:1::-2001:db8:1::ffff", + "client-class": "Client_foo" + } + ] + }, + ... + ], + ... + } + +Class Priority +============== + +Client classes in Kea follow the order in which they are specified in the +configuration (vs. alphabetical order). Required classes follow the order in +which they are required. + +When determining which client-class information (comprising of +options, lease lifetimes or DHCPv4 field values) that is part of class +definitions, to include in the response, the server examines the union of +options from all of the assigned classes. If two or more classes include the +same class information, the value from the first assigned class is used. +``ALL`` is always the first class, hence the class with the highest +priority, and matching required classes are last, so they have the +lowest priority. + +Optons defined in classes override any global options, and in turn will be +overridden by options defined for an individual subnet, shared network, pool or +reservation. + +On the other hand, lease lifetimes and DHCPv4 field values defined at class +scope override any values defined globally, in a subnet scope, or in a +shared-network scope. + +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 +examines the three options - NTP, SMTP, and POP3 - and returns any that +the client requested. As the NTP server was defined twice, the server +chooses only one of the values for the reply; the class from which the +value is obtained is determined as explained in the previous paragraphs. + +Classes and Hooks +================= + +Hooks may be used to classify packets. This may be useful if the +expression would be complex or time-consuming to write, and could be +better or more easily written as code. Once the hook has added the proper class name +to the packet, the rest of the classification system will work as expected +in choosing a subnet and selecting options. For a description of hooks, +see :ref:`hooks-libraries`; for information on configuring classes, +see :ref:`classification-configuring` and :ref:`classification-subnets`. + +Debugging Expressions +===================== + +While constructing classification expressions, administrators may find +it useful to enable logging; see :ref:`logging` for a more complete +description of the logging facility. + +To enable the debug statements in the classification system, +the severity must be set to ``DEBUG`` and the debug level to at least 55. +The specific loggers are ``kea-dhcp4.eval`` and ``kea-dhcp6.eval``. + +To understand the logging statements, it is essential to understand a bit about +how expressions are evaluated; for a more complete description, refer to +[the design document](https://gitlab.isc.org/isc-projects/kea/-/wikis/designs/client-classification-design). +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 is +traversed in order; as each token is executed, it is 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 +emits 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 are 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 are displayed in the order +they were popped. For most expressions this will not matter, but for the +concat expression the values are displayed in reverse order from their +written order 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' + +.. note:: + + The debug logging may be quite verbose if there are multiple + expressions to evaluate; it is intended as an aid in helping + create and debug expressions. Administrators should plan to disable debug + logging when expressions are working correctly. Users may also + wish to include only one set of expressions at a time in the + configuration file while debugging them, to limit the log + statements. For example, when adding a new set of expressions, an administrator + might find it more convenient to create a configuration file that + only includes the new expressions until they are working + correctly, and then add the new set to the main configuration file. |