Understanding Client Classification
  • Updated on 25 Oct 2019
  • 23 minutes to read
  • Contributors
  • Print
  • Share
  • Dark
    Light

Understanding Client Classification

  • Print
  • Share
  • Dark
    Light

Most DHCP server administrators have heard or read about client classification. Unlike other DHCP server features, it is not described in any standards, but it is still one of the most widely used features in DHCP installations. The number of possible applications and the complexity of client classification are often sources of confusion for administrators. This document clarifies various aspects of client classification in Kea. You should read this article if:

  • you have never used client classification before,
  • you have tried to configure a Kea server to use client classification but the server did not behave as you hoped,
  • you are curious about the details of how the Kea server applies client classification to the processed DHCP messages, or
  • you were unable find sufficient information in the Kea ARM regarding client classification.

Classification Properties

Client classification is not necessarily about differentiating DHCP clients by their hardware type or the role of the device in the network. Client classification should rather be perceived as a stateless DHCP packet pre-processing mechanism to examine the incoming DHCP packet's contents and associate the packets with a class based on some configuration criteria. Special rules can be applied for processing packets belonging to different classes. Let's discuss the properties of client classification which stem from this definition.

Packet Classification

The Kea server looks at the contents of each received packet and associates it with one or more classes. If none of the defined classification criteria apply to the received packet, it remains associated with no classes (i.e. is classless). Classless packets are processed by the server without applying any rules exclusively defined for classes. For example, a response sent by the server as a result of receiving a classless packet will not contain the DHCP options defined within the classes' scopes. Another example: a subnet reserved for a particular class can't be selected for the classless packet.

In many cases what is important is not which client has sent the packet, but what it sent in the payload of the packet. From that perspective, the client classification feature could be better named packet classification. However, if classes are properly defined, the administrator may be able to differentiate between clients by looking at the contents of the packets they send. In other words, it is possible to use packet classification to mimic client classification. This can be illustrated with the typical class configuration present in cable networks:

"client-classes": [
   {
        "name" : "CableModem",
        "test" : "substring(option[60].hex,0,6) == 'docsis'"
    }
]

This Kea configuration snippet defines the new class "CableModem", which is assigned to a received packet whenever the Vendor Class Identifier option (code 60) contains the string docsis. Assuming that this string is merely present in the packets sent by cable modems, and not in the packets sent by routers (which are behind the cable modems), the packet classification in this particular case has the effect of classifying the clients into either cable modems or other (classless) devices. It is typical in such configurations for the server to select a different subnet for cable modems and for other devices, taking into account the classes associated with the packet.

Having demonstrated how the classification can be used to implicitly differentiate between the types of devices (client classification), we will now briefly discuss a different case which demonstrates that classification is much more powerful and can be useful beyond just segregating client devices into different categories.

Suppose there are two relay agents forwarding DHCP packets to your server. The server has only one subnet configured, and we want to define two IP address pools within this subnet. A client whose packets are forwarded via the first relay agent should be assigned an IP address from the first pool, and the client which packets are forwarded via the second relay agent should be assigned an IP address from the second pool. The first step is to define two client classes, which are assigned to the packet according to the relay agent address (giaddr) present in the received packet:

"client-classes": [
    {
        "name": "Relay1",
        "test": "pkt4.giaddr == 192.0.3.1"
    },
    {
        "name": "Relay2",
        "test": "pkt4.giaddr == 192.0.4.1"
    }
]

All packets including the giaddr field with the IP address of 192.0.3.1 (forwarded by the relay with this address) will be assigned to the "Relay1" class, and all packets forwarded via the relay agent with the IP address of 192.0.4.1 will be assigned to the "Relay2" class. Note that this classification does not segregate the clients by the type of device, as in the previous example. In this case, all devices behind both relays may be of the same type (same vendor, model, firmware), but the packets they send will be assigned to different classes, depending on the relay agent through which they reach the server. In this particular case, the term packet classification better describes the usage of the feature than client classification.

