Kea 2.7.5
DHCP-DDNS Component

Kea is capable of sending dynamic DNS updates to DNS Servers, based on lease changes made by Kea's DHCP servers. When DDNS updating is enabled, the DHCP servers generate requests to update DNS as they make lease changes. These requests, implemented by isc::dhcp_ddns::NameChangeRequest (NCR), are sent to a separate process, informally known as D2. D2 processes these requests by carrying out DDNS conversations with the appropriate DNS servers such that they update the DNS data.

The design documentation for D2 can be found here: D2 Design.

The implementation is split in several libraries which can be used separately (linked only when required):

  • src/lib/dns (libkea-dns++) - contains classes and enumerations which define DNS update message, message content, EDNS, RData, RR Types, RCODEs, OPCODEs, DNS Name, TSIG key and TSIG record, exception types, etc.
  • src/lib/dhcp_ddns (libkea-dhcp_ddns) - contains classes to send and receive, encapsulate and decapsulate DNS messages, etc.
  • src/lib/d2srv (libkea-d2srv) - contains classes to handle NCR transactions, the DNS client implementation, statistics, configuration parser and manager, logger, DNS Zone, etc.
  • src/bin/d2 (kea-dhcp-ddns) - contains classes which handle message queues, D2 process and D2 controller internals, etc.

This document contains several UML diagrams, and a few conventions used within these diagrams are worth noting:

  • If a class is appearing on class diagram for the first time (within this document) its background color will be yellow. If it has been presented already, its background color will be blue and/or its details may not be shown.
  • Associations between classes in D2 are implemented in most cases via typedefs, sometimes through a small chain of typedefs. These typedefs are shown for accuracy but are unimportant to a general discussion.

D2's CPL Derivations

D2's core application classes are DDNS-specific derivations of the CPL as show in the diagram below:

D2's CPL Derivations

Configuration Management

D2's configuration management uses the same underlying mechanisms as Kea's DHCP servers. It's configuration information is organized into a hierarchical data model which is mirrored in the implementation by a hierarchy of parser classes and the runtime classes they instantiate.

D2's data model is organized as follows:

  • A set of application level values such as the D2's IP address, port
  • Two lists of "domains": one for Forward DNS and one for Reverse DNS.
    Each domain is described by a zone name and a list of DNS servers that can update that zone.
  • A list of TSIG keys for conducting signed DDNS exchanges with DNS servers

The runtime classes that embody this model are shown in the following diagram:

D2's Configuration Data Classes

The parsing classes, as one would expect, parallel the runtime classes quite closely. The parsers are named for the runtime class they instantiate and are either designed to parse a single occurrence of that class or list of that class. The parser classes are shown in the diagram below:

D2's Configuration Parsers

For more details on the parsers see src/lib/d2srv/d2_config.h.

The underlying common libraries for configuration parsing support configuration input in JSON format, that complies with a fixed set of generic constructs that may be described by a spec file (also JSON).

Request Reception and Queuing

D2 is designed to receive requests from Kea's DHCP servers, asynchronously and store them in queue to be processed. The classes responsible for this are shown in the diagram below:

Request Management Classes

D2QueueMgr is state-driven, albeit with a very simple state model. The states are defined by isc::d2::D2QueueMgr::State (see src/bin/d2/d2_queue_mgr.h).

Update Execution

The DDNS protocol can lead to a multiple step conversation between the updater and the DNS server to update entries for a single client. In addition, NameChangeRequests can request changes be made for both forward and reverse DNS. In order to carry out the appropriate conversation, D2 wraps each request in a stateful transaction.

Working such transactions serially can be inefficient, especially if those requests involve different DNS servers. Therefore, D2 has been designed to work on more than one transaction at a time by creating and managing a list of transactions.

The classes which are responsible for carrying out this work are shown in the following diagram:

Update Execution Classes
  • isc::d2::D2UpdateMgr owned by D2Process, orchestrates the fulfillment of each request by managing the execution of its transaction. Its high level method isc::d2::D2UpdateMgr::sweep() is meant to be called whenever IO events occur (see src/bin/d2/d2_update_mgr.h). The following steps are performed each time the method is called:
    • Any transaction which has been completed, is logged and culled from the transaction list.
    • Start a new transaction for the next queued request (if any).
  • isc::d2::NameChangeTransaction - abstract state-driven class which carries out the steps necessary to fulfill a single request. Fulfilling a request is achieved as IO events in response it DDNS requests drive the transaction through its state model (see src/lib/d2srv/nc_trans.h). The type of transaction is based on the nature of the request, this is discussed further on Transactions section.
  • isc::d2::DNSClient - an asynchronous worker which atomically performs a single DDNS packet exchange with a given server, providing the response via a callback mechanism. Each time a transaction's state model calls for a packet exchange with a DNS server, it uses an instance of this class to do it (see src/lib/d2srv/dns_client.h).
  • isc::d2::D2UpdateMessage - container for sending and receiving DDNS packets (see src/lib/d2srv/d2_update_message.h).

Main Event Loop

Now that all of the primary components have been introduced it is worth while discussing D2's main event loop. As mentioned earlier D2 is constructed around the CPL which is designed to be driven by asynchronous IO processed by a common IO service thread (isc::asiolink::io_service). Any IO that needs to be performed by the application thread uses this service to do so. This organizes the IO event processing into a single event loop centered around the service. (This does not preclude spinning off worker threads to conduct other tasks, with their own io_service instances). D2's main event loop, implemented in isc::d2::D2Process::run() may be paraphrased as follows:

As long as we should not shutdown repeat the following steps:
1. Check on the state of the request queue. Take any actions necessary
regarding it. For example, it may be in the process of shutting down
its listener prior to applying a configuration change.
2. Give update manager a "time slice" to cull finished transactions and
start new ones.
3. Block until one or more of the following IO events occur:
a. NCR message has been received
b. Transaction IO has completed
c. Interval timer expired
d. A process command has been received
e. Something has stopped the IO service (most likely a fatal error)

Transactions

There are two types of NameChangeRequests: an "Add" that is issued when DNS entries need to be added for new or updated lease, and a "Remove" that is issued when DNS entries need to be removed for obsolete or expired lease. The DDNS protocol dictates the steps that should be followed in both cases.

D2's design addresses this by calling for two types of transactions: one for adding entries and one for removing them, each with their own state model. The transaction classes are shown in the following diagram:

NameChangeTransaction Derivations

The state models for these two transactions implement DDNS with conflict resolution as described in RFC 4703.

The state model for isc::d2::NameAddTransaction is diagrammed below:

State Model for NameAddTransaction

The state model for isc::d2::NameRemoveTransaction is depicted next:

State Model for NameRemoveTransaction

State Model Implementation

D2 implements a abstract state-machine through a light weight set of classes. At construction, the model's dictionary of events and states is initialized. This allows, the deriving class the ability to bind a method of its choosing to each state as that state's handler. Each handler contains the knowledge of how to respond to the "posted" event and including posting other events and transitioning to other states.

Executing the model consists of beginning at the current state with the posted event and continuing until the model needs to wait for an IO-based event or it has reached the end of the state model. These classes will likely move to a common library.

State Model Classes
  • isc::util::StateModel - provides the mechanics for executing a state model described by a dictionary events and states. It provides methods to:
    • initialize the model - constructs the dictionary of events and states.
    • start the model - sets the model to its initial state, posts a "start" event and starts the model "running".
    • run the model - process the posted event (if one) until the model reaches a wait state or reaches the end state.
    • transition from one state to another.
  • isc::d2::Event - Defines an event used to stimulate the model.
  • isc::d2::State - Defines a state with the model, each state has a handler method that is executed upon transition "into" that state from another state.
  • isc::d2::LabeledValue - An abstract that associates a integer value with a text label.
  • isc::d2::LabeledValueSet - A collection of LabeledValues for which the integer values must be unique.

Transaction Execution Example

The following sequence chart depicts the typically sequence of events that occur when D2UpdateMgr creates and starts executing a transaction:

Transaction Execution Sequence