wiki:ClientClassificationDiscussion

Client Classification Requirements

THIS IS WORK IN PROGRESS

The design that seeks to fulfill those requirements is available here: ClientClassificationDesign.

Within the DHCP server there is a need to be able to tailor the response to characteristics of the client. Although this can be done by a custom hook, there is a need to allow the tailoring to be done in the configuration file. A mailing list thread has discussed the format of this configuration: this document summarises that discussion.

Basics

Section 7.2.13 in the Kea manual talks about the simple classification that we already have in the DHCPv4 and illustrates how to use it to limit access to an IPv4 subnet. (We have the same functionality in DHCPv6). In essence, Kea looks for the vendor class identifier option and, if present, associated the packet with a class named VENDOR_CLASS_xxx, where "xxx" is the contents of the vendor class identifier option. Once the class has been created, Kea can restrict the subnet from which the address is drawn by including a

"client-class": "VENDOR_CLASS_xxx"

statement in the "subnet" clause.

The two elements of that scheme are:

  1. Creation of a client class based on some criteria.
  2. Use of the class to allow/restrict the action of Kea.

Any scheme we have will need to do both of these things, and a possible way to express this in a Kea configuration is described below.

Creation of Client Class

Client classes are defined in a clause within the "Dhcp4" or "Dhcp6" blocks. The clause comprises an array of maps, each map defining a single class. When a packet is received, all the maps are scanned, as a packet may be associated with multiple classes.

The format of the specification is:

"client-classes": [
    {
        "test": "expression-string",
        "name": "name-string"
    },
    {
        "test": "expression-string",
        "name": "name-string"
    },
    ...
]

Each map contains up to two elements: a test and name.

Client Class Test

The map component "test" defines the test for a class. "expression-string" is free text containing an expression that evaluates to true or false. If it evaluates to true, the class whose name is the value of "name" is added to the list of classes associated with the packet. If the "test" element is absent, the test is assumed to be true and the class unconditionally added to the classes of the packet.

The elements of "expression-string" are:

  • Literals - such as strings and numbers. Strings are enclosed in single quotes.
  • Data - data extracted from the packet, either data from the general DHCP protocol fields or data from the options.
  • Operators - such as "==" for equality comparison.
  • Functions - applied to a literal or field, this produces a value that is used in the calculation.

To identify data, the names of options are used (without quotes). For example, the name vendor-class-identifier is the name of the eponymous option. If the option is an array, the offset of the element in the array (starting at 0) is appended to the name in square brackets, e.g. routers[3]. If the option has named fields, a field can be specified by appending its name to the option name, separating the two by a period (e.g. client-fqdn.flags). The special name "pkt" is reserved to indicate that the data comes from the DHCP packet itself, not the options. Thus "pkt.chaddr" would be the "chaddr" field from the (DHCP4) packet.

Functions are specified in the same way as used in computer code, i.e. a name followed by a set of arguments in parentheses, e.g "substr(client-fqdn.name, 0, 1)".

As an example, the "test" clause

"test": "substr(vendor-class-identifier, 2, 4) == 'ABCD'"

would evaluate to true if the third through sixth characters of the vendor-class-identifier option in the packet evaluate to the string ABCD and false if not. ("substr" is assumed to be the name of the substring function.) If vendor-class-identifier is not present, its value is assumed to the the empty string and the expression will evaluate false.

In the first release of client classification in 1.0 the following are supported:

  • Operators
    • == (equality check)
  • Functions
    • substr(data, offset, length) - returns a substring "length" long starting at "offset" (which starts from 0)
    • exists(data) - returns true or false if "data" exists. If "data" is a literal (e.g. 'abc') it always returns true. If it is the name of an option, it only returns true if the named option exists in the packet.

Client Class Name

The class name is an expression which evaluates to a string. typically it will be a set of elements separated by the "+" (concatentation) operator. Each element can be one of:

  • A literal string.
  • An option or packet field specification. If any of these evaluate to a non-string value, that value is converted to the appropriate string before use.
  • A function operating on a literal string or data.

As noted above, Kea already creates a class for each packet based on the vendor class identifier. The equivalent in the current proposal is the clause:

"client-classes": [
    {
        "test": "exists(vendor-class-identifier)",
        "name": "'VENDOR_IDENTIFIER_' + vendor-class-identifier"
    },
]

The test is only true if the vendor-class-identifier option exists. If so, class name is formed by appending the contents of the vendor-class-identifier option to the string 'VENDOR_CLASS_IDENTIFIER_'.

DISCUSSION/QUESTIONS
Constructing Class Names
The syntax proposed uses the free text form to specify the name of the class. Ideally, this should use the same parser as the expression parser. However, this may run into problems. If the parser is very flexible, it may handle numbers differently from strings. For example, when evaluating an addition, 1 + 1 = 2, but '1' + '1' = '11'.

Suppose that the name field comprises

'VENDOR_IDENTIFIER_' + opt1 + opt2

... where opt1 is a numeric option evaluating to 1, and opt2 a numeric option evaluating to 2. "'VENDOR_OPTION_' + 1" is a string plus an integer. So the integer is promoted to a string and we end up with 'VENDOR_OPTION_1'. The same applies when adding opt2, so the result is 'VENDOR_OPTION_12'. However, suppose the name field comprises:

opt1 + opt2 + '_VENDOR_IDENTIFIER'

opt1 and opt2 are both integers so the result of the addition is the integer 3. When combined with the '_VENDOR_IDENTIFIER' suffix we end up with '3_VENDOR_IDENTIFIER'. In other words, in the expressions opt1 + opt2 is either the string '3' or the string '12' depending on where it appears in the expression.

A possible alternative is to promote each term in the expression to a string before concatenation. That may still be confusing if the behavior of "+" in the "name" clause is different to that in the "test" clause. An alternative syntax is:

"name": ["term", "term", ...]

...where "term" is either a literal or a data value (no operators allowed) is clearer. The result is the result of concatenating all the terms together in order, each term being promoted to a string before the concatenation. So the "client-classes" example at the end of the section becomes:

"client-classes": [
    {
        "test": "exists(vendor-class-identifier)",
        "name": ["'VENDOR_IDENTIFIER_'", "vendor-class-identifier"]
    },
]

Use of Class Information

The current classification scheme requires that for a configuration block to be used, it must either contain no class name or, if it does, the name must match the class associated with the packet. The most frequent use of client classification would be to return a specific address or set of options to the client depending on its class.

To do this, Kea will support a "class-option-data" map of the form:

"class-option-data": [
    {
        "client-class": "class-name",
        "option-data": [ ... ]
    }
]

... which associates an option-data definition (already present in Kea) with a class. This can be added to both the top-level and the subnet declarations to provide class-specific option data.

The search path for an option value is then:

  1. Subnet class-option-data matching the client class
  2. Subnet option-data
  3. Global class-option-data matching the client class
  4. Global option-data

As an example, consider the following configuration. (It may be easier to read the notes at the bottom then look at the relevant parts of the configuration.)

"Dhcp4": {

# (Class definitions have been omitted from this example.)

    "option-data": [
        {
            "name": "domain-name-servers",
            "data": "192.0.2.3"
        }
    ],
    "class-option-data": [
        {
            "client-class": "beta",
            "option-data": [
                {
                    "name": "domain-name-servers",
                    "data": "10.0.0.1"
                }
            ]
        },
        {
            "client-class": "gamma",
            "option-data": [
                {
                    "name": "time-servers",
                    "data": "10.2.3.4"
                }
            ]
        }
    ],
    "subnet4": [
        {
            "subnet": "192.0.2.0/24",
            "client-class": "alpha"
        },
        {
            "subnet": "192.0.3.0/24",
            "client-class": "beta"
        },
        {
            "subnet": "192.0.4.0/24",
            "client-class": "gamma"
        },
        {
            "subnet": "192.0.5.0/24",
            "client-class": "delta",
            "option-data": [
                {
                    "name": "domain-name-servers",
                    "data", "10.0.0.2"
                }
             ]
        },
        {
             "subnet": "192.0.6.0/24",
             "option-data": [
                  {
                       "name": "domain-name-servers",
                       "data", "10.0.0.3"
                  }
             ],
             "class-option-data": [
                  {
                       "client-class": "epsilon",
                       "option-data": [
                           {
                              "name": "domain-name-servers",
                              "data": "10.0.0.4"
                           }
                       ]
                  }
             ]
        }
    ]
}

(Extraneous subnet and option definition information omitted.)

The logic for picking up the options is that if a client is classified as:

  • "alpha": The subnets are searched sequentially, and this class matches the class restriction of the first subnet 192.0.2.0/24. As there is no option definition in the "subnet" clause, Kea checks the global options. The only class option definitions are for the class "beta", so it picks up the global option definitions, giving the DNS server address as 192.0.2.3.
  • "beta": The second subnet (192.0.3.0/24) is selected with the class matching. Again there are no option definitions in the subnet, so the global options are searched. There are global class option definitions for "beta", so those options are picked up, giving the DNS server as 10.0.0.1. The generic global options are checked but as the only option defined is for domain-name-servers - which Kea has already found - that definition is ignored.
  • "gamma": The subnet 192.0.4.0/24 is selected. Like "beta" there is a global class option definition for this class, so "time-servers" is set to 10.2.3.4. Unlike "beta" though, when the global option-data is examined, Kea finds domain-name-servers defined. This is an option that has not already been found, so in addition to "time-servers", the option "domain-name-servers" is picked up with a value of 192.0.2.3.
  • "delta": domain-name-servers is defined in the matching subnet definition, so the value of 10.0.0.2 is used. No other options are defined in the search path, so that is the only option picked up.
  • "epsilon": Kea will settle on the subnet 192.0.6.0/24 as all the other subnets are restricted to other classes. Within this subnet, there is a class option definition for domain-name-servers matching the class "epsilon" so the defined value 10.0.0.4 is used. There are no other options in the search path, so this is the only option returned.
  • Other classes: again the subnet 192.0.6.0/24 is used as that is the only one that matches. The client-option-data clause in that subnet definition only matches "epsilon", so other classes will use the "option-data" clause and the value of domain-name-servers as 10.0.0.3. Again, as there are no other options in the search path, this the only option used.
Last modified 2 years ago Last modified on Oct 13, 2015, 3:23:44 PM