Note
The term client classification may sometimes be confusing because it can be applied to much more complex conditions for processing DHCP traffic than simply segregating the traffic from different device types into different classes. The term client classification was used for this feature in Kea for historical reasons: first, this is how the users of ISC DHCP referred to this feature and it became a de facto standard; second, the case whereby the classification is used to infer the type of device from the packet contents is still its most widely used application. In this section, however, we have tried to emphasize that this is not the only supported application.

Stateless

A Kea server receiving a packet from a DHCP client evaluates the packet contents against the classes defined in the server's configuration. The matching classes are associated with the packet and influence the way it is processed by the server and the response that is generated and sent to the client. The server neither collects nor stores any additional information beyond the association of the packet with the classes; when the response is sent to the client, the server "forgets" those associations as well. The next received packet is classified independently from any classification applied to the previously processed packets; because of this, we say that client classification is "stateless." The stateless nature implies that classification is not appropriate for dealing with use cases where any state information must be held when processing across multiple packets.

For example, some users have expressed interest in rate limiting in Kea and have suggested that clients which tend to flood the server with excessive DHCP traffic should be associated with some selected class. As a result, the server could completely ban any clients belonging to this class or simply drop some of their traffic. Even if we ignore the lack of certain mechanisms in the Kea server to drop packets belonging to a certain class, client classification is inappropriate to solve this problem because of its stateless nature. Rate limiting requires storing the historical information about the behavior of the clients and the client classification is not designed to do that.

Client classification applies to a given packet
The classification for other received packets is performed independently.

Special Processing Rules

Client classification is one of the most critical and widely used features in Kea. It controls non-standard packet processing rules in an elegant and easy-to-configure manner. ISC continuously improves this feature by adding new ways of processing the received packets depending on what set of classes they belong to. For example, one of the recently proposed extensions was to add a new built-in DROP class; the server would drop all packets belonging to this class. The administrator could specify certain conditions according to which this class would be associated with the received packet. This way, the administrator could filter out unwanted traffic using the classification expressions rather than writing a dedicated hooks library. There are many other processing rules which may be implemented in the future, depending on demand from users.

Built-in classes
A built-in class is a pre-defined class with a well-known name and meaning to the server. For example, the KNOWN built-in class stands for "packet from a known client," i.e. a client with static host reservations. The administrator may define special processing rules for packets for which host reservations exist.

In this section we briefly list the available special processing rules and applications of the client classification:

Processing rule Example application
Some shared networks are only used when a received packet belongs to a given class. Reserve access to a given shared network to a particular group of clients, e.g. cable modems.
Some subnets are only used when a received packet belongs to a given class. Reserve access to a given subnet to the particular group of clients, e.g. cable modems.
Some address and prefix delegation pools are only used when a received packet belongs to a given class. Load balance the clients between two distinct pools, e.g. in the case of High Availability.
Some DHCP options (option values) are included in the DHCP response only when a received packet belongs to a given class. A particular group of clients, e.g. cable modems, receives options that other clients don't receive.
Custom option formats (definitions) can be configured for DHCPv4 private options (with codes between 224 and 254) depending on the class a received packet belongs to. Support an old PXE client vendor, which may require custom formatting of a private option.
Custom option formats (definitions) can be configured for Vendor-Specific Information (code 43) depending on the class a received packet belongs to. Include sub-options in option 43 with the format appropriate for the particular vendor.

In the subsequent sections we describe in detail some of these processing rules.

Restrict the Use of Networks and Subnets

Client classification can be used to restrict access to selected shared networks and/or subnets. Typically, the use of shared networks and subnets is restricted to selected types of devices. For example, a class can be defined which is assigned to a packet only if the Vendor Class Identifier option contains the "docsis" string, which indicates that the DHCP client is a cable modem. Consider the following subnet configuration snippet:

