|
|
# DHCP DDNS DESIGN
|
|
|
|
|
|
Note - this document could use markup work to make the TOC work.
|
|
|
|
|
|
DHCP-driven DNS Updates (name changes) will be carried out by a new module. For ease of discussion (and typing) this module is code named "D2" (1 D for DHCP and another D for DDNS). It will be constructed as a Kea component. The document is structured as follows:
|
|
|
|
|
|
* [#point1 B10-D2 Components]
|
|
|
* [#point2 B10-D2 Classes]
|
|
|
* [#point3 DNS Update Request Initiation]
|
|
|
* [#point4 D2 Event Processing]
|
|
|
* [#point5 NameAddTransaction State Model]
|
|
|
* [#point6 NameRemoveTransaction State Model]
|
|
|
* [#point7 Miscellaneous]
|
|
|
* [#point8 Addendum: PersistenceMgr Design]
|
|
|
|
|
|
|
|
|
## B10-D2 Components [=#point1]
|
|
|
|
|
|
The following diagram depicts the server and its components:
|
|
|
|
|
|
### B10-D2 Component Diagram
|
|
|
|
|
|
![ddns_components.4.svg](/uploads/35049cef0cfaa1924e41213fb476bbc4/ddns_components.4.svg)
|
|
|
|
|
|
1. D2Controller-D2Process - The primary or parent component, responsible for startup, instantiating sub-components, common resources, and shutdown.
|
|
|
|
|
|
2. !QueueMgr - Maintains a queue of name changes to be be processed, and provide services for querying the queue.
|
|
|
|
|
|
3. !UpdateMgr - Manages the selection and processing of name changes; it is the primary driver of the system.
|
|
|
|
|
|
4. !PersistenceMgr - Provides persistent storage services to !QueueMgr for persisting the name change queue across restarts.
|
|
|
|
|
|
5. !CfgMgr - Responsible for interfacing with BIND10 configuration API; provides access to configuration information to the other components.
|
|
|
|
|
|
6. NameChangeIPC - Subsystem which provides the ability to send and receive !NameChangeRequests via interprocess communication.
|
|
|
|
|
|
7. RDBMS - Subsystem which provides persistent storage of data. This may be MySQL, Memfile, or potentially some other RDBMS.
|
|
|
|
|
|
8. isc::asiolink - Bind10 library which provides IP socket IO.
|
|
|
|
|
|
|
|
|
## B10-D2 Classes [=#point2]
|
|
|
|
|
|
The following diagram shows the classes which make up the components of the server:
|
|
|
|
|
|
### B10-D2 Class Diagram
|
|
|
|
|
|
![overview_classes.5.svg](/uploads/61eec6635dcd6244e02b498b929ea817/overview_classes.5.svg)
|
|
|
|
|
|
D2Process is a singleton which provides startup and shutdown services and parenting all of the sub-component objects. D2Controller a wrapper class for D2Process that allows the process to function as a B10 component. This is pattern is similar to the approach used in B10-Dhcp4 and B10-Dhcp6 servers.
|
|
|
|
|
|
D2CfgMgr is a singleton which provides support for the BIND10 configuration API and services for accessing configuration information. It will provide one or more services for matching DNS servers to name changes based on as a various criteria such as:
|
|
|
1. Match by zone
|
|
|
2. Match by explicit list
|
|
|
3. Custom Matching "hook"?
|
|
|
|
|
|
!QueueMgr, is a singleton that will maintain a list of requests for name changes as a queue of !NameChangeRequests. !NameChangeRequests will emanate from the DHCP servers for lease grants, renewals, releases, and expirations (Note expiration driven changes may emanate from a separate house-keeping process). Each request will contain the following information:
|
|
|
|
|
|
1. FQDN - Fully qualified domain name (specified by the client or generated by the DHCP server).
|
|
|
2. IP Address - IPv4 or IPv6 address assigned to the lease.
|
|
|
3. DHCID - Unique client identifier generated by the DHCP server.
|
|
|
4. Type of Change - "Add" if the mapping is new or updated, or "Remove" to delete the mapping.
|
|
|
5. Forward Change Flag - If true the mapping change should be sent to the forward DNS server.
|
|
|
6. Reverse Change Flag - If true, the mapping change should be sent to the reverse DNS server.
|
|
|
7. Lease TTL - The lease duration.
|
|
|
|
|
|
The NameChangeIPC subsystem will consist of UDP socket-based messaging system that provides a sender class, !NameChangeSender for sending messages and a listener interface, !NameChangeListener, for consuming messages. DHCP servers will use a !NameChangeSender to send name change request messages. !QueueMgr will implement the !NameChangeListener interface to receive the requests. The message flow will be one-way from !NameChangeSenders to the !QueueMgr. Senders will not receive acknowledgements or request outcomes. The initial implementation will send the messages across the wire using JSON. Supporting message transport in JSON provides the following advantages:
|
|
|
|
|
|
1. The messages are more open and can be handled in languages other than C++ such as Python. This will make it far easier to test D2 as well as permitting customers to readily create their own consumers.
|
|
|
|
|
|
2. The messages are human readable which for diagnostics purposes is very helpful, certainly during initial development.
|
|
|
|
|
|
The exact JSON layout is left as an implementation detail. It will be important, however, for NameChangeIPC to offer alternative message formatting in a manner that abstracts it away from its users. In high load environments it may be beneficial to handle the messages in a binary form.
|
|
|
|
|
|
!QueueMgr will provide one or more services for selecting entries from the queue. These services will be used by the !UpdateMgr for selecting name changes to process. Finally, !QueueMgr is responsible for persisting the queue contents across reboots via the !PersistenceMgr.
|
|
|
|
|
|
!UpdateMgr, is a singleton which is responsible for fulfilling the name change requests. It will use services provided by !QueueMgr to select and dequeue name change requests to process. Part of the selection process will need to determine if there are any changes already in progress for the same client. For instance, if a Remove is in progress for a given client, then do not dequeue an Add for the same client until the Remove is complete. Once a request has been selected and dequeued for processing, !UpdateMgr will its forward and reverse server lists using services provided by !CfgMgr. !UpdateMgr will then instantiate the appropriate !NameChangeTransaction passing it the !NameChangeRequest and server lists.
|
|
|
|
|
|
!NameChangeTransaction is an abstract, state-driven class for conducting a single name change. Each transaction may conduct multiple DNS Update request-response exchanges with DNS Servers to effect the name change. Transactions will use a !DnsClient instance to carry out these exchanges. !DnsClient provide a wrapper around the existing isc::asiolink library to perform the required socket IO. The initial implementation will support UDP only. Each transaction instance use the DNS exchange events to move it forward through its state-model.
|
|
|
|
|
|
!NameAddTransaction is the derivation for adding (or updating) a mapping to DNS, while !NameRemoveTransaction is the derivation for removing a mapping from DNS. Which class is used for a given request is determined by the request's type, "add" or "remove". Note also that a given request is either a single IPv4 mapping or single IPv6 mapping. As such, when text below refers to an "address record" or "address RR" it implies an "A" record or "AAAA" record accordingly. The state model for an add is discussed [#point5 here] and the state model for removal is discussed [#point6 here].
|
|
|
|
|
|
## DNS Update Request Initiation [=#point3]
|
|
|
|
|
|
D2 is effectively a DDNS client which undertakes DNS updates on behalf of others, primarily the DHCP servers. This design places the burden of determining when a lease change warrants an update of DNS information on the DHCP servers. This decision was based on a number of factors:
|
|
|
|
|
|
1. There are scenarios under which the DHCP servers will generate or alter the FQDN for a given client and that name must be returned to the client.
|
|
|
|
|
|
2. As the DHCP servers are masters of the lease database, they already have access to the relevant information needed to render a decision. D2 would have to either compete with DHCP servers for access to or duplicate the lease information.
|
|
|
|
|
|
3. Administrators will configure the decision to update forward/reverse/both/none on a subnet basis. This decision is communicated back to the clients and is in keeping with other DHCP server parameters.
|
|
|
|
|
|
4. The decision who will do try to do the update (client, server, both or none) is actually a result of negotiation between DHCP client and DHCP server.
|
|
|
|
|
|
5. D2 could process requests from sources that are not driven by lease events. One reasonable example is a command-line tool that would act as a replacement of nsupdate.
|
|
|
|
|
|
While the initial decision making is upstream from D2 that is not say D2 will blindly attempt every request. It will be capable of processing some requests while discarding or delaying others. DNS servers might not be available or requests may be obsolete based on other pending requests. Stated more succinctly, the DHCP servers will include only as much of the decision process required to send respond back to the client, any other decision making will be left to D2.
|
|
|
|
|
|
|
|
|
## D2 Event Processing [=#point4]
|
|
|
|
|
|
D2 is essentially asynchronous in nature and its design must account for this. The following list summarizes the asynchronous events that must be handled:
|
|
|
|
|
|
|
|
|
1. Name change request messages from DHCP servers and/or a house-keeper process. When messages are received:
|
|
|
a. They need to be added to the queue as !NameChangeRequests.
|
|
|
b. D2 must be made aware that the Queue has entries.
|
|
|
|
|
|
2. DNS message exchange events from DNS update exchanges with DNS servers. Each transaction may require multiple sends and receives. Each send and receive outcome amounts to an event. These events will be used to move each transaction through its state machine. At points in which the transaction must wait for IO, control should be yielded.
|
|
|
|
|
|
3. Control Events - Command (e.g. shutdown) and configuration events that emanate from BIND10.
|
|
|
|
|
|
4. Completed Transactions - While not an event per se, D2 does need to monitor its list of transactions, for those that have completed.
|
|
|
|
|
|
This discussion is based on the assumption, that D2 should be able to carry out more than one DNS update at a time. The design should reuse existing libraries where possible and to that end the DNS UPDATE exchanges will use the asiolink library to carry out socket IO.
|
|
|
|
|
|
In addition, the NameChangeIPC will also be socket-based. This will allow the use of the asiolink library for handling the receipt of name change request messages thus unifying the asynchronous IO and event callback mechanisms for all of the external event sources.
|
|
|
|
|
|
While the initial implementation will be single-threaded, the design is structured such that it could be extended to multi-threaded operation as a possible performance alternative. The single-threaded approach is presented first below.
|
|
|
|
|
|
### Single-Threaded Operation
|
|
|
|
|
|
In single-threaded operation, the main processing loop performs a single "blocking" wait for events from all the event sources.
|
|
|
|
|
|
Since both the NameChangeIPC and DNS exchanges will use asiolink/socket IO, D2 can use a single asiolink::io_service object to wait for and execute callbacks for both event sources.
|
|
|
|
|
|
The !QueueMgr's NameChangeIPC callback will create NCRs from the packets received and add them to the NCR Queue.
|
|
|
|
|
|
For a DNS transaction, its DNS exchange callback(s) will "post" the appropriate event to transaction's state machine. This drives the state machine forward until next IO-wait is reached or the state machine completes.
|
|
|
|
|
|
The !UpdateMgr event loop could look something like this:
|
|
|
```
|
|
|
while (!shutdown) {
|
|
|
|
|
|
// Execute all ready callbacks, or wait for one
|
|
|
if (io_service.poll() || io_service.run_one()) {
|
|
|
|
|
|
// One or more of the following has occurred
|
|
|
// a. NCR message received and has been queued
|
|
|
// b. Pending DNS IO has completed
|
|
|
// c. Interval timer expired
|
|
|
|
|
|
if (QueueMgr->queueNotEmpty()) {
|
|
|
// Dequeue next pending request
|
|
|
NCR = QueueMgr->dequeue();
|
|
|
|
|
|
// Start a new transaction for the NCR
|
|
|
instantiate_transaction(io_service, NCR);
|
|
|
}
|
|
|
|
|
|
handle_finished_transactions();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// we are here because there is no more work to do
|
|
|
// somebody stopped io_service, shutdown request likely culprit
|
|
|
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
To ensure we periodically sweep, an Interval timer will be used to generate a ready callback at regular intervals. It may well be that proper chaining of asio callback handler will reduce the loop to a single invocation of io_service::run, which would continuously process events. This will be left as an implementation detail.
|
|
|
|
|
|
|
|
|
### Multi-Threaded Operation
|
|
|
|
|
|
As with the single-threaded operation, D2 will wait for and execute callbacks for the NameChangeIPC. !QueueMgr runs in the main D2 thread, its NameChangeIPC callback will create NCRs from the packets received and add them to its NCR Queue.
|
|
|
|
|
|
Each transaction, however, runs in its own thread, using its own io_service object. Its events are never felt by the main thread. It will wait for and process its own callbacks, driving itself through its state machine.
|
|
|
|
|
|
The !UpdateMgr event loop remains effectively the same as before:
|
|
|
|
|
|
```
|
|
|
|
|
|
while (!shutdown)
|
|
|
{
|
|
|
// Execute all ready callbacks, or wait for one
|
|
|
if (io_service.poll() || io_service.run_one())
|
|
|
{
|
|
|
// One or more of the following has occurred
|
|
|
// a. NCR message received and has been queued
|
|
|
// b. Interval timer expired
|
|
|
|
|
|
if (QueueMgr->queueNotEmpty()) {
|
|
|
// Dequeue next pending request
|
|
|
NCR = QueueMgr->dequeue();
|
|
|
|
|
|
// Start a new threaded transaction for the NCR
|
|
|
instantiate threaded_transaction(NCR);
|
|
|
}
|
|
|
|
|
|
handle_finished_transactions();
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// we are here because there is no more work to do
|
|
|
// somebody stopped io_service, shutdown request likely culprit
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
Again, to ensure we periodically sweep, an Interval timer will be used to generate a ready callback at regular intervals. As before, the loop may well reduce to a single invocation of io_service.run().
|
|
|
|
|
|
The only real difference between the two event loops is in the type of transaction instantiated, and this could be readily addressed with configuration parameter value. The multi-threaded implementation will be treated as a possible future-enhancement.
|
|
|
|
|
|
|
|
|
## !NameAddTransaction State Model [=#point5]
|
|
|
|
|
|
!NameAddTransaction implements a state machine for adding (or updating) a DNS mapping. This state machine is based upon the processing logic described in RFC 4703, Sections 5.3 and 5.4. In short, it will first attempt to update the forward DNS mapping (if Forward Change Flag is true), and then the reverse DNS mapping (if Reverse Change Flag is true). The state diagram for !NameAddTransaction is shown below:
|
|
|
|
|
|
|
|
|
![name_add_state_diagram.svg](/uploads/f759709a64f105711b698197dece2fb0/name_add_state_diagram.svg)
|
|
|
|
|
|
### !NameAddTransaction State Discussion:
|
|
|
|
|
|
#### Created
|
|
|
Initial state upon instantiation.
|
|
|
|
|
|
#### Ready
|
|
|
Follows instantiation, the transaction data has been initialized and it is ready to begin execution. !UpdateMgr instigates the start transition when it is ready for the transaction to begin processing. On start, transition to !SelectingFwdServer if the Forward Change Flag is true, otherwise transition to !SelectingRevServer (It is assumed that at least one of these flags is always true).
|
|
|
|
|
|
#### !SelectingFwdServer
|
|
|
Choose the next available server from the list of forward servers and transition to !AddingFwdAddrs via !ServerSelected. If no more servers are available, transition to !ProcessAddFailed via !NoMoreServers (Note when the forward updates cannot be made, do not attempt the reverse updates).
|
|
|
|
|
|
#### !AddingFwdAddrs
|
|
|
Attempt to add a new mapping to the DNS:
|
|
|
1. Build a DNS UPDATE request with one prerequisite which asserts that the FQDN does not exist; and an update section which contains an add of an address RR that matches the DHCP binding in the request.
|
|
|
|
|
|
2. Use !DnsClient to send the DNS UPDATE to the selected server and return with a response or error.
|
|
|
|
|
|
3. Analyze the outcome
|
|
|
a. If this update succeeds then transition to !SelectingRevServer if the Reverse Change Flag is true; otherwise transition to !ProcessAddOk.
|
|
|
b. If this update fails because the name is already in use, transition to !ReplacingFwdAddrs via !FqdnInUse.
|
|
|
c. If the update fails with an IO error transition back to !SelectingFwdServer.
|
|
|
d.If the update fails for any other reason transition to !ProcessAddFailed.
|
|
|
|
|
|
#### !ReplacingFwdAddrs
|
|
|
Attempt to replace a mapping when the FQDN is in use by the same client:
|
|
|
1. Build a DNS UPDATE with two prerequisites:
|
|
|
a. An assertion that the FQDN exists,
|
|
|
b. An assertion that the FQDN has attached to it a DHCID RR that matches the DHCID given in the request
|
|
|
and an UPDATE section that contains:
|
|
|
a. A delete of any existing address RRs for the FQDN
|
|
|
b. An add of an address RR that matches the DHCP binding in the request
|
|
|
|
|
|
2. Use !DnsClient to send the DNS UPDATE to the selected server and return with a response or error.
|
|
|
|
|
|
3. Analyze the outcome:
|
|
|
a. If this update succeeds then transition to !SelectingRevServer if the Reverse Change Flag is true otherwise transition to !ProcessAddOk.
|
|
|
b. If this update fails because the name is no longer in use, transition back to !AddingFwdAddrs via !FqdnNotInUse.
|
|
|
c. If the update fails with an IO error transition back to !SelectingFwdServer (Note this effectively restarts the forward resolution portion on a different server).
|
|
|
d. If the update fails for any other reason transition to !ProcessAddFailed.
|
|
|
|
|
|
#### !SelectingRevServer
|
|
|
Choose the next available server from the list of reverse servers and transition to !ReplacingRevPtrs via !ServerSelected. If no more servers are available, transition to !ProcessAddFailed via !NoMoreServers.
|
|
|
|
|
|
#### !ReplacingRevPtrs
|
|
|
Attempt to replace the reverse DNS mapping:
|
|
|
1. Build a DNS UPDATE that contains no prerequisites and an update section which contains:
|
|
|
a. A delete of all PTR RRs associated with the request IP address
|
|
|
b. An add of a PTR RR whose data is the FQDN from the request
|
|
|
|
|
|
2. Use !DnsClient to send the DNS UPDATE to the selected server and return with a response or error.
|
|
|
|
|
|
3. Analyze the outcome:
|
|
|
a. If this update succeeds then transition to !ProcessAddOk.
|
|
|
b. If this update fails with an IO error transition back to !SelectingRevServer.
|
|
|
c.If this update fails for any other reason transition to !ProcessAddFailed.
|
|
|
|
|
|
#### !ProcessAddOk
|
|
|
Record success accordingly. Transitions to final state upon destruction.
|
|
|
|
|
|
#### !ProcessAddFailed
|
|
|
Record failure accordingly. Depending on failure conditions, it is possible we may institute some form of retry. Transitions to final state upon destruction.
|
|
|
|
|
|
## !NameRemoveTransaction State Model [=#point6]
|
|
|
|
|
|
!NameRemoveTransaction implements a state machine for removing a DNS mapping. This state machine is based upon the processing logic described in RFC 4703, Sections 5.5. In short, it will first attempt to remove the forward DNS mapping (if Forward Change Flag is true), and then remove the reverse DNS mapping (if Reverse Change Flag is true). The state diagram for !NameRemoveTransaction is shown below:
|
|
|
|
|
|
![name_remove_state_diagram.2.svg](/uploads/d2c7facc09f047fb8687a75d68612fc6/name_remove_state_diagram.2.svg)
|
|
|
|
|
|
### !NameRemoveTransaction State Discussion:
|
|
|
|
|
|
#### Created
|
|
|
Initial state upon instantiation.
|
|
|
|
|
|
#### Ready
|
|
|
Follows instantiation, the transaction data has been initialized and it is ready to begin execution. !UpdateMgr instigates the start transition when it is ready for the transaction to begin processing. On start, transition to !SelectingFwdServer if the Forward Change Flag is true, otherwise transition to !SelectingRevServer (It is assumed that at least one of these flags is always true).
|
|
|
|
|
|
#### !SelectingFwdServer
|
|
|
Choose the next available server from the list of forward servers and transition to !RemovingFwdAddrs via !ServerSelected. If no more servers are available, transition to !ProcessRemoveFailed via !NoMoreServers (Note if the forward updates cannot be made, do not attempt the reverse updates).
|
|
|
|
|
|
#### !RemovingFwdAddrs
|
|
|
Attempt to remove the address records associated with a specific DHCID:
|
|
|
|
|
|
1. Build a DNS UPDATE request with one prerequisite which asserts that DHCID RR that matches the DHCID in the request exists; and an update section which contains a deletion of the client's specific address RR.
|
|
|
|
|
|
2. Use !DnsClient to send the DNS UPDATE to the selected server and return with a response or error.
|
|
|
|
|
|
3. Analyze the outcome:
|
|
|
a. If this update succeeds or fails with name no longer in use, then transition to RemovingFwdRRs.
|
|
|
b. If the update fails with an IO error or timeout, transition back to !SelectingFwdServer.
|
|
|
c. If the update fails for any other reason transition to !ProcessRemoveFailed (Note if the forward address removal fails do not proceed with reverse removal).
|
|
|
|
|
|
#### RemovingFwdRRs
|
|
|
Attempts to remove all remaining RRs (including the DHCID RR) for the matching DHCID if there are no other associated address records.
|
|
|
|
|
|
1. Build a DNS UPDATE with three prerequisites:
|
|
|
a. An assertion that the DHCID RR matching the request DHCID exists
|
|
|
b. An assertion that there are no associated A RRs
|
|
|
c. An assertion that there are no associated AAAA RRs
|
|
|
and an UPDATE section that contains a delete of all RRs for the FQDN
|
|
|
|
|
|
2. Use !DnsClient to send the DNS UPDATE to the selected server and return with a response or error.
|
|
|
|
|
|
3. Analyze the outcome:
|
|
|
a. If this update succeeds then transition to !SelectingRevServer if the Reverse Change Flag is true, otherwise transition to !ProcessRemoveOk.
|
|
|
b. If this update fails for any reason, transition to !ProcessRemoveFailed. (TBD: What if it fails for an A or AAAA record still existing? Should we still try to remove the PTRs?)
|
|
|
|
|
|
#### !SelectingRevServer
|
|
|
Choose the next available server from the list of reverse servers and transition to !RemovingRevPtrs via !ServerSelected. If no more servers are available, transition to !ProcessRemoveFailed via !NoMoreServers.
|
|
|
|
|
|
#### !RemovingRevPtrs
|
|
|
1. Build a DNS UPDATE that contains a prerequisite which asserts that the domain name in the PTR record matches the FQDN in the request and an update section which deletes the PTR RR that matches the DHCP binding.
|
|
|
|
|
|
2. Use !DnsClient to send the DNS UPDATE to the selected server and return with a response or error.
|
|
|
|
|
|
3. Analyze the outcome:
|
|
|
a. If this update succeeds transition to !ProcessRemoveOk.
|
|
|
b. If the update fails with an IO error transition back to !SelectingRevServer.
|
|
|
c. If the update fails for any other reason transition to !ProcessRemoveFailed.
|
|
|
|
|
|
#### !ProcessRemoveOk
|
|
|
Record success accordingly. Transitions to final state upon destruction.
|
|
|
|
|
|
#### !ProcessRemoveFailed
|
|
|
Record failure accordingly. Depending on failure conditions, it is possible we may institute some form of retry. Transitions to final state upon destruction.
|
|
|
|
|
|
## Miscellaneous [=#point7]
|
|
|
|
|
|
1. DNS Server responses with an invalid TSIG key will be logged and discarded by !DnsClient when waiting for responses. It should continue to wait for a valid response until it reaches a prescribed timeout value. This is to guard against spoofed responses.
|
|
|
|
|
|
## Addendum: !PersistenceMgr Design [=#point8]
|
|
|
|
|
|
### Introduction
|
|
|
|
|
|
D2 has been largely implemented at the time of this writing. The last major portion of functionality left to complete is the !PersistenceMgr. This component was viewed as least critical to the initial development and as such was not provided much in the way of design details. The section contains a detailed design proposal for the !PersistenceMgr component.
|
|
|
|
|
|
### Overview
|
|
|
|
|
|
The !PersistenceMgr's primary function is to safely store a copy of the !NameChangeRequests (NCRs) that have been received by D2 but not yet processed so they may be retained across restarts. This separates D2's working queue of a requests from the persisted copy of those requests, allowing the store to be implemented in any number of ways without impacting the rest of D2. It will be embodied by a new class, isc::d2::D2PersistenceMgr.
|
|
|
|
|
|
D2PersistenceMgr will own and manage an instance of a !PersistentStore which it will use to maintain an accurate copy of the pending NCRs. It will receive from the store a unique identifier called a !PersistentId for each NCR that is stored. This id can then be used to later remove NCRs from the store.
|
|
|
|
|
|
!PersistentStore defines an interface for persisting opaque data blobs and through which derivations can provide persistent storage implementations.
|
|
|
|
|
|
D2Process will own and manage an instance of the D2PersistenceMgr just as it does for D2QueueMgr and D2UpdateMgr.
|
|
|
|
|
|
At startup D2QueueMgr will load the queue with the store contents by querying the D2PersistenceMgr, and then begin accepting requests.
|
|
|
|
|
|
When D2QueueMgr receives a NCR it will add it to the queue and pass it to the D2PersistenceMgr to be stored.
|
|
|
|
|
|
After D2UpdateMgr has processed a NCR, it will notify the D2PersistenceMgr to delete it from the store.
|
|
|
|
|
|
The D2PersistenceMgr is solely responsible for maintaining the store content. This includes keeping the physical representation current while mitigating growth by removing deleted entries (compression) as necessary.
|
|
|
|
|
|
The following class diagram shows the new classes in relation to existing D2 classes:
|
|
|
|
|
|
### Persistence Management Class Diagram
|
|
|
|
|
|
![persistence_mgr_classes.svg](/uploads/a3dbe02923230d02ab316d37f89b69db/persistence_mgr_classes.svg)
|
|
|
|
|
|
### D2PersistenceMgr
|
|
|
|
|
|
This class will be located in isc::d2 along with the other D2-specific classes. D2PersistenceMgr will provide services to:
|
|
|
|
|
|
1. Add a NCR to the store, assigning it a !PersistentId which uniquely identifies the NCR's persisted copy within the store
|
|
|
|
|
|
2. Delete a NCR from the store by its !PersistentId.
|
|
|
|
|
|
3. Retrieve a list of all NCRs in the store in chronological order.
|
|
|
|
|
|
4. Erase all of the entries in the store
|
|
|
|
|
|
|
|
|
### !PersistentStore
|
|
|
|
|
|
The !PersistentStore will be an abstract class which represents permanent storage of opaque data blobs with unique identifiers. The idea being that the store has no knowledge of the blob details. It is not intended to serve as a database allowing complex queries, rather it is a hard copy that can be readily recovered across restarts. It is up to the application to attach meaning to the contents of each blob. The store guarantees that each blob will be associated with a unique id and reliably stored until asked to remove it. This class will be located in isc::util as it is not specific to D2.
|
|
|
|
|
|
It will provide services to:
|
|
|
|
|
|
1. open (create) the store
|
|
|
2. add a blob to the store, returning its assigned id
|
|
|
3. delete a blob from the store by id
|
|
|
4. retrieve a list containing all of the store entries in chronological order
|
|
|
5. delete all entries in the store
|
|
|
|
|
|
Initially, data blobs must be character data. This is primarily due to the human readability of the data for diagnostic purposes as well as making easier to fabricate test data or parse captured data. However, extending it to support binary blobs would be trivial.
|
|
|
|
|
|
### Potential !PersistentStore Derivations
|
|
|
|
|
|
The underlying implementation for the actual store could be done in any number of ways. This document presents three alternatives. One thing that all three have in common is that they leverage the existing ability of NCRs to be converted to and from JSON text. So in all three solutions, the persisted form of the NCR is as JSON text. Here are the three options:
|
|
|
|
|
|
#### 1. !FlatFileStore
|
|
|
|
|
|
Uses a util::CSVFile to store two types of entries: one to add an entry and one to delete an entry. (By default columns will be separated by the caret "^" as JSON text contains commas).
|
|
|
|
|
|
1. Type - indicates if the entry is an entry addition or an entry deletion. Values are "add" or "del"
|
|
|
|
|
|
2. Id - the entry's !PersistentId as expressed as text
|
|
|
|
|
|
3. Value - the ASCII characters terminated by a line feed. The column will be empty for deletions as this no value in repeating the text.
|
|
|
|
|
|
Example content might look like the following:
|
|
|
|
|
|
```
|
|
|
Type^Id^Value\n
|
|
|
add^103^{"change_type":1, .. "ip_address":"192.168.2.10",.. }\n
|
|
|
add^104^{"change_type":0, .. "ip_address":"192.168.2.75",.. }\n
|
|
|
:
|
|
|
del^103^\n
|
|
|
:
|
|
|
```
|
|
|
|
|
|
(Note that the examples show NCR JSON text excerpts and line feeds are depicted.)
|
|
|
|
|
|
The persistent ID is a simple sequence generated by the store. At any point in time, the list of active entries is the list of all "add" records for which there is no "del" record.
|
|
|
|
|
|
##### Store Compression
|
|
|
Since the file content is a collection of adds and deletes, over time it will require logic to periodically compress the file to mitigate growth. The initial implementation will compress the contents after every "n" deletes. In addition, the queue manager can be modified to clear the store contents via D2PersistenceMgr::clearStore service whenever the working queue drains to zero.
|
|
|
|
|
|
|
|
|
#### 2. !MmapStore
|
|
|
|
|
|
Use mmap() to create a memory mapped file whose content consist of an array of fixed size blocks, each block containing:
|
|
|
|
|
|
1. in-use flag - denotes if block is available. 'y' means the block is contains valid data, 'n' means the block is empty.
|
|
|
|
|
|
2. time received - system time of add as 10 digit text with leading zeros
|
|
|
|
|
|
3. value - ASCII text padded with spaces to maximum fixed sized for value. This value is passed to the !MmapStore constructor.
|
|
|
|
|
|
4. end of block - '\n' for convenience
|
|
|
|
|
|
The !PersistentId equates to the array index. The store maintains a pool of available IDs (i.e. array entries).
|
|
|
|
|
|
When an NCR is added, it is assigned an ID from the free pool, the array entry for that ID (array[ID]) is populated as follows:
|
|
|
1. in-use is set to yes
|
|
|
2. time received is set to current system time
|
|
|
3. ncr text block is populated with JSON text from NCR
|
|
|
|
|
|
When an entry is deleted the in-use flag is for array[Id] is set to 'n' and the ID is returned to the free pool.
|
|
|
|
|
|
The first block of the array, ID=0 would be reserved for instance specific information such as maximum value size, pad characters and so forth.
|
|
|
|
|
|
|
|
|
#### 3. !DatabaseStore
|
|
|
|
|
|
Create a table in the lease database to store the requests. The table could look something like this:
|
|
|
|
|
|
pm_id : sequence, primary key
|
|
|
|
|
|
ncr json text : varchar (or varbinary which would eliminate escaping quotes)
|
|
|
|
|
|
The !PersistentId could be generated by the store or as an auto-incrementing sequence generated by the RDBMS. The former is simpler, the latter requires the an SQL function to do the insert which returns the assigned id as a result set.
|
|
|
|
|
|
Adding and removing NCRs is simply a matter of doing inserts and deletes.
|
|
|
|
|
|
#### Comparison of Derivations
|
|
|
Comparing the three alternatives:
|
|
|
|
|
|
1. !FlatFileStore
|
|
|
|
|
|
* Lease complex
|
|
|
* Store is text file with variable length content
|
|
|
* Should have no OS dependent quirks
|
|
|
* Requires periodic compression of the file to avoid endless growth
|
|
|
* Requires least amount of new code
|
|
|
|
|
|
2. !MmapStore
|
|
|
|
|
|
* Most complex
|
|
|
* Store is a text file but of fixed length entries
|
|
|
* Might have OS dependent quirks
|
|
|
* Does not require compression, deleted entries are reused
|
|
|
* Should be fast has kernel handles the IO
|
|
|
* Requires the most amount of new code
|
|
|
|
|
|
3. !DatabaseStore
|
|
|
|
|
|
* Moderate complexity
|
|
|
* Only supported if Kea is configured to use MySQL or PostgreSQL
|
|
|
* RDBMS code is structured around !LeaseMgr, this would need to be extended
|
|
|
* Would require RDBMS specific SQL statements, schema changes
|
|
|
* Does not require explicit compression of deleted entries
|
|
|
|
|
|
Given its relative simplicity, it is recommended that the initial derivation implemented be !FlatFileStore. It should be sufficient for initial use and provide a means to observe and analyze the runtime dynamics of Kea with DDNS under varying conditions such as client load and DNS server responsiveness . Should it be discovered that other implementations might be
|
|
|
better suited to or provide additional efficiencies for certain use cases they can be added as needed.
|
|
|
|
|
|
### Development Tasks
|
|
|
|
|
|
In addition to developing the new classes themselves, integrating the use of persistence into D2 would require the following:
|
|
|
|
|
|
1. !NameChangeRequest would be extended to contain a !PersistentId member with accessors. Its default value will indicate that it is not stored (PersistentStore::NOT_STORED). The D2PersistenceMgr will update this member to the value assigned by the adding the NCR to the store and would reset it to a value of NOT_STORED after deleting the NCR from the store.
|
|
|
|
|
|
2. D2Process would need to be modified to:
|
|
|
a. Own and manage an instance of the D2PersistenceMgr.
|
|
|
b. Pass the D2PersistenceMgr instance to the D2QueueMgr and
|
|
|
D2UpdateMgr at their construction
|
|
|
|
|
|
3. D2QueueMgr would be modified to:
|
|
|
a. Accept a pointer to a D2PersistenceMgr instance on construction.
|
|
|
b. Upon startup and prior to accepting requests, load the working queue from
|
|
|
the store
|
|
|
c. When a NCR is added to the queue, pass it to D2PersistenceMgr for storage.
|
|
|
|
|
|
4. D2UpdateMgr would be modified to:
|
|
|
a. Accept a pointer to a D2PersistenceMgr instance on construction.
|
|
|
b. Instruct D2PersistenceMgr to delete the NCR from the store after the NCR has been processed. |