[kea-dev] Some thoughts about client classification

Tomek Mrugalski tomasz at isc.org
Fri Sep 25 13:59:29 UTC 2015


On 21.09.2015 21:08, Stephen Morris wrote:
> Creation of client class
> ---
> Client classes are defined in a clause within the "Dhcp4" or "Dhcp6"
> blocks, and contain the name of the class and a single "test" clause,
> that defines the criteria for inclusion in that class, e.g.
> 
> "client-classes": [
>     {
>         "name": "class-name",
> 	"test": [ selector, op, value ]
>     },
>     {
>         "name": "class-name",
>         "test": [ selector, op, value ]
>     }...
> ]
> 
> The "test" component is an array giving the name of the selector in the
> packet, a comparison operation, and a comparison value.  To keep things
> simple, everything is done as string comparisons. This means that for a
> first pass:
> 
> * The values of integers are converted to base 10 string format before
> being checked.
> * IP addresses are converted to presentation format.
> * Boolean values are converted to "true" or "false".
> * Binary data is converted to a hexadecimal string.
This should be a generic mechanism for any class to text representation.

> "selector" is either the name of a field in the packet or the name of an
> option carried by the packet.  If an option is an array, then the
> elements of the array are concatenated together (possibly separated by a
> separator such as "/" or ";") before being used in the comparison. If a
> substring is required, that is included in the name of the selector by
> suffixing it with "[start-offset:end-offset]".
> 
> "op" is one of: "lt", "le", "eq", "ne", "ge", "gt" or "match".  The
> first six are self-explanatory and compare the string derived from the
> option with the value.  "match" checks if the option string matches the
> regular expression.
> 
> "value" is the value to compare against.
This will work only for very basic case and is not extensible to more
advanced cases. To be more specific let me give a very concrete example.

In cable networks, there's an essential need to distinguish if the
device is a cable modem or something behind it. To test it, the DHCP
server must compare hw address of the packet with value of subpoption
1023 in vendor information option with enterprise-id=4461, which itself
it stored in RAI option put there by relay (CMTS). In logical terms,
that looks more or less like this:

rai && rai.suboption(vivso) && (rai.suboption(vivso).enterprise-id ==
4461) && (rai.suboption(vivso).suboption(1023) == hwaddr)

Both the syntax and format proposed is inadequate. It has a number of
other flaws. It does not cover unary operators (I want to assign clients
to a class if option X exists or does not exist). It does not cover (and
is not extensible for) any boolean operators.

> Kea searches the "client-classes" array testing the packet against each
> element of the array in order.  The first clause that matches sets the
> class of the packet to the name given. If no clause matches, the packet
> is unclassified (i.e. its class name is the empty string "").
That is incorrect as well. Packet can belong to zero or more classes.
Sure, for now we assign only up to one class, but the structures and
methods are prepared for handling multiple classes. In particular, cable
networks are especially suitable for this. You can have cable_modems
class, but also arris, arris_model123 etc. There's very good reason for
such classes - each type of cable modem may require a different boot
parameters. John said that sometimes even a different firmware version
requires some tweaks.

-----------------------------------
Here's my view on client classification:

1. I agree with the "everything evaluates to string" approach
All options need to be convertable to strings. We already have
Option::toText(). Unfortunately, its format is intended for good
readability. Therefore we'll need a replacement. Let's call it
tentatively toString() (we can come up with a better name). We should
implement a generic version in base Option class and also for selected
few option types that we consider essential for 1.0. That will be
extended in upcoming releases.

2. Class definition. This is something that can start simple, but will
eventually evolve to complex boolean expressions, including logical sum,
alternative, parentheses and negations. For now, we can cover
just simple operators, as Stephen had suggested, but the syntax must not
be a limiting factor here. In my opinion, we should have an expression
that is being evaluated. That's the most flexible and extensible approach.

For now, I think we should support the followign operations:
== - matches when values are equal
!= - matches when values are different
>, >=, <=, < - normal string comparisons (note: 02 < 1 )

The proposed way to refer to options by their name is also a poor
choice. For now, we'll cover only options. But soon you'll want to cover
additional information, like packet fields (e.g. chaddr), but also other
meta-information (e.g. source IP address, network interface, relay IP
address, number of relays, specific option X from relay Y etc.).
Therefore options should be referred to as option[number] or
option[option-name]. This is essential especially in DHCPv6, which is
nested and each relay adds another encapsulation level. In cascade
relays scenario, each relay may add a different instance of the same
option. One day we must be able to distinguish between them.

For 1.0 we should have a generic way to extract option value that works
well for few (integer, address, string, fqdn) option types. We also
should add a couple fields. chaddr is the obvious one, but also likely
giaddr (to classify based on relay addresses in v4 and maybe htype. We
will add more support for other fields in upcoming releases.

Two important aspects to consider here are regular expressions and
binary/hex format. One day (after 1.0) we should implement an operator
regexp(value, regexp) that would evaluate a regular expression. Another
would be something like base converter in ISC DHCP. That's something
that we'll add later.

Also, for more complex options, it would be very useful to extract
specific fields. something like fqdn.name, fqdn.n_bit or similar. This
is something I think can be done after 1.0.

For 1.0 we can probably live with a simple A operator B, but for the
next release we should extend it to cover parentheses and more complex
expressions. To handle arbitrary complex expressions, we'll need to
implement reverse polish notation, but it's not difficult.

Finally, the design does not cover one important thing: an ability to
use result of the expression as a class. Right now we extract content of
vendor-class as use it as VENDOR_CLASS_[content]. There's C++ code that
does that. Something similar should be doable by users, without a need
to write C++ code.

To wrap it up, in my opinion the class definition would look like this:

 "client-classes": [
     {
         "name": "class-name1",
 	 "test": "(any-expression-here)"
     },
     {
         "name": "class-name",
         "test": "(any-expression-here)"
     }...
 ]

Examples:
 "test": "chaddr == 08002b02deadbeef"
... matches if the hardware address (in the "chaddr" field) is that
specified.

  "test": ["option[vendor-class-identifier] contains "foo"]
Matches if the vendor class identifier option contains the string "foo"
somewhere in it. (Previous proposal was to use 'match' which may be
easily mistunderstood as "match in its entirety".

> 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.
There are several levels on which client classification can be used:
1. subnet - in subnet X allow only clients from class Y
2. pool - in pool X allow only clients from class Y
3. options - use options X,Y,Z for clients from class Y
4. host - for a given host reservation, automatically assign to class X

We already have support for #1. I think it's realistic to have #2.
implemented in 1.0. #3 is also very useful, but it would require
implementing parsers for options nested in class definitions. This is
tedious work and I think we can't realistically shove it into 1.0. This
is a complex topic for testing and we don't want to rush things just
before 1.0. Hopefully lots of people will look at Kea once it reaches
1.0. We can't risk news like "kea 1.0 is broken, because classification
doesn't work".

Tomek

p.s.
We did spend a lot of time discussing client classification during last
and previous all-hands. Anyone bothered to look at our notes?



More information about the kea-dev mailing list