subnet4: [
    {
        "id": 1,
        "subnet": "192.0.2.0/24",
        "interface": "eth0",
        "client-class": "CableModem",
        ...
    }
]

This associates the subnet with the "CableModem" class. In other words, this subnet can only be used (selected) when the client's packet is classified as belonging to the "CableModem" class. Obviously, the client's packet must also meet other criteria for selecting this subnet; in particular, the packet must be received over the interface "eth0".

The client-class parameter
The meaning of this parameter is often misunderstood by users. Specifying "client-class": "CableModem" for the subnet doesn't mean that this subnet is selected for all packets belonging to the CableModem class. It merely means that this subnet can only be selected when a received packet belongs to this class and it is never selected for the packets that don't belong to this class.

Consider two subnets, each including the interface parameter set to eth0, and one of them contains the client-class set to CableModem. When the packet is received over the interface eth0, any of these subnets can be selected if a received packet belongs to the CableModem class. However, it is guaranteed that the subnet including the CableModem class will never be selected for a classless packet. Therefore, the client-class specification is not causing the server to select any particular subnet; it is used to limit access to this subnet for a group of clients and eliminate the clients which don't belong to the given class. This is a very important distinction!

It is possible to mimic subnet selection using client classes by associating each subnet with an appropriate class of clients:

subnet4: [
    {
        "id": 1,
        "subnet": "192.0.2.0/24",
        "interface": "eth0",
        "client-class": "CableModem",
        ...
    },
    {
        "id": 2,
        "subnet": "192.0.3.0/24",
        "interface": "eth0",
        "client-class": "Router",
        ...
    }
]

The first subnet is associated with the CableModem class, while the second subnet is associated with the Router class. The first subnet may only be used for packets classified as CableModem and the second subnet may only be used for packets classified as Router. Simply speaking, the first subnet can only be selected for cable modems and the second subnet can only be selected for routers. Note that in a general case this is different from saying that "the first subnet is selected for cable modems and the second subnet is selected for routers"; in this configuration example, however, the end result is the same.

When the server receives a packet it first classifies it. If the classes are defined properly in the server configuration, we may expect that packets from cable modems will be assigned to the CableModem class and packets from routers will be assigned to the Router class. In order to further process the packet (assign an IP address etc.), the server must select a subnet for this packet. As we mentioned above, the subnet is not selected by matching the client class associated with the packet. In this particular case, the subnet will be selected by the interface name. Note that both subnets include the same interface name, which means that both of them are good candidates for being selected. If the interface name does not match, the subnet won't be selected for the client even if the client class matches!

The server typically reviews the subnets list sequentially. For a packet sent by the router, the server initially selects the first subnet (if the interface matches). When the subnet is selected, the server checks whether this subnet is allowed to be selected by the specified client class. It does not match in this particular case, so the server proceeds to the next subnet. This time, the interface and the client class both match, so the server uses the second subnet for the router.

Note that we've been able to influence the subnet selection for different classes of clients, but this must be used with care. This approach relies on client classes being specified for each subnet. Sometimes it is hard to define a class expression which will allow all clients for which the particular subnet should be chosen. In the example above, if a packet sent by a router does not match any of the defined classes, no subnet will be selected for that packet. As a result, the router won't be provisioned.

Sometimes it may be useful to create an additional subnet to be selected for all clients that match neither of the existing classes, such as guests or low-priority clients. They match none of the existing classes but we don't want to leave them out of service. Due to the role of client classification in the subnet selection mechanism, it would be wrong not to specify the "client-class" for this catch-all subnet. However, this would leave the class open to all clients contacting the DHCP server, including those that belong to the CableModem and Router class; we want this subnet to be selected only for clients that do not belong to these two classes. Let's consider this example:

subnet4: [
    {
        "id": 1,
        "subnet": "192.0.2.0/24",
        "interface": "eth0",
        "client-class": "CableModem",
        ...
    },
    {
        "id": 2,
        "subnet": "192.0.3.0/24",
        "interface": "eth0",
        "client-class": "Router",
        ...
    },
    {
        "id": 3,
        "subnet": "192.0.4.0/24",
        "interface": "eth0",
        "client-class": "CatchAll",
        ...
    }
]

The new subnet can only be selected when the packet belongs to the CatchAll class. To achieve our goal, this class should only be assigned to any packet which belongs to neither the CableModem nor the Router class. This dependency can be expressed on the class definition level:

"client-classes": [
    {
        "name" : "CableModem",
        "test" : "substring(option[60].hex,0,6) == 'docsis'"
    },
    {
        "name" : "Router",
        "test" : "substring(option[60].hex,0,6) == 'router'"
    },
    {
        "name" : "CatchAll",
        "test" : "not member('CableModem') and not member('Router')"
    }
]

The CatchAll class is always assigned to a packet when the packet belongs to neither the CableModem nor the Router class. Consequently, the server selects the third subnet because the CatchAll client class only matches this subnet and the packet does not belong to any of the classes for which the first two subnets can be selected.

Note that all these examples also apply to the shared network selection.

The following table summarizes the subnet selection depending on the client-class parameter value (specified for a subnet or shared network) and the class(es) assigned to the packet. The selected means that the subnet can be selected for the given packet. Conversely, not selected means that the subnet will never be selected for the packet. The empty client-class means that there are no restrictions on the subnet with respect to the client classification. As can be observed in the table below, such a subnet can be selected for all received packets assuming that the packet matches its server selector, e.g. interface name, relay agent address, etc.

"client-class": "class1" "client-class": "class2" "client-class": ""
packet in "class1" selected not selected selected
packet in "class2" not selected selected selected
packet in "class1" and "class2" selected selected selected
packet in no class not selected not selected selected

Class-Specific Options Assignment

We have described how client classification can be used to influence subnet (or shared network) selection. Subnet configuration usually comes with a set of specific DHCP options, but many times we want to further differentiate DHCP options assignments within the particular subnet, depending on the packet contents.

Additional Options

The simple configuration provided below includes a subnet with one DHCP option domain-name-servers. All DHCP clients for which this subnet is selected are assigned this option, regardless of whether they belong to any class. The example configuration also includes the ExceptionalClient class definition, which is assigned to all clients sending vendor class identifier option (60) including the string CallCo. The client class definition also contains the option-data list with a single DHCP option log-servers. DHCP clients belonging to this class are assigned this option apart from the appropriate global options, shared network and subnet-specific options, pool-specific options and host-specific options. In this simple example, the client belonging to the class CallCo and the subnet 192.0.2.0/24 receives two options: domain-name-servers with the IP address of 10.0.0.1, and log-servers with the IP address of 10.0.0.2.

"client-classes": [
    {
        "name": ExceptionalClient",
        "test": "option[vendor-class-identifier].text == 'CallCo'",
        "option-data": [
            {
                "name": "log-servers",
                "data": "10.0.0.2"
            }
         ]
    }
],
"subnet4": [
     {
         "id": 1,
         "subnet": "192.0.2.0/24",
         "interface": "eth0",
         "option-data": [
             {
                 "name": "domain-name-servers",
                 "data": "10.0.0.1"
             }
         ]
         ...
     }
]

Options Precedence

In the previous example, we demonstrated how to assign subnet-specific options and class-specific options. However, we assigned two options with different option codes and therefore there was no conflict between them. In reality, DHCP options with overlapping option codes may be specified at various configuration levels; there are strict precedence rules that the server follows to select which of the "conflicting" option instances are used.

The DHCP server configuration allows for specifying DHCP options at various levels:

  • Host options level
  • Pool options level
  • Subnet options level
  • Shared network options level
  • Client class options level
  • Global options level

