Kea: DHCPv4 Server Component

| Kea 3.0.0

DHCPv4 Server Component

Kea includes the "kea-dhcp4" component, which is the DHCPv4 server implementation. This component is built around the isc::dhcp::Dhcpv4Srv class which controls all major operations performed by the server such as: DHCP messages processing, callouts execution for many hook points, FQDN processing and interactions with the "kea-dhcp-ddns" component, lease allocation, system signals handling etc.

The "kea-dhcp4" component requires linking with many different libraries to obtain access to common functions like: interfaces and sockets management, configuration parsing, leases management and allocation, hooks infrastructure, statistics management etc.

The following sections walk through some of the details of the "kea-dhcp4" component implementation.

# Configuration Parser in DHCPv4

Note: parsers are currently being migrated to isc::data::SimpleParser. See Simple JSON Parser page for details.

The common configuration parsers for the DHCP servers are located in the src/lib/dhcpsrv/parsers/ directory. Parsers specific to the DHCPv4 component are located in the src/bin/dhcp4/json_config_parser.cc. These parsers derive from the common configuration parsers and customize their behavior. For example: the `Subnet4ConfigParser` is used to parse parameters describing a single subnet. It derives from the `isc::dhcp::SubnetConfigParser`, which implements the common base for both DHCPv4 and DHCPv6 subnets. The Subnet4ConfigParser implements the `initSubnet` abstract method, which creates an instance of the DHCPv4 subnet. This method is invoked by the parent class.

Some parsers for the DHCPv4 server derive from the isc::dhcp::DhcpConfigParser class directly. This is an abstract class, defining a basic interface for all configuration parsers. All DHCPv4 parsers deriving from this class directly have their entire implementation in the src/bin/dhcp4/json_config_parser.cc.

# Configuration Parser for DHCPv4 (bison)

If you are here only to learn absolute minimum about the new parser, here's how you use it:

// The following code:
json = isc::data::Element::fromJSONFile(file_name, true);
// can be replaced with this:
Parser4Context parser;
json = parser.parseFile(file_name, Parser4Context::PARSER_DHCP4);
isc::data::Element::fromJSONFile
static ElementPtr fromJSONFile(const std::string &file_name, bool preproc=false)
Reads contents of specified file and interprets it as JSON.
Definition data.cc:817

For an introduction, rationale and issues the new parser tries to address, see Configuration Parser for DHCPv6 (bison).

The code change for 5017 introduces flex/bison based parser. It is essentially defined in two files: dhcp4_lexer.ll, which defines regular expressions that are used on the input (be it a file or a string in memory). In essence, this code is being called repeatedly and each time it returns a token. This repeats until either the parsing is complete or syntax error is encountered. For detailed discussion, how they operate see Configuration Parser for DHCPv6 (bison).

# Parsing Partial Configuration in DHCPv4

See Parsing Partial Configuration in DHCPv6.

# Config File Includes

See Configuration Files Inclusion.

# Avoiding syntactical conflicts in parsers

See Avoiding syntactical conflicts in parsers.

# DHCPv4 configuration inheritance

One notable useful feature of DHCP configuration is its parameter inheritance. For example, the "renew-timer" value may be specified at a global scope and it then applies to all subnets. However, some subnets may have it overwritten with subnet specific values that takes precedence over global values that are considered defaults. The parameters inheritance is implemented by means of the "global context". The global context is represented by the isc::dhcp::ParserContext class and it holds pointers to storage of different kinds, e.g. text parameters, numeric parameters etc. When the server is parsing the top level configuration parameters it passes pointers to the storages of the appropriate kind, to the parsers being invoked to parse the global values. Parsers will store the parsed values into these storages. Once the global parameters are stored in the global context, the parsers for the nested configuration parameters are invoked. These parsers check the presence of the parameters overriding the values of the global parameters. If a value is not present, the values from the global context is used.

A good example of inheritance is the implementation of the isc::dhcp::SubnetConfigParser. The `getParam` method is used throughout the class to obtain values of the parameters defining a subnet. It first checks if the specific value is present in the local values storage. If it is not present, it uses the value from the global context.

