|
|
[[_TOC_]]
|
|
|
|
|
|
# History
|
|
|
|
|
|
The concept of spawning classes was used in ISC DHCP.
|
|
|
|
|
|
Here is an excerpt from `man dhcpd.conf`:
|
|
|
|
|
|
SPAWNING CLASSES
|
|
|
|
|
|
It is possible to declare a spawning class. A spawning class is a class
|
|
|
that automatically produces subclasses based on what the client sends. The
|
|
|
reason that spawning classes were created was to make it possible to create
|
|
|
lease-limited classes on the fly. The envisioned application is a cable-
|
|
|
modem environment where the ISP wishes to provide clients at a particular
|
|
|
site with more than one IP address, but does not wish to provide such
|
|
|
clients with their own subnet, nor give them an unlimited number of IP ad‐
|
|
|
dresses from the network segment to which they are connected.
|
|
|
|
|
|
Many cable modem head-end systems can be configured to add a Relay Agent
|
|
|
Information option to DHCP packets when relaying them to the DHCP server.
|
|
|
These systems typically add a circuit ID or remote ID option that uniquely
|
|
|
identifies the customer site. To take advantage of this, you can write a
|
|
|
class declaration as follows:
|
|
|
|
|
|
class "customer" {
|
|
|
spawn with option agent.circuit-id;
|
|
|
lease limit 4;
|
|
|
}
|
|
|
|
|
|
Now whenever a request comes in from a customer site, the circuit ID option
|
|
|
will be checked against the class's hash table. If a subclass is found
|
|
|
that matches the circuit ID, the client will be classified in that subclass
|
|
|
and treated accordingly. If no subclass is found matching the circuit ID,
|
|
|
a new one will be created and logged in the dhcpd.leases file, and the
|
|
|
client will be classified in this new class. Once the client has been
|
|
|
classified, it will be treated according to the rules of the class, includ‐
|
|
|
ing, in this case, being subject to the per-site limit of four leases.
|
|
|
|
|
|
The use of the subclass spawning mechanism is not restricted to relay agent
|
|
|
options - this particular example is given only because it is a fairly
|
|
|
straightforward one.
|
|
|
|
|
|
# Requirement
|
|
|
|
|
|
It is desirable that Kea achieves the same level of scalability in terms of how many classes can be
|
|
|
lease limited, or rate limited, or affected by any other feature that can be attributed to a class
|
|
|
and involves class counting. Ideally, like in ISC DHCP, this would be possible with a single client
|
|
|
class definition.
|
|
|
|
|
|
# Abstract
|
|
|
|
|
|
Spawning classes always have test expression because the test expression is instrumental in
|
|
|
spawning. If a class does not have a test expression, it is considered a regular class.
|
|
|
|
|
|
______________
|
|
|
:speech_left: razvan:
|
|
|
Implementation detail: a new flag is required, so that the evaluateString is called on the spawning class instead of the regular evaluateBool called on the regular class.
|
|
|
|
|
|
:speech_left: andrei: I saw it as an abstraction called `evaluate()` that allows both bool and
|
|
|
string as result types. The result type is precisely what differentiates
|
|
|
between the two and only after they are evaluated you will know what you are
|
|
|
dealing with. How will you know what value to give the flag in your proposal
|
|
|
if you don't know if the class is regular or spawning?
|
|
|
______________
|
|
|
|
|
|
The test expression in spawning classes always has a string result as opposed to a boolean result,
|
|
|
as is the case with regular classes. If the result is anything other than string or boolean, an
|
|
|
error is thrown at configuration time or, respectively, at inserting the class in the backend, just
|
|
|
as it did before. Configuring Kea with a client class that has a string test expression will be
|
|
|
allowed instead of being met with an error.
|
|
|
|
|
|
Each distinct string resulted from the test expression, as packets are being classified, is a client
|
|
|
class of its own and is called a subclass.
|
|
|
|
|
|
A packet will only have a single subclass per evaluation. Having multiple subclasses from
|
|
|
a spawning class may be possible with extensions to the expression evaluator and would require the
|
|
|
test result to be a list of strings, but that, if necessary, should only be the subject of a future
|
|
|
iteration.
|
|
|
|
|
|
The empty string does not represent a valid subclass, but instead is considered to be the result of
|
|
|
an unsuccessful evaluation of the test expression e.g. as in the case of option 123 not existing for
|
|
|
`"test": option[123].hex`. In cases such as this, the subclass cannot be generated and packet
|
|
|
processing will simply continue without one.
|
|
|
|
|
|
Since the result of a spawning class can change over time, on re-evaluation, all subclasses are
|
|
|
deleted. The rest is the same as with regular classes. Classes definitions are iterated through and
|
|
|
are re-evaluated, resulting in new subclasses which are assigned to the packet.
|
|
|
|
|
|
After evaluation of a spawning class, a packet is assigned two classes: the spawning class and the
|
|
|
subclass.
|
|
|
|
|
|
______________
|
|
|
:speech_left: razvan:
|
|
|
There is no need for a distinction between spawning class and the resulting subclass. The Subclass is just the evaluation (instantiation) of the template class.
|
|
|
______________
|
|
|
|
|
|
Both spawning classes and subclasses can be used as part of other test expressions e.g. `"test": "member(subclass)"`.
|
|
|
|
|
|
To avoid conflict names with regular classes, conflicts between subclasses from the same spawning
|
|
|
class or from different spawning classes, the subclass should be prefixed with
|
|
|
`SPAWNING_<spawning-class-name>_SUBCLASS_`.
|
|
|
|
|
|
______________
|
|
|
:speech_left: razvan:
|
|
|
There is no need to avoid conflicts. The subclasses resulting in the same exaluated expression are treated the same. If a distinction is needed between two spawning classes, the 'concat' token or '+' operator can be used in the expression to add a unique identifier to the respective class
|
|
|
|
|
|
:speech_left: andrei: Agreed. The paragraph above should be dropped.
|
|
|
______________
|
|
|
|
|
|
The usual case involves referencing the class by the spawning class' name, but for convenience, and
|
|
|
if nobody needs to go out of their way to do so, subclasses should also be referenceable. This saves
|
|
|
the user from defining a second class e.g. using `SPAWNING_interface_class_SUBCLASS_eth0` directly
|
|
|
instead of creating another client class definition
|
|
|
`{ "name": "ETH0_CLASS", "test": "pkt.iface == 'eth0'" }`.
|
|
|
|
|
|
No changes are required in the configuration backend schema.
|
|
|
|
|
|
# Implementation Design
|
|
|
|
|
|
Implementation-wise, the same data types that are already used for a regular class can be used for a
|
|
|
spawning class: `ClientClassDef` for the definition and `typedef std::string ClientClass;` for the
|
|
|
runtime reference. The subclasses never have a definition, so they only make use of the latter.
|
|
|
|
|
|
However, features that benefit from class spawning should affect subclasses, while the definition
|
|
|
should reference the spawning class to facilitate scalable configuration.
|
|
|
|
|
|
## Choice 1. Separate associative container
|
|
|
|
|
|
This requires the packet to carry both in a separate associative container such as
|
|
|
`std::unordered_map<ClientClass, ClientClass>`. One unfortunate consequence is that
|
|
|
spawning-benefitting features should be tuned to lookup by spawn classes and to effectively use the
|
|
|
subclass further. They can hold the key-value pair if they need both classes. For example,
|
|
|
the limit manager should lookup spawning classes for any limit definitions and pass the subclass to
|
|
|
`LimitManager::checkLeaseLimits`. Deleting all the subclasses is as easy as `.clear()`.
|
|
|
|
|
|
Pros:
|
|
|
- Best performance
|
|
|
- Few changes required
|
|
|
- Robust. Nicely isolated
|
|
|
|
|
|
Cons:
|
|
|
- Inconsistent implementation for regular vs spawning
|
|
|
- Not transparent. The map stands out like a thorn on a rose.
|
|
|
|
|
|
## Choice 2. Deduce the name of spawning class out of the subclass' name
|
|
|
|
|
|
Considering that subclasses are required to contain the name of the spawning class in the form of
|
|
|
`SPAWNING_<spawning-class-name>_SUBCLASS_<subclass-name>`, the latter can be deduced when needed.
|
|
|
The advantage is that subclasses can be associated with the packet the same way as with regular
|
|
|
client classes through the use of the `ClientClasses` multi index container. This is more
|
|
|
transparent and more robust as it has a high chance of being more easily integrated with components
|
|
|
that interact with client classes. Spawning-benefitting features are not excused of doing extra
|
|
|
work. They need to check each class name for containing keywords `SPAWNING_` and `SUBCLASS_` and
|
|
|
deduce the spawning class name. Deleting all the subclasses requires the same work.
|
|
|
|
|
|
Pros:
|
|
|
- Fewest changes required
|
|
|
- Most transparent. Integrates/scales well with any other client class feature
|
|
|
|
|
|
Cons:
|
|
|
- Inconsistent implementation for regular vs spawning
|
|
|
- Poor performance caused by string checking for each class name
|
|
|
- Not robust. Prevents people from using class names of a certain format
|
|
|
|
|
|
## Choice 3. Key-value store for all classes
|
|
|
|
|
|
`ClientClasses` currently acts as a set data type. If it would be changed to act as a map holding
|
|
|
classes indexed by classes where:
|
|
|
1. For regular classes, the key and value would be the same.
|
|
|
2. For spawning classes, the spawning class would be key and the subclass would be value.
|
|
|
|
|
|
Then access to classes would be consistent between regular classes and spawning classes. Deleting
|
|
|
all subclasses can be improved through a boost index, but is still costly.
|
|
|
|
|
|
Pros:
|
|
|
- Consistent implementation for regular vs spawning
|
|
|
- Transparent. Integrates/scales well with any other client class feature
|
|
|
|
|
|
Cons:
|
|
|
- Lots of code changes
|
|
|
- Poor performance caused by unnecessary map lookups for regular classes
|
|
|
|
|
|
______________
|
|
|
:speech_left: razvan:
|
|
|
There is no need to use a mapping between the spawning class and the subclass (instance).
|
|
|
After evaluating the expression (by calling evaluateString), the subclass is added to the packet.
|
|
|
|
|
|
:speech_left: andrei: How do you scalably apply a limit to all the interface subclasses in the
|
|
|
example below? Applying it to `eth0`, `eth1` and so on, is not what we want
|
|
|
because this could have been achieved with regular classes. In other words,
|
|
|
how does LimitManager know how to reference it by the superclass, but apply it
|
|
|
to the subclass?
|
|
|
______________
|
|
|
|
|
|
# Configuration Examples
|
|
|
|
|
|
Spawning classes can be defined and used in file configurations:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"Dhcp6": {
|
|
|
"client-classes": [
|
|
|
{
|
|
|
"name": "interface_class",
|
|
|
"test": "pkt.iface",
|
|
|
"user-context": {
|
|
|
"limits": {
|
|
|
"address-limit": 2,
|
|
|
"prefix-limit": 1,
|
|
|
"rate-limit": "1000 packets per second"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
"subnet6": [
|
|
|
{
|
|
|
"require-client-classes": ["SPAWNING_interface_class_SUBCLASS_eth0"],
|
|
|
"subnet": "2001:db8::/64"
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Spawning classes can be defined in the configuration backend:
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"arguments": {
|
|
|
"client-classes": [
|
|
|
{
|
|
|
"name": "spawning-class",
|
|
|
"test": "pkt.iface",
|
|
|
"user-context": {
|
|
|
"limits": {
|
|
|
"address-limit": 2,
|
|
|
"prefix-limit": 1,
|
|
|
"rate-limit": "1000 packets per second"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
"server-tags": [
|
|
|
"ALL"
|
|
|
]
|
|
|
},
|
|
|
"command": "remote-classX-set",
|
|
|
"service": [
|
|
|
"dhcpX"
|
|
|
]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
In both examples, the limits apply per subclass, not per spawning class.
|
|
|
|
|
|
______________
|
|
|
:speech_left: razvan:
|
|
|
By using spawning classes, the limits feature can be easily configured, but if using the class at subnet or pool level, the exact value of the evaluated spawning class (instance) must be used. This means that if there are N possible evaluation results, if all of them are used to select a specific subnet or pool, then N individual value must be set, one at the respective level for each configuration element.
|
|
|
______________
|
|
|
|
|
|
# Follw-ups
|
|
|
|
|
|
* 2022-Sep-08: https://pad.isc.org/p/kea-template-classes |
|
|
\ No newline at end of file |