Internally, the DHCP server stores all configured options in the respective option containers: one container for each of the configuration levels listed above. Let's call these containers: O-hosts, O-pools, O-subnets, O-networks, and O-globals. Since each class may come with its own option set, we also have class-specific containers, e.g. O-class-foo options belonging to class foo, O-class-bar options belonging to class bar, etc.

When the server receives a packet, it first builds a list of containers from which it will assign DHCP options and puts these containers in the following order:

  • O-hosts
  • O-pools
  • O-subnets
  • O-networks
  • Multiple O-class containers (according to the order of evaluation of the classes)
  • O-globals

The server starts assigning DHCP options from the top to the bottom container; it starts from the host-specific options and includes all DHCP options present in the host reservation for a particular client in the DHCP response packet. If the client has no host reservation, the server simply moves to the next container.

The server iterates over options in the O-pools container and includes them in the DHCP response. However, the server skips those pool-specific options which are already found in the response (assigned from the host reservation). At that point, the DHCP response contains a mix of host-specific and pool-specific options. Some of the host-specific options override those from pools and other levels; this allows some host-specific option values to be selected for a particular client, while assigning another value for other clients using the given address pool.

The server assigns options from the O-subnets and O-networks container using the same algorithm, resulting in pool-specific options overriding subnet-specific options and subnet-specific options overriding shared network-specific options.

The options from multiple O-class containers are added next. Suppose we have two classes defined:

"client-classes": [
    {
        "name": "floor1",
        "test": "pkt4.giaddr == 192.0.2.45",
        "option-data": [
            {
                "name": "log-servers",
                "data": "10.0.0.2"
            }
         ]
    },
    {
        "name": "right-wing",
        "test": "option[vendor-class-identifier].text == 'Pro'",
        "option-data": [
            {
                "name": "log-servers",
                "data": "10.0.0.3"
            }
         ]
    }
]

Also, let's assume that the received packet belongs to both defined classes. The server creates two option containers - O-class-floor1 and O-class-right-wing - and assigns options from these containers in the order in which the classes are specified/evaluated. The server includes the log-servers option with the value of 10.0.0.2 in the response, if the log-servers option hasn't yet been included while processing other containers (hosts, pools, subnets, shared networks). Next, the server processes the O-class-right-wing container. In this example, the server skips the log-servers option with the value of 10.0.0.3, as this option has been already added from the O-class-floor1 container.

Finally, after processing all O-class containers, the server processes the O-globals container and assigns global options which do not duplicate already-added options.

The options-inheritance mechanism described above is graphically presented in the following picture.

kea-options-inheritance(1)

The colored rectangles represent configured DHCP options; different colors indicate options specified at different configuration levels. For example, global options are shown in gray, subnet-level options are green, etc. Options specified for different client classes are also shown in different colors. The location of the option on the horizontal axis reflects its option code, i.e. the left-most option has the lowest option code and the right-most option has the highest option code. Two options on the same vertical axis (one above another) have the same option code. The options stored in the DHCP response are the combination of options specified at various levels. Client-class options are assigned in the order in which the client classes are evaluated (specified in the configuration file).

Influencing Options Precedence

In the previous section we described how the server assigns DHCP options to the response it sends. We explained that the server follows options assignment order from host-specific options through pool-specific options, all the way up to the global options which are the least preferred. This options assignment order is always the same and there are currently no configuration adjustments that allow for customization.

Some users have indicated the need to be able to configure the class-specific options to take precedence over subnet, shared-network, or pool-specific options; in other words, the class-specific options would be assigned right after host-specific options. This is often required in deployments where classes describe a group of clients of a similar type or having similar capabilities. In this scenario, classes are semantically similar to hosts or the class is the superset of hosts.

More specifically, the desired assignment options order in this case would be:

  • O-hosts
  • Multiple O-class containers
  • O-pools
  • O-subnets
  • O-networks
  • O-globals

As mentioned above, there is no way to explicitly set such an options assignment order; however, client classification brings some mechanisms which help to mimic such behavior (at least partially).