isc::dhcp::Triplet<uint32_t>
SubnetConfigParser::getParam(const std::string& name) {
uint32_t value = 0;
try {
// look for local value
value = uint32_values_->getParam(name);
} catch (const DhcpConfigError &) {
try {
// no local, use global value
value = global_context_->uint32_values_->getParam(name);
} catch (const DhcpConfigError &) {
isc_throw(DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
}
return (Triplet<uint32_t>(value));
}
DhcpConfigError
To be removed. Please use ConfigError instead.
Definition dhcp_config_error.h:58
isc::util::Triplet
This template specifies a parameter value.
Definition triplet.h:37
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition exceptions/exceptions.h:210

Note that if the value is neither present in the local storage nor in the global context an error is signaled.

Parameter inheritance is done once, during the reconfiguration phase. Reconfigurations are rare, so extra logic here is not a problem. On the other hand, values of those parameters may be used thousands times per second, so access to these parameters must be as efficient as possible. In fact, currently the code has to only call `Subnet4::getT1()`, regardless if the "renew-timer" has been specified as a global or subnet specific value.

Debugging a configuration parser may be confusing. Therefore there is a special class called DebugParser. It does not configure anything, but just accepts any parameter of any type. If requested to commit configuration, it will print out received parameter name and its value. This class is not currently used, but it is convenient to have it every time a new parameter is added to the DHCP configuration. For that purpose it should be left in the code.

# Custom functions to parse message options

The DHCPv4 server implementation provides a generic support to define option formats and set option values. A number of options formats have been defined for standard options in libdhcp++. However, the formats for vendor specific options are dynamically configured by the server's administrator and thus can't be stored in libdhcp++. Such option formats are stored in the isc::dhcp::CfgMgr. The libdhcp++ provides functions for recursive parsing of options which may be encapsulated by other options up to any level of encapsulation, but these functions are unaware of the option formats defined in the isc::dhcp::CfgMgr because they belong to a different library. Therefore, the generic functions isc::dhcp::LibDHCP::unpackOptions4 and isc::dhcp::LibDHCP::unpackOptions6 are only useful to parse standard options whose definitions are provided in the libdhcp++. In order to overcome this problem a callback mechanism has been implemented in `Option` and `Pkt4` classes. By installing a callback function on an instance of `Pkt4`, the server may provide a custom implementation of the options parsing algorithm. This callback function will take precedence over the `LibDHCP::unpackOptions4` and `LibDHCP::unpackOptions6` functions. With this approach, the callback is implemented within the context of the server and it has access to all objects which define its configuration (including dynamically created option definitions).

Private (codes 224-254) and VSI (code 43) options are not decoded by `LibDHCP::unpackOptions4` but by isc::dhcp::Dhcpv4Srv::deferredUnpack function after classification. To make this function to perform or not deferred processing the simplest is to add or not the option code to the isc::dhcp::Pkt4::getDeferredOptions list.

# DHCPv4 Server Support for the Dynamic DNS Updates

The DHCPv4 server supports processing of the DHCPv4 Client FQDN option (RFC4702) and the DHCPv4 Host Name option (RFC2132). A client may send one of these options to convey its fully qualified or partial name to the server. The server may use this name to perform DNS updates for the client. If server receives both options in the same message, the DHCPv4 Client FQDN Option is processed and the Host Name option is ignored. If only Host Name Option is present in the client's message, it is used to update DNS.

The server may be configured to use a different name to perform DNS update for the client. In this case the server will return one of the DHCPv4 Client FQDN or Host Name Option in its response with the name which was selected for the client to indicate that this name will be used to perform DNS update.

The kea-dhcp-ddns process is responsible for the actual communication with the DNS, i.e. to send DNS update messages. The kea-dhcp4 module is responsible for generating isc::dhcp_ddns::NameChangeRequest and sending it to the kea-dhcp-ddns module. The isc::dhcp_ddns::NameChangeRequest object represents changes to the DNS bindings, related to acquisition, renewal or release of the DHCP lease. The kea-dhcp4 module implements the simple FIFO queue of the NameChangeRequest objects. The module logic, which processes the incoming DHCPv4 Client FQDN and Host Name Options puts these requests into the FIFO queue.

Todo
  Currently the FIFO queue is not processed after the NameChangeRequests are generated and added to it. In the future implementation steps it is planned to create a code which will check if there are any outstanding requests in the queue and send them to the kea-dhcp-ddns module when server is idle waiting for DHCP messages.

When client gets an address from the server, a DHCPv4 server may generate 0, 1 or 2 NameChangeRequests during single message processing. Server generates no NameChangeRequests if it is not configured to update DNS or it rejects the DNS update for any other reason.

The server may generate one NameChangeRequest in the case where a client acquires a new lease or it releases an existing one. In the former case, the NameChangeRequest type is CHG_ADD, which indicates that the kea-dhcp-ddns module should add a new DNS binding for the client, and it is assumed that there is no DNS binding for this client already. In the latter case, the NameChangeRequest type is CHG_REMOVE to indicate to the kea-dhcp-ddns module that an existing DNS binding should be removed from the DNS. The binding consists of the forward and reverse mapping. The server may only remove the mapping which it had added. Therefore, the lease database holds the information which updates (no update, reverse only update, forward only update or both reverse and forward update) have been performed when the lease was acquired or renewed. Server checks this information to make a decision which mapping it is supposed to remove when lease is released.

The server may generate two NameChangeRequests in the case where client is renewing a lease and it already has a DNS binding for that lease. The DHCPv4 server will check if there is an existing lease for the client which has sent a message and if DNS Updates had been performed for this lease. If the notion of client's FQDN changes, comparing to the information stored in the lease database, the DHCPv4 has to remove an existing binding from the DNS and then add a new binding according to the new FQDN information received from the client. If the client's FQDN information (including the client's name and type of update performed) doesn't change comparing to the NameChangeRequest is not generated.

The DHCPv4 Client FQDN Option comprises flags which communicate to the server what updates (if any) client expects the server to perform. Server may be configured to obey client's preference or to do FQDN processing in a different way. If the server overrides client's preference it will communicate it by sending the DHCPv4 Client FQDN Option in its responses to a client, with the appropriate flags set.

Todo
  Note: the current implementation doesn't allow configuration of the server's behavior with respect to DNS Updates. This is planned for the future. The default behavior is constituted by the set of constants defined in the (upper part of) dhcp4_srv.cc file. Once the configuration is implemented, these constants will be removed.

# DHCPv4 Client Classification

The Kea DHCPv4 currently supports two classification modes: simplified client classification (that was an early implementation that used values of vendor class option) and full client classification.

## Simple Client Classification in DHCPv4

The Kea DHCPv4 server supports simplified client classification. It is called "simplified", because the incoming packets are classified based on the content of the vendor class (60) option. More flexible classification was added in 1.0 and is described in Full Client Classification in DHCPv4 .

For each incoming packet, isc::dhcp::Dhcpv4Srv::classifyPacket() method is called. It attempts to extract content of the vendor class option and interpret as a name of the class. For now, the code has been tested with two classes used in cable modem networks: eRouter1.0 and docsis3.0, but any other content of the vendor class option will be interpreted as a class name.

In principle any given packet can belong to zero or more classes. As the current classifier is very modest, there's only one way to assign a class (based on vendor class option), the ability to assign more than one class to a packet is not yet exercised. Nevertheless, there is such a possibility and it will be used in a near future. To check whether a packet belongs to given class, isc::dhcp::Pkt4::inClass method should be used.

The code sometimes refers to this classification as "simple" or 'built-in", because it does not require any configuration and thus is built into the server logic. @subsection dhcpv4ClassifierFull Full Client Classification in DHCPv4 Kea 1.0 introduced full client classification. Each client class consists of a name and an expression that can be evaluated on an incoming packet. If it evaluates to true, this packet is considered a member of said class. Class definitions are stored in isc::dhcp::ClientClassDef objects that are kept in isc::dhcp::ClientClassDictionary. This is convenient as there are often multiple classes associated with a given scope. As of Kea 1.0, the only supported scope is global, but there are plans to support class definitions that are subnet specific. Client classification is done in isc::dhcp::Dhcpv4Srv::classifyPacket. First, the old "built-in" (see Simple Client Classification in DHCPv4) classification is called. Then the code iterates over all class definitions and for each class definition it calls isc::dhcp::evaluate, which is implemented in libeval (see libkea-eval - Expression Evaluation and Client Classification Library). If the evaluation is successful, the class name is added to the packet (by calling isc::dhcp::pkt::addClass).

If packet belongs to at least one class, this fact is logged. If there are any exceptions raised during class evaluation, an error is logged and the code attempts to evaluate the next class.

## How client classification information is used in DHCPv4

The classification code has been revamped in Kea 1.1. The old code that did specific things for cable modems can now be achieved with the general classification code. Users can simply define the class with next-address and/or filename in it.

It is possible to define class restrictions in subnet, so a given subnet is only accessible to clients that belong to a given class. That is implemented as isc::dhcp::Pkt4::classes_ being passed in isc::dhcp::Dhcpv4Srv::selectSubnet() to isc::dhcp::CfgMgr::getSubnet4(). Currently this capability is usable, but the number of scenarios it supports is limited.

Finally, it is possible to define client class-specific options, so clients belonging to a class foo, will get options associated with class foo. This is implemented in isc::dhcp::Dhcpv4Srv::buildCfgOptionList.

# Configuration backend for DHCPv4

Earlier Kea versions had a concept of backends, which were implementations of different ways how configuration could be delivered to Kea. It seems that the concept of backends didn't get much enthusiasm from users and having multiple backends was cumbersome to maintain, so it was removed in 1.0.

# Reconfiguring DHCPv4 server with SIGHUP signal

Online reconfiguration (reconfiguration without a need to restart the server) is an important feature which is supported by all modern DHCP servers. When using the JSON configuration backend, a configuration file name is specified with a command line option of the DHCP server binary. The configuration file is used to configure the server at startup. If the initial configuration fails, the server will fail to start. If the server starts and configures successfully it will use the initial configuration until it is reconfigured.

The reconfiguration request can be triggered externally (from other process) by editing a configuration file and sending a SIGHUP signal to DHCP server process. After receiving the SIGHUP signal, the server will re-read the configuration file specified at startup. If the reconfiguration fails, the server will continue to run and use the last good configuration.

The signal handler for SIGHUP (also for SIGTERM and SIGINT) are installed in the kea_controller.cc using the `isc::util::SignalSet` class. The `isc::dhcp::Dhcp4Srv` calls `isc::dhcp::Daemon::handleSignal` on each pass through the main loop. This method fetches the last received signal and calls a handler function defined in the kea_controller.cc. The handler function calls a static function `configure` defined in the kea_controller.cc.

The signal handler reconfigures the server using the configuration file specified at server startup. The location of this file is held in the `Daemon` class.

# Other DHCPv4 topics

For hooks API support in DHCPv4, see The Hooks API for the DHCPv4 Server.

For a description of how DHCPv4-over-DHCPv6 is implemented, see DHCPv4-over-DHCPv6 DHCPv4 Server Side.