Facilitating Classification with Template Classes
  • 27 Jan 2023
  • 5 Minutes to read
  • Contributors
  • Dark
    Light
  • PDF

Facilitating Classification with Template Classes

  • Dark
    Light
  • PDF

Article Summary

What is classification anyway?

Client classification helps with differentiating between clients. It can be used to change the behavior of many parts of the DHCP message processing:

  • subnet selection
  • pool selection
  • lease limiting
  • rate limiting
  • DDNS tuning
  • dropping queries
  • definition of DHCPv4 private (codes 224-254) and code 43 options
  • assignment of different options
  • for DHCPv4 cable modems, the setting of specific options for use with the TFTP
    server address and the boot file field

What are template classes?

Template classes are a special type of classes that can spawn an indefinite number of regular classes using a single class definition. That makes it an efficient way of configuring multiple classes. The spawned classes need to be mentioned in other parts of the configuration in order to make effective use of them. Template classes don't make it easier for the spawned classes to be referenced. That part of the configuration is, unfortunately, just as challenging as before, with a couple of exceptions (see limiting).

How is a template class defined?

Template classes are defined with the template-test entry in the class definition instead of test. The template-test needs to have a string result instead of test's bool. If the template-test expression evaluates to something different than empty string, then the packet gets classified to both the template class, let's say $name, and the associated spawned class name in the form of SPAWN_$name_$expressionValue. If, on the other hand, the template-test expression evaluates to an empty string, then a class is not spawned for the currently processing packet, nor is the template class assigned to it.

Example. OUI Vendor

Here is one example of a template class that spawns a regular class for each OUI vendor. It does so by taking the first three bytes from the DHCPv6 DUID, assuming it is of DUID-LL type.

{
  "Dhcp6": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "hexstring(substring(option[1].hex, 4, 3), ':')"
      }
    ]
  }
}

This will make Kea classify the packet both to the template class itself, and to the spawned class. Receiving a packet with DUID set to 00:03:00:01:01:02:03:12:34:56 will make Kea log the following line, confirming the assignment to the two classes:

DEBUG DHCP6_CLASS_ASSIGNED duid=[00:03:00:01:01:02:03:12:34:56], tid=0x1: client packet has been assigned to the following class(es): ALL, oui-vendor, SPAWN_oui-vendor_01:02:03, UNKNOWN

For completeness' sake, it's worth mentioning that other DUID types can be filtered out from the set of spawned. Notice the use of empty string below at the end of the test expression. If the test expression evaluates to it, it means that there will be no spawned class for the currently processing packet, and also that the template class will not be assigned to it.

{
  "Dhcp6": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "ifelse(hexstring(substring(option[1].hex, 0, 2), '') == '0003', hexstring(substring(option[1].hex, 4, 3), ':'), '')"
      }
    ]
  }
}

Conversely, the empty string, could have been replaced with a meaningful name like NOT_DUID_LL. In that case, the SPAWN_oui-vendor_NOT_DUID_LL class would have been assigned to the packet and this class could be further used throughout the configuration.

There is a DHCPv4 equivalent configuration.

{
  "Dhcp4": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "hexstring(substring(pkt4.mac, 0, 3), ':')"
      }
    ]
  }
}

Use case 1. Reference the spawned class directly in configuration

Say that an administrator wants to control what subnet is selected for each OUI vendor. Each subnet would be guarded by the spawned class' name. In the example below, vendor 01:02:03 is going to get addresses from the 2001:db8:1::/96 range, while vendor aa:bb:cc is going to get address from the 2001:db8:2::/96 range.

{
  "Dhcp6": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "hexstring(substring(option[1].hex, 4, 3), ':')"
      }
    ],
    "shared-networks": [
      {
        "subnet6": [
          {
            "client-class": "SPAWN_oui-vendor_01:02:03",
            "subnet": "2001:db8:1::/96",
            "pools": [
              {
                "pool": "2001:db8:1::/96
              }
            ]
          },
          {
            "client-class": "SPAWN_oui-vendor_aa:bb:cc",
            "subnet": "2001:db8:2::/96",
            "pools": [
              {
                "pool": "2001:db8:2::/96
              }
            ]
          }
        ]
      }
    ]
  }
}
How it relates to regular classes

It's possible to use regular classes to achieve vendor-based subnet selection, but each vendor would need to have its own class defined in the configuration with a matching test expression.

Use case 2. Have a class dependency on the template class or spawned class

Spawned classes can also be referenced in other class definitions as well. In the following example, packets coming from the two OUI vendors are dropped.

{
  "Dhcp6": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "hexstring(substring(option[1].hex, 4, 3), ':')"
      },
      {
        "name": "SPAWN_oui-vendor_01:02:03"
      },
      {
        "name": "SPAWN_oui-vendor_aa:bb:cc"
      },
      {
        "name": "DROP",
        "test": "member('SPAWN_oui-vendor_01:02:03') or member('SPAWN_oui-vendor_aa:bb:cc')"
      }
    ]
  }
}

One drawback is that the spawned classes need to be defined without a test expression nonetheless. This is a requirement of the member functionality.

Template classes themselves can also be dependencies of other classes, but that offers little advantage over using a regular class with != '' added at the end of the expression.

How it relates to regular classes

Requiring a class to be a member of another is not a new feature in template classing. This section just ensures that it still possible to use it with template classes and with spawned classes alike.

Use case 3. Define the spawned class

Resources, such as lease times or option data, can be added to a spawned class. In the example below, one vendor has its valid lifetime set, while the other has DNS servers set.

{
  "Dhcp6": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "hexstring(substring(option[1].hex, 4, 3), ':')"
        ]
      },
      {
        "name": "SPAWN_oui-vendor_01:02:03",
        "valid-lifetime": 7200
      },
      {
        "name": "SPAWN_oui-vendor_aa:bb:cc",
        "option-data": [
          {
            "code": 23,
            "name": "dns-servers",
            "data": "2001:db8::7, 2001:db8::8"
          }
        ]
      }
    ]
  }
}
How it relates to regular classes

It's possible to use regular classes to achieve vendor-based lease times or option data, but that would require a full client class definition with a test expression alongside its name, lease times, and option data. The benefit with template classes, is that you don't have to think of a test expression for each of the spawned classes.

Use case 4. Lease limiting and rate limiting

Limits can be defined a single time, and they get applied individually to each spawned class (i.e. to each vendor).

{
  "Dhcp6": {
    "client-classes": [
      {
        "name": "oui-vendor",
        "template-test": "hexstring(substring(option[1].hex, 4, 3), ':')",
        "user-context": {
          "limits": {
            "address-limit": 2,
            "rate-limit": "10 packets per minute"
          }
        }
      }
    ]
  }
}

From a high-level point of view, this may not appear as a separate use case. It's similar to the other use cases, in the sense that a resource is defined in the template class definition and each of the spawned classes gets to benefit from the added resource. But the limits hook library has special logic to limit each spawned class individually. If this logic were missing, the limits would be attributed to the template class' name and all of the packets would account towards the same limit, instead of each vendor having their own separate limit. Also, if it were not for template classes, applying a limit individually would have required a separate client class definition for each vendor. This is the one use case where both the definition of the client class and the use of it across the configuration are optimized into a single configuration entry.