The initial configuration we're going to use in this example looks as follows:

"client-classes": [
    {
        "name": "Foo",
        "test": "option[vendor-class-identifier].text == 'CallCo'",
        "option-data": [
            {
                "name": "log-servers",
                "data": "10.0.0.2"
            }
         ]
    }
],
"subnet4": [
     {
         "id": 1,
         "subnet": "192.0.2.0/24",
         "relay": { "ip-address": "192.0.2.100" },
         "option-data": [
             {
                 "name": "log-servers",
                 "data": "10.0.0.1"
             }
         ]
         ...
     }
]

With the default options assignment precedence, a client belonging to the class Foo and subnet 192.0.2.0/24 is given the log-servers option with the IP address of 10.0.0.1, because the subnet-specific option takes precedence over the class-specific option.

Now, let's rewrite our configuration to use the property of client classification that options are assigned in the classes' evaluation order:

"client-classes": [
    {
        "name": "Foo",
        "test": "option[vendor-class-identifier].text == 'CallCo'",
        "option-data": [
            {
                "name": "log-servers",
                "data": "10.0.0.2"
            }
         ]
    },
    {
        "name": "subnet-192.0.2.0-client",
        "test": "member('ALL')",
        "only-if-required": true,
        "option-data": [
            {
                "name": "log-servers",
                "data": "10.0.0.2"
            }
         ]
    },
],
"subnet4": [
     {
         "id": 1,
         "subnet": "192.0.2.0/24",
         "relay": { "ip-address": "192.0.2.100" },
         "require-client-classes": [ "subnet-192.0.2.0-client" ]
         ...
     }
]

The only-if-required flag set to true for the class subnet-192.0.2.0-client indicates that this class is evaluated only when the selected pool, subnet, or shared network configuration indicates that this class must be evaluated (i.e. is required). The require-client-classes list contains the list of classes that must be evaluated only if the subnet is selected. The log-servers option, which used to be embedded in the subnet configuration, is now moved into the new class subnet-192.0.2.0-client. Because the class is configured to be evaluated when required, it is not evaluated unless the subnet 192.0.2.0/24 is selected. Specifically, it is not evaluated for subnets which don't explicitly require this class.

If the subnet 192.0.2.0/24 is selected, the server evaluates the subnet-192.0.2.0-client class in addition to the class Foo. However, the class Foo is evaluated first; therefore the options belonging to the class Foo will take precedence over the options within the class subnet-192.0.2.0-client. If the client doesn't belong to the class Foo, the option from the subnet-192.0.2.0-client is assigned instead. The table below shows what value of the log-servers option is returned depending on the combination of the classes and subnets the client belongs to.

Belongs to subnet 192.0.2.0/24 Belongs to class Foo Belongs to class subnet-192.0.2.0-client Log Servers
yes yes yes 10.0.0.2
yes no yes 10.0.0.1
no yes no 10.0.0.2
no no no -

In this example, the client is always assigned to the class subnet-192.0.2.0-client when the 192.0.2.0/24 subnet is selected because the member('ALL') condition always evaluates to true.

The following state diagram describes the process of assigning DHCP options in this example.

kea-class-options-assignment

The server first iterates over the specified classes in the order in which they are defined in the configuration. For each class which is always to be evaluated, it performs the evaluation and assigns the class to the packet if the evaluation yelds "true"; it skips all classes marked as only-if-required. After iterating over all the classes, the server selects the subnet. The subnet may include the required-client-classes parameter, which indicates which classes should be evaluated in addition to those already evaluated in the first step. The server iterates over these classes and evaluates them, assigning them to the packet when they match. Finally, for each class assigned to the packet the server checks whether any options are specified. Those options are assigned to the packet using the precedence algorithm described above.

Class-Specific Option Definitions

The mechanism by which it is possible to use different option definitions for different types of clients is well-described in the Kea ARM.

Was this article helpful?