BIND 10 master, updated. b54530b4539cec4476986442e72c047dddba7b48 [master] Merge branch 'trac3008'. This adds classes to support sending and receiving update requests with DHCP-DDNS.

BIND 10 source code commits bind10-changes at lists.isc.org
Wed Jul 24 18:08:06 UTC 2013


The branch, master has been updated
       via  b54530b4539cec4476986442e72c047dddba7b48 (commit)
       via  c5c64939c80687faa5692fef51c8435209736d55 (commit)
       via  6f5ce2f575e16f71a88ac35ec93c07ecd254d231 (commit)
       via  0a3773e5df10cc118dfd9dab3ce35bc612ca8ae7 (commit)
       via  07c566bffc7e726945d1c3fc58988c0186d5e0c0 (commit)
       via  a765638850588cc8980c622351124f1319c46365 (commit)
       via  f683ad20d4a1936679ebfbc3f7eabedee1ff5182 (commit)
      from  c845a96005b28acde2d62a93ee8c046162da4cb4 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit b54530b4539cec4476986442e72c047dddba7b48
Merge: c845a96 c5c6493
Author: Thomas Markwalder <tmark at isc.org>
Date:   Wed Jul 24 13:09:14 2013 -0400

    [master] Merge branch 'trac3008'. This adds classes to support
    sending and receiving update requests with DHCP-DDNS.

-----------------------------------------------------------------------

Summary of changes:
 src/bin/d2/Makefile.am                |    2 +
 src/bin/d2/d2_cfg_mgr.cc              |   14 +-
 src/bin/d2/d2_messages.mes            |   64 +++-
 src/bin/d2/ncr_io.cc                  |  281 +++++++++++++++
 src/bin/d2/ncr_io.h                   |  632 +++++++++++++++++++++++++++++++++
 src/bin/d2/ncr_msg.cc                 |   18 +
 src/bin/d2/ncr_msg.h                  |   79 +++--
 src/bin/d2/ncr_udp.cc                 |  299 ++++++++++++++++
 src/bin/d2/ncr_udp.h                  |  562 +++++++++++++++++++++++++++++
 src/bin/d2/tests/Makefile.am          |    3 +
 src/bin/d2/tests/ncr_udp_unittests.cc |  498 ++++++++++++++++++++++++++
 11 files changed, 2402 insertions(+), 50 deletions(-)
 create mode 100644 src/bin/d2/ncr_io.cc
 create mode 100644 src/bin/d2/ncr_io.h
 create mode 100644 src/bin/d2/ncr_udp.cc
 create mode 100644 src/bin/d2/ncr_udp.h
 create mode 100644 src/bin/d2/tests/ncr_udp_unittests.cc

-----------------------------------------------------------------------
diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am
index 2308505..9fcad88 100644
--- a/src/bin/d2/Makefile.am
+++ b/src/bin/d2/Makefile.am
@@ -57,7 +57,9 @@ b10_dhcp_ddns_SOURCES += d2_cfg_mgr.cc d2_cfg_mgr.h
 b10_dhcp_ddns_SOURCES += d2_update_message.cc d2_update_message.h
 b10_dhcp_ddns_SOURCES += d2_zone.cc d2_zone.h
 b10_dhcp_ddns_SOURCES += dns_client.cc dns_client.h
+b10_dhcp_ddns_SOURCES += ncr_io.cc ncr_io.h
 b10_dhcp_ddns_SOURCES += ncr_msg.cc ncr_msg.h
+b10_dhcp_ddns_SOURCES += ncr_udp.cc ncr_udp.h
 
 nodist_b10_dhcp_ddns_SOURCES = d2_messages.h d2_messages.cc
 EXTRA_DIST += d2_messages.mes
diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc
index 07068d6..671f624 100644
--- a/src/bin/d2/d2_cfg_mgr.cc
+++ b/src/bin/d2/d2_cfg_mgr.cc
@@ -17,12 +17,6 @@
 
 #include <boost/foreach.hpp>
 
-using namespace std;
-using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::data;
-using namespace isc::asiolink;
-
 namespace isc {
 namespace d2 {
 
@@ -102,12 +96,14 @@ D2CfgMgr::createConfigParser(const std::string& config_id) {
     D2CfgContextPtr context = getD2CfgContext();
 
     // Create parser instance based on element_id.
-    DhcpConfigParser* parser = NULL;
+    isc::dhcp::DhcpConfigParser* parser = NULL;
     if ((config_id == "interface")  ||
         (config_id == "ip_address")) {
-        parser = new StringParser(config_id, context->getStringStorage());
+        parser = new isc::dhcp::StringParser(config_id, 
+                                             context->getStringStorage());
     } else if (config_id == "port") {
-        parser = new Uint32Parser(config_id, context->getUint32Storage());
+        parser = new isc::dhcp::Uint32Parser(config_id, 
+                                             context->getUint32Storage());
     } else if (config_id ==  "forward_ddns") {
         parser = new DdnsDomainListMgrParser("forward_mgr",
                                              context->getForwardMgr(),
diff --git a/src/bin/d2/d2_messages.mes b/src/bin/d2/d2_messages.mes
index d2358ce..fdf6928 100644
--- a/src/bin/d2/d2_messages.mes
+++ b/src/bin/d2/d2_messages.mes
@@ -70,7 +70,7 @@ application when it is not running.
 
 % DCTL_ORDER_ERROR configuration contains more elements than the parsing order
 An error message which indicates that configuration being parsed includes
-element ids not specified the configuration manager's parse order list. This 
+element ids not specified the configuration manager's parse order list. This
 is a programmatic error.
 
 % DCTL_ORDER_NO_ELEMENT element: %1 is in the parsing order but is missing from the configuration
@@ -78,7 +78,7 @@ An error message output during a configuration update.  The program is
 expecting an item but has not found it in the new configuration.  This may
 mean that the BIND 10 configuration database is corrupt.
 
-% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2 
+% DCTL_PARSER_FAIL configuration parsing failed for configuration element: %1, reason: %2
 On receipt of message containing details to a change of its configuration,
 the server failed to create a parser to decode the contents of the named
 configuration element, or the creation succeeded but the parsing actions
@@ -124,16 +124,52 @@ has been invoked.
 This is a debug message issued when the Dhcp-Ddns application encounters an
 unrecoverable error from within the event loop.
 
+% DHCP_DDNS_INVALID_NCR application received an invalid DNS update request: %1
+This is an error message that indicates that an invalid request to update
+a DNS entry was received by the application.  Either the format or the content
+of the request is incorrect. The request will be ignored.
+
+% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
+This is a debug message issued when the DHCP-DDNS application encountered an
+error while decoding a response to DNS Update message. Typically, this error
+will be encountered when a response message is malformed.
+
+% DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR application encountered an error while closing the listener used to receive NameChangeRequests : %1
+This is an error message that indicates the application was unable to close the
+listener connection used to receive NameChangeRequests.  Closure may occur
+during the course of error recovery or during normal shutdown procedure.  In
+either case the error is unlikely to impair the application's ability to
+process requests but it should be reported for analysis.
+
+% DHCP_DDNS_NCR_RECV_NEXT_ERROR application could not initiate the next read following a request receive.
+This is a error message indicating that NameChangeRequest listener could not
+start another read after receiving a request.  While possible, this is highly
+unlikely and is probably a programmatic error.  The application should recover
+on its own.
+
+% DHCP_DDNS_NCR_SEND_CLOSE_ERROR DHCP-DDNS client encountered an error while closing the sender connection used to send NameChangeRequests : %1
+This is an error message that indicates the DHCP-DDNS client was unable to
+close the connection used to send NameChangeRequests.  Closure may occur during
+the course of error recovery or during normal shutdown procedure.  In either
+case the error is unlikely to impair the client's ability to send requests but
+it should be reported for analysis.
+
+% DHCP_DDNS_NCR_SEND_NEXT_ERROR DHCP-DDNS client could not initiate the next request send following send completion.
+This is a error message indicating that NameChangeRequest sender could not
+start another send after completing the send of the previous request.  While
+possible, this is highly unlikely and is probably a programmatic error.  The
+application should recover on its own.
+
+% DHCP_DDNS_NCR_UDP_RECV_ERROR UDP socket receive error while listening for DNS Update requests: %1
+This is an error message indicating that an IO error occurred while listening
+over a UDP socket for DNS update requests. This could indicate a network
+connectivity or system resource issue.
+
 % DHCP_DDNS_NO_MATCH No DNS servers match FQDN: %1
 This is warning message issued when there are no domains in the configuration
-which match the cited fully qualified domain name (FQDN).  The DNS Update 
+which match the cited fully qualified domain name (FQDN).  The DNS Update
 request for the FQDN cannot be processed.
 
-% DHCP_DDNS_INVALID_RESPONSE received response to DNS Update message is malformed: %1
-This is a debug message issued when the DHCP-DDNS application encountered an error
-while decoding a response to DNS Update message. Typically, this error will be
-encountered when a response message is malformed.
-
 % DHCP_DDNS_PROCESS_INIT application init invoked
 This is a debug message issued when the Dhcp-Ddns application enters
 its init method.
@@ -149,3 +185,15 @@ in event loop.
 % DHCP_DDNS_SHUTDOWN application is performing a normal shut down
 This is a debug message issued when the application has been instructed
 to shut down by the controller.
+
+% DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR unexpected exception thrown from the application receive completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's request receive completion handler.  This is a
+programmatic error that needs to be reported.  Dependent upon the nature of
+the error the application may or may not continue operating normally.
+
+% DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR unexpected exception thrown from the DHCP-DDNS client send completion handler: %1
+This is an error message that indicates that an exception was thrown but not
+caught in the application's send completion handler.  This is a programmatic
+error that needs to be reported.  Dependent upon the nature of the error the
+client may or may not continue operating normally.
diff --git a/src/bin/d2/ncr_io.cc b/src/bin/d2/ncr_io.cc
new file mode 100644
index 0000000..aa3ef69
--- /dev/null
+++ b/src/bin/d2/ncr_io.cc
@@ -0,0 +1,281 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/ncr_io.h>
+
+namespace isc {
+namespace d2 {
+
+//************************** NameChangeListener ***************************
+
+NameChangeListener::NameChangeListener(RequestReceiveHandler&
+                                       recv_handler)
+    : listening_(false), recv_handler_(recv_handler) {
+};
+
+void
+NameChangeListener::startListening(isc::asiolink::IOService& io_service) {
+    if (amListening()) {
+        // This amounts to a programmatic error.
+        isc_throw(NcrListenerError, "NameChangeListener is already listening");
+    }
+
+    // Call implementation dependent open.
+    try {
+        open(io_service);
+    } catch (const isc::Exception& ex) {
+        stopListening();
+        isc_throw(NcrListenerOpenError, "Open failed:" << ex.what());
+    }
+
+    // Set our status to listening.
+    setListening(true);
+
+    // Start the first asynchronous receive.
+    try {
+        doReceive();
+    } catch (const isc::Exception& ex) {
+        stopListening();
+        isc_throw(NcrListenerReceiveError, "doReceive failed:" << ex.what());
+    }
+}
+
+void
+NameChangeListener::stopListening() {
+    try {
+        // Call implementation dependent close.
+        close();
+    } catch (const isc::Exception &ex) {
+        // Swallow exceptions. If we have some sort of error we'll log
+        // it but we won't propagate the throw.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_LISTEN_CLOSE_ERROR).arg(ex.what());
+    }
+
+    // Set it false, no matter what.  This allows us to at least try to
+    // re-open via startListening().
+    setListening(false);
+}
+
+void
+NameChangeListener::invokeRecvHandler(const Result result,
+                                      NameChangeRequestPtr& ncr) {
+    // Call the registered application layer handler.
+    // Surround the invocation with a try-catch. The invoked handler is
+    // not supposed to throw, but in the event it does we will at least
+    // report it.
+    try {
+        recv_handler_(result, ncr);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Start the next IO layer asynchronous receive.
+    // In the event the handler above intervened and decided to stop listening
+    // we need to check that first.
+    if (amListening()) {
+        try {
+            doReceive();
+        } catch (const isc::Exception& ex) {
+            // It is possible though unlikely, for doReceive to fail without
+            // scheduling the read. While, unlikely, it does mean the callback
+            // will not get called with a failure. A throw here would surface
+            // at the IOService::run (or run variant) invocation.  So we will
+            // close the window by invoking the application handler with
+            // a failed result, and let the application layer sort it out.
+            LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_RECV_NEXT_ERROR)
+                      .arg(ex.what());
+
+            // Call the registered application layer handler.
+            // Surround the invocation with a try-catch. The invoked handler is
+            // not supposed to throw, but in the event it does we will at least
+            // report it.
+            NameChangeRequestPtr empty;
+            try {
+                recv_handler_(ERROR, empty);
+            } catch (const std::exception& ex) {
+                LOG_ERROR(dctl_logger,
+                          DHCP_DDNS_UNCAUGHT_NCR_RECV_HANDLER_ERROR)
+                          .arg(ex.what());
+            }
+        }
+    }
+}
+
+//************************* NameChangeSender ******************************
+
+NameChangeSender::NameChangeSender(RequestSendHandler& send_handler,
+                                   size_t send_queue_max)
+    : sending_(false), send_handler_(send_handler),
+      send_queue_max_(send_queue_max) {
+
+    // Queue size must be big enough to hold at least 1 entry.
+    if (send_queue_max == 0) {
+        isc_throw(NcrSenderError, "NameChangeSender constructor"
+                  " queue size must be greater than zero");
+    }
+}
+
+void
+NameChangeSender::startSending(isc::asiolink::IOService& io_service) {
+    if (amSending()) {
+        // This amounts to a programmatic error.
+        isc_throw(NcrSenderError, "NameChangeSender is already sending");
+    }
+
+    // Clear send marker.
+    ncr_to_send_.reset();
+
+    // Call implementation dependent open.
+    try {
+        open(io_service);
+    } catch (const isc::Exception& ex) {
+        stopSending();
+        isc_throw(NcrSenderOpenError, "Open failed: " << ex.what());
+    }
+
+    // Set our status to sending.
+    setSending(true);
+}
+
+void
+NameChangeSender::stopSending() {
+    try {
+        // Call implementation dependent close.
+        close();
+    } catch (const isc::Exception &ex) {
+        // Swallow exceptions. If we have some sort of error we'll log
+        // it but we won't propagate the throw.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_SEND_CLOSE_ERROR).arg(ex.what());
+    }
+
+    // Set it false, no matter what.  This allows us to at least try to
+    // re-open via startSending().
+    setSending(false);
+}
+
+void
+NameChangeSender::sendRequest(NameChangeRequestPtr& ncr) {
+    if (!amSending()) {
+        isc_throw(NcrSenderError, "sender is not ready to send");
+    }
+
+    if (!ncr) {
+        isc_throw(NcrSenderError, "request to send is empty");
+    }
+
+    if (send_queue_.size() >= send_queue_max_) {
+        isc_throw(NcrSenderQueueFull, "send queue has reached maximum capacity:"
+                  << send_queue_max_ );
+    }
+
+    // Put it on the queue.
+    send_queue_.push_back(ncr);
+
+    // Call sendNext to schedule the next one to go.
+    sendNext();
+}
+
+void
+NameChangeSender::sendNext() {
+    if (ncr_to_send_) {
+        // @todo Not sure if there is any risk of getting stuck here but
+        // an interval timer to defend would be good.
+        // In reality, the derivation should ensure they timeout themselves
+        return;
+    }
+
+    // If queue isn't empty, then get one from the front. Note we leave
+    // it on the front of the queue until we successfully send it.
+    if (send_queue_.size()) {
+        ncr_to_send_ = send_queue_.front();
+
+       // @todo start defense timer
+       // If a send were to hang and we timed it out, then timeout
+       // handler need to cycle thru open/close ?
+
+       // Call implementation dependent send.
+       doSend(ncr_to_send_);
+    }
+}
+
+void
+NameChangeSender::invokeSendHandler(const NameChangeSender::Result result) {
+    // @todo reset defense timer
+    if (result == SUCCESS) {
+        // It shipped so pull it off the queue.
+        send_queue_.pop_front();
+    }
+
+    // Invoke the completion handler passing in the result and a pointer
+    // the request involved.
+    // Surround the invocation with a try-catch. The invoked handler is
+    // not supposed to throw, but in the event it does we will at least
+    // report it.
+    try {
+        send_handler_(result, ncr_to_send_);
+    } catch (const std::exception& ex) {
+        LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+                  .arg(ex.what());
+    }
+
+    // Clear the pending ncr pointer.
+    ncr_to_send_.reset();
+
+    // Set up the next send
+    try {
+        sendNext();
+    } catch (const isc::Exception& ex) {
+        // It is possible though unlikely, for sendNext to fail without
+        // scheduling the send. While, unlikely, it does mean the callback
+        // will not get called with a failure. A throw here would surface
+        // at the IOService::run (or run variant) invocation.  So we will
+        // close the window by invoking the application handler with
+        // a failed result, and let the application layer sort it out.
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_SEND_NEXT_ERROR)
+                  .arg(ex.what());
+
+        // Invoke the completion handler passing in failed result.
+        // Surround the invocation with a try-catch. The invoked handler is
+        // not supposed to throw, but in the event it does we will at least
+        // report it.
+        try {
+            send_handler_(ERROR, ncr_to_send_);
+        } catch (const std::exception& ex) {
+            LOG_ERROR(dctl_logger, DHCP_DDNS_UNCAUGHT_NCR_SEND_HANDLER_ERROR)
+                      .arg(ex.what());
+        }
+    }
+}
+
+void
+NameChangeSender::skipNext() {
+    if (send_queue_.size()) {
+        // Discards the request at the front of the queue.
+        send_queue_.pop_front();
+    }
+}
+
+void
+NameChangeSender::clearSendQueue() {
+    if (amSending()) {
+        isc_throw(NcrSenderError, "Cannot clear queue while sending");
+    }
+
+    send_queue_.clear();
+}
+
+} // namespace isc::d2
+} // namespace isc
diff --git a/src/bin/d2/ncr_io.h b/src/bin/d2/ncr_io.h
new file mode 100644
index 0000000..37d68c2
--- /dev/null
+++ b/src/bin/d2/ncr_io.h
@@ -0,0 +1,632 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_IO_H
+#define NCR_IO_H
+
+/// @file ncr_io.h
+/// @brief This file defines abstract classes for exchanging NameChangeRequests.
+///
+/// These classes are used for sending and receiving requests to update DNS
+/// information for FQDNs embodied as NameChangeRequests (aka NCRs). Ultimately,
+/// NCRs must move through the following three layers in order to implement
+/// DHCP-DDNS:
+///
+///    * Application layer - the business layer which needs to
+///    transport NameChangeRequests, and is unaware of the means by which
+///    they are transported.
+///
+///    * NameChangeRequest layer - This is the layer which acts as the
+///    intermediary between the Application layer and the IO layer.  It must
+///    be able to move NameChangeRequests to the IO layer as raw data and move
+///    raw data from the IO layer in the Application layer as
+///    NameChangeRequests.
+///
+///    * IO layer - the low-level layer that is directly responsible for
+///    sending and receiving data asynchronously and is supplied through
+///    other libraries.  This layer is largely unaware of the nature of the
+///    data being transmitted.  In other words, it doesn't know beans about
+///    NCRs.
+///
+/// The abstract classes defined here implement the latter, middle layer,
+/// the NameChangeRequest layer.  There are two types of participants in this
+/// middle ground:
+///
+///    * listeners - Receive NCRs from one or more sources. The DHCP-DDNS
+///   application, (aka D2), is a listener. Listeners are embodied by the
+///   class, NameChangeListener.
+///
+///    * senders - sends NCRs to a given target.  DHCP servers are senders.
+///   Senders are embodied by the class, NameChangeListener.
+///
+/// These two classes present a public interface for asynchronous
+/// communications that is independent of the IO layer mechanisms.  While the
+/// type and details of the IO mechanism are not relevant to either class, it
+/// is presumed to use isc::asiolink library for asynchronous event processing.
+///
+
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <d2/ncr_msg.h>
+#include <exceptions/exceptions.h>
+
+#include <deque>
+
+namespace isc {
+namespace d2 {
+
+/// @brief Exception thrown if an NcrListenerError encounters a general error.
+class NcrListenerError : public isc::Exception {
+public:
+    NcrListenerError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrListenerOpenError : public isc::Exception {
+public:
+    NcrListenerOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO receive.
+class NcrListenerReceiveError : public isc::Exception {
+public:
+    NcrListenerReceiveError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for receiving NameChangeRequests.
+///
+/// NameChangeListener provides the means to:
+/// -  Supply a callback to invoke upon receipt of an NCR or a listening
+/// error
+/// -  Start listening using a given IOService instance to process events
+/// -  Stop listening
+///
+/// It implements the high level logic flow to listen until a request arrives,
+/// invoke the implementation's handler and return to listening for the next
+/// request.
+///
+/// It provides virtual methods that allow derivations supply implementations
+/// to open the appropriate IO source, perform a listen, and close the IO
+/// source.
+///
+/// The overall design is based on a callback chain. The listener's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// receive inbound NameChangeRequests.  The listener derivation will supply
+/// its own callback to the IO layer to process receive events from its IO
+/// source.  This is referred to as the NameChangeRequest completion handler.
+/// It is through this handler that the NameChangeRequest layer gains access
+/// to the low level IO read service results.  It is expected to assemble
+/// NameChangeRequests from the inbound data and forward them to the
+/// application layer by invoking the application layer callback registered
+/// with the listener.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestReceiveHandler.  It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive inbound NCRs, a caller implements a derivation of the
+/// RequestReceiveHandler and supplies an instance of this derivation to the
+/// NameChangeListener constructor. This "registers" the handler with the
+/// listener.
+///
+/// To begin listening, the caller invokes the listener's startListener()
+/// method, passing in an IOService instance.  This in turn will pass the
+/// IOService into the virtual method, open().  The open method is where the
+/// listener derivation performs the steps necessary to prepare its IO source
+/// for reception (e.g. opening a socket, connecting to a database).
+///
+/// Assuming the open is successful, startListener will call the virtual
+/// doReceive() method.  The listener derivation uses this method to
+/// instigate an IO layer asynchronous passing in its IO layer callback to
+/// handle receive events from its IO source.
+///
+/// As stated earlier, the derivation's NameChangeRequest completion handler
+/// MUST invoke the application layer handler registered with the listener.
+/// This is done by passing in either a success status and a populated
+/// NameChangeRequest or an error status and an empty request into the
+/// listener's invokeRecvHandler method. This is the mechanism by which the
+/// listener's caller is handed inbound NCRs.
+class NameChangeListener {
+public:
+
+    /// @brief Defines the outcome of an asynchronous NCR receive
+    enum Result {
+      SUCCESS,
+      TIME_OUT,
+      STOPPED,
+      ERROR
+    };
+
+    /// @brief Abstract class for defining application layer receive callbacks.
+    ///
+    /// Applications which will receive NameChangeRequests must provide a
+    /// derivation of this class to the listener constructor in order to
+    /// receive NameChangeRequests.
+    class RequestReceiveHandler {
+      public:
+        /// @brief Function operator implementing a NCR receive callback.
+        ///
+        /// This method allows the application to receive the inbound
+        /// NameChangeRequests. It is intended to function as a hand off of
+        /// information and should probably not be time-consuming.
+        ///
+        /// @param result contains that receive outcome status.
+        /// @param ncr is a pointer to the newly received NameChangeRequest if
+        /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+        /// wise.
+        /// @throw This method MUST NOT throw.
+        virtual void operator ()(const Result result,
+                                 NameChangeRequestPtr& ncr) = 0;
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param recv_handler is a pointer the application layer handler to be
+    /// invoked each time a NCR is received or a receive error occurs.
+    NameChangeListener(RequestReceiveHandler& recv_handler);
+
+    /// @brief Destructor
+    virtual ~NameChangeListener() {
+    };
+
+    /// @brief Prepares the IO for reception and initiates the first receive.
+    ///
+    /// Calls the derivation's open implementation to initialize the IO layer
+    /// source for receiving inbound requests.  If successful, it issues first
+    /// asynchronous read by calling the derivation's doReceive implementation.
+    ///
+    /// @param io_service is the IOService that will handle IO event processing.
+    ///
+    /// @throw NcrListenError if the listener is already "listening" or
+    /// in the event the open or doReceive methods fail.
+    void startListening(isc::asiolink::IOService& io_service);
+
+    /// @brief Closes the IO source and stops listen logic.
+    ///
+    /// Calls the derivation's implementation of close and marks the state
+    /// as not listening.
+    void stopListening();
+
+    /// @brief Calls the NCR receive handler registered with the listener.
+    ///
+    /// This is the hook by which the listener's caller's NCR receive handler
+    /// is called.  This method MUST be invoked by the derivation's
+    /// implementation of doReceive.
+    ///
+    /// NOTE:
+    /// The handler invoked by this method MUST NOT THROW. The handler is
+    /// at application level and should trap and handle any errors at
+    /// that level, rather than throw exceptions.  If an error has occurred
+    /// prior to invoking the handler, it will be expressed in terms a failed
+    /// result being passed to the handler, not a throw.  Therefore any
+    /// exceptions at the handler level are application issues and should be
+    /// dealt with at that level.
+    ///
+    /// This method does wrap the handler invocation within a try-catch
+    /// block as a fail-safe.  The exception will be logged but the
+    /// receive logic will continue.  What this implies is that continued
+    /// operation may or may not succeed as the application has violated
+    /// the interface contract.
+    ///
+    /// @param result contains that receive outcome status.
+    /// @param ncr is a pointer to the newly received NameChangeRequest if
+    /// result is NameChangeListener::SUCCESS.  It is indeterminate other
+    /// wise.
+    void invokeRecvHandler(const Result result, NameChangeRequestPtr& ncr);
+
+    /// @brief Abstract method which opens the IO source for reception.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// prepare the IO source to receive requests.
+    ///
+    /// @param io_service is the IOService that process IO events.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+    /// @brief Abstract method which closes the IO source.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// "close" the IO source.
+    ///
+    /// @throw If the implementation encounters an error it  MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void close() = 0;
+
+    /// @brief Initiates an IO layer asynchronous read.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// initiate an asynchronous read of the IO source with the
+    /// derivation's IO layer handler as the IO completion callback.
+    ///
+    /// @throw If the implementation encounters an error it  MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void doReceive() = 0;
+
+    /// @brief Returns true if the listener is listening, false otherwise.
+    ///
+    /// A true value indicates that the IO source has been opened successfully,
+    /// and that receive loop logic is active.
+    bool amListening() const {
+        return (listening_);
+    }
+
+private:
+    /// @brief Sets the listening indicator to the given value.
+    ///
+    /// Note, this method is private as it is used the base class is solely
+    /// responsible for managing the state.
+    ///
+    /// @param value is the new value to assign to the indicator.
+    void setListening(bool value) {
+        listening_ = value;
+    }
+
+    /// @brief Indicates if the listener is listening.
+    bool listening_;
+
+    /// @brief Application level NCR receive completion handler.
+    RequestReceiveHandler& recv_handler_;
+};
+
+/// @brief Defines a smart pointer to an instance of a listener.
+typedef boost::shared_ptr<NameChangeListener> NameChangeListenerPtr;
+
+
+/// @brief Thrown when a NameChangeSender encounters an error.
+class NcrSenderError : public isc::Exception {
+public:
+    NcrSenderError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs during IO source open.
+class NcrSenderOpenError : public isc::Exception {
+public:
+    NcrSenderOpenError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderQueueFull : public isc::Exception {
+public:
+    NcrSenderQueueFull(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception thrown if an error occurs initiating an IO send.
+class NcrSenderSendError : public isc::Exception {
+public:
+    NcrSenderSendError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Abstract interface for sending NameChangeRequests.
+///
+/// NameChangeSender provides the means to:
+/// - Supply a callback to invoke upon completing the delivery of an NCR or a
+/// send error
+/// - Start sending using a given IOService instance to process events
+/// - Queue NCRs for delivery
+/// - Stop sending
+///
+/// It implements the high level logic flow to queue requests for delivery,
+/// and ship them one at a time, waiting for the send to complete prior to
+/// sending the next request in the queue.  If a send fails, the request
+/// will remain at the front of queue and will be the send will be retried
+/// endlessly unless the caller dequeues the request.  Note, it is presumed that
+/// a send failure is some form of IO error such as loss of connectivity and
+/// not a message content error.  It should not be possible to queue an invalid
+/// message.
+///
+/// It should be noted that once a request is placed onto the send queue it
+/// will remain there until one of three things occur:
+///     * It is successfully delivered
+///     * @c NameChangeSender::skipNext() is called
+///     * @c NameChangeSender::clearSendQueue() is called
+///
+/// The queue contents are preserved across start and stop listening
+/// transitions. This is to provide for error recovery without losing
+/// undelivered requests.
+
+/// It provides virtual methods so derivations may supply implementations to
+/// open the appropriate IO sink, perform a send, and close the IO sink.
+///
+/// The overall design is based on a callback chain.  The sender's caller (the
+/// application) supplies an "application" layer callback through which it will
+/// be given send completion notifications. The sender derivation will employ
+/// its own callback at the IO layer to process send events from its IO sink.
+/// This callback is expected to forward the outcome of each asynchronous send
+/// to the application layer by invoking the application layer callback
+/// registered with the sender.
+///
+/// The application layer callback is structured around a nested class,
+/// RequestSendHandler.  It consists of single, abstract operator() which
+/// accepts a result code and a pointer to a NameChangeRequest as parameters.
+/// In order to receive send completion notifications, a caller implements a
+/// derivation of the RequestSendHandler and supplies an instance of this
+/// derivation to the NameChangeSender constructor. This "registers" the
+/// handler with the sender.
+///
+/// To begin sending, the caller invokes the listener's startSending()
+/// method, passing in an IOService instance.  This in turn will pass the
+/// IOService into the virtual method, open().  The open method is where the
+/// sender derivation performs the steps necessary to prepare its IO sink for
+/// output (e.g. opening a socket, connecting to a database).  At this point,
+/// the sender is ready to send messages.
+///
+/// In order to send a request, the application layer invokes the sender
+/// method, sendRequest(), passing in the NameChangeRequest to send.  This
+/// method places the request onto the back of the send queue, and then invokes
+/// the sender method, sendNext().
+///
+/// If there is already a send in progress when sendNext() is called, the method
+/// will return immediately rather than initiate the next send.  This is to
+/// ensure that sends are processed sequentially.
+///
+/// If there is not a send in progress and the send queue is not empty,
+/// the sendNext method will pass the NCR at the front of the send queue into
+/// the virtual doSend() method.
+///
+/// The sender derivation uses this doSend() method to instigate an IO layer
+/// asynchronous send with its IO layer callback to handle send events from its
+/// IO sink.
+///
+/// As stated earlier, the derivation's IO layer callback MUST invoke the
+/// application layer handler registered with the sender.  This is done by
+/// passing in  a status indicating the outcome of the send into the sender's
+/// invokeSendHandler method. This is the mechanism by which the sender's
+/// caller is handed outbound notifications.
+
+/// After invoking the application layer handler, the invokeSendHandler method
+/// will call the sendNext() method to initiate the next send.  This ensures
+/// that requests continue to dequeue and ship.
+///
+class NameChangeSender {
+public:
+
+    /// @brief Defines the type used for the request send queue.
+    typedef std::deque<NameChangeRequestPtr> SendQueue;
+
+    /// @brief Defines a default maximum number of entries in the send queue.
+    static const size_t MAX_QUEUE_DEFAULT = 1024;
+
+    /// @brief Defines the outcome of an asynchronous NCR send.
+    enum Result {
+        SUCCESS,
+        TIME_OUT,
+        STOPPED,
+        ERROR
+    };
+
+    /// @brief Abstract class for defining application layer send callbacks.
+    ///
+    /// Applications which will send NameChangeRequests must provide a
+    /// derivation of this class to the sender constructor in order to
+    /// receive send outcome notifications.
+    class RequestSendHandler {
+      public:
+        /// @brief Function operator implementing a NCR send callback.
+        ///
+        /// This method allows the application to receive the outcome of
+        /// each send.  It is intended to function as a hand off of information
+        /// and should probably not be time-consuming.
+        ///
+        /// @param result contains that send outcome status.
+        /// @param ncr is a pointer to the NameChangeRequest that was
+        /// delivered (or attempted).
+        ///
+        /// @throw This method MUST NOT throw.
+        virtual void operator ()(const Result result,
+                                 NameChangeRequestPtr& ncr) = 0;
+    };
+
+    /// @brief Constructor
+    ///
+    /// @param send_handler is a pointer the application layer handler to be
+    /// invoked each time a NCR send attempt completes.
+    /// @param send_queue_max is the maximum number of entries allowed in the
+    /// send queue.  Once the maximum number is reached, all calls to
+    /// sendRequest will fail with an exception.
+    NameChangeSender(RequestSendHandler& send_handler,
+            size_t send_queue_max = MAX_QUEUE_DEFAULT);
+
+    /// @brief Destructor
+    virtual ~NameChangeSender() {
+    }
+
+    /// @brief Prepares the IO for transmission.
+    ///
+    /// Calls the derivation's open implementation to initialize the IO layer
+    /// sink for sending outbound requests.
+    ///
+    /// @param io_service is the IOService that will handle IO event processing.
+    ///
+    /// @throw NcrSenderError if the sender is already "sending" or
+    /// NcrSenderOpenError if the open fails.
+    void startSending(isc::asiolink::IOService & io_service);
+
+    /// @brief Closes the IO sink and stops send logic.
+    ///
+    /// Calls the derivation's implementation of close and marks the state
+    /// as not sending.
+    void stopSending();
+
+    /// @brief Queues the given request to be sent.
+    ///
+    /// The given request is placed at the back of the send queue and then
+    /// sendNext is invoked.
+
+    ///
+    /// @param ncr is the NameChangeRequest to send.
+    ///
+    /// @throw NcrSenderError if the sender is not in sending state or
+    /// the request is empty; NcrSenderQueueFull if the send queue has reached
+    /// capacity.
+    void sendRequest(NameChangeRequestPtr& ncr);
+
+    /// @brief Dequeues and sends the next request on the send queue.
+    ///
+    /// If there is already a send in progress just return. If there is not
+    /// a send in progress and the send queue is not empty the grab the next
+    /// message on the front of the queue and call doSend().
+    ///
+    void sendNext();
+
+    /// @brief Calls the NCR send completion handler registered with the
+    /// sender.
+    ///
+    /// This is the hook by which the sender's caller's NCR send completion
+    /// handler is called.  This method MUST be invoked by the derivation's
+    /// implementation of doSend.   Note that if the send was a success,
+    /// the entry at the front of the queue is removed from the queue.
+    /// If not we leave it there so we can retry it.  After we invoke the
+    /// handler we clear the pending ncr value and queue up the next send.
+    ///
+    /// NOTE:
+    /// The handler invoked by this method MUST NOT THROW. The handler is
+    /// application level logic and should trap and handle any errors at
+    /// that level, rather than throw exceptions.  If IO errors have occurred
+    /// prior to invoking the handler, they are expressed in terms a failed
+    /// result being passed to the handler.  Therefore any exceptions at the
+    /// handler level are application issues and should be dealt with at that
+    /// level.
+    ///
+    /// This method does wrap the handler invocation within a try-catch
+    /// block as a fail-safe.  The exception will be logged but the
+    /// send logic will continue.  What this implies is that continued
+    /// operation may or may not succeed as the application has violated
+    /// the interface contract.
+    ///
+    /// @param result contains that send outcome status.
+    void invokeSendHandler(const NameChangeSender::Result result);
+
+    /// @brief Removes the request at the front of the send queue
+    ///
+    /// This method can be used to avoid further retries of a failed
+    /// send. It is provided primarily as a just-in-case measure. Since
+    /// a failed send results in the same request being retried continuously
+    /// this method makes it possible to remove that entry, causing the
+    /// subsequent entry in the queue to be attempted on the next send.
+    /// It is presumed that sends will only fail due to some sort of
+    /// communications issue. In the unlikely event that a request is
+    /// somehow tainted and causes an send failure based on its content,
+    /// this method provides a means to remove th message.
+    void skipNext();
+
+    /// @brief Flushes all entries in the send queue
+    ///
+    /// This method can be used to discard all of the NCRs currently in the
+    /// the send queue.  Note it may not be called while the sender is in
+    /// the sending state.
+    /// @throw NcrSenderError if called and sender is in sending state.
+    void clearSendQueue();
+
+    /// @brief Abstract method which opens the IO sink for transmission.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// prepare the IO sink to send requests.
+    ///
+    /// @param io_service is the IOService that process IO events.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void open(isc::asiolink::IOService& io_service) = 0;
+
+    /// @brief Abstract method which closes the IO sink.
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// "close" the IO sink.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void close() = 0;
+
+    /// @brief Initiates an IO layer asynchronous send
+    ///
+    /// The derivation uses this method to perform the steps needed to
+    /// initiate an asynchronous send through the IO sink of the given NCR.
+    ///
+    /// @param ncr is a pointer to the NameChangeRequest to send.
+    /// derivation's IO layer handler as the IO completion callback.
+    ///
+    /// @throw If the implementation encounters an error it MUST
+    /// throw it as an isc::Exception or derivative.
+    virtual void doSend(NameChangeRequestPtr& ncr) = 0;
+
+    /// @brief Returns true if the sender is in send mode, false otherwise.
+    ///
+    /// A true value indicates that the IO sink has been opened successfully,
+    /// and that send loop logic is active.
+    bool amSending() const {
+        return (sending_);
+    }
+
+    /// @brief Returns true when a send is in progress.
+    ///
+    /// A true value indicates that a request is actively in the process of
+    /// being delivered.
+    bool isSendInProgress() const {
+        return ((ncr_to_send_) ? true : false);
+    }
+
+    /// @brief Returns the maximum number of entries allowed in the send queue.
+    size_t getQueueMaxSize() const  {
+        return (send_queue_max_);
+    }
+
+    /// @brief Returns the number of entries currently in the send queue.
+    size_t getQueueSize() const {
+        return (send_queue_.size());
+    }
+
+private:
+    /// @brief Sets the sending indicator to the given value.
+    ///
+    /// Note, this method is private as it is used the base class is solely
+    /// responsible for managing the state.
+    ///
+    /// @param value is the new value to assign to the indicator.
+    void setSending(bool value) {
+            sending_ = value;
+    }
+
+    /// @brief Boolean indicator which tracks sending status.
+    bool sending_;
+
+    /// @brief A pointer to regisetered send completion handler.
+    RequestSendHandler& send_handler_;
+
+    /// @brief Maximum number of entries permitted in the send queue.
+    size_t send_queue_max_;
+
+    /// @brief Queue of the requests waiting to be sent.
+    SendQueue send_queue_;
+
+    /// @brief Pointer to the request which is in the process of being sent.
+    NameChangeRequestPtr ncr_to_send_;
+};
+
+/// @brief Defines a smart pointer to an instance of a sender.
+typedef boost::shared_ptr<NameChangeSender> NameChangeSenderPtr;
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/bin/d2/ncr_msg.cc b/src/bin/d2/ncr_msg.cc
index cf47176..093f60a 100644
--- a/src/bin/d2/ncr_msg.cc
+++ b/src/bin/d2/ncr_msg.cc
@@ -455,5 +455,23 @@ NameChangeRequest::toText() const {
     return (stream.str());
 }
 
+bool
+NameChangeRequest::operator == (const NameChangeRequest& other) {
+    return ((change_type_ == other.change_type_) &&
+            (forward_change_ == other.forward_change_) &&
+            (reverse_change_ == other.reverse_change_) &&
+            (fqdn_ == other.fqdn_) &&
+            (ip_address_ == other.ip_address_) &&
+            (dhcid_ == other.dhcid_) &&
+            (lease_expires_on_ == other.lease_expires_on_) &&
+            (lease_length_ == other.lease_length_));
+}
+
+bool
+NameChangeRequest::operator != (const NameChangeRequest& other) {
+    return (!(*this == other));
+}
+
+
 }; // end of isc::dhcp namespace
 }; // end of isc namespace
diff --git a/src/bin/d2/ncr_msg.h b/src/bin/d2/ncr_msg.h
index b3a4153..4919f14 100644
--- a/src/bin/d2/ncr_msg.h
+++ b/src/bin/d2/ncr_msg.h
@@ -73,13 +73,13 @@ public:
     /// a contiguous stream of digits, with no delimiters. For example a string
     /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
     ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// @throw NcrMessageError if the input data contains non-digits
     /// or there is an odd number of digits.
     D2Dhcid(const std::string& data);
 
     /// @brief Returns the DHCID value as a string of hexadecimal digits.
     ///
-    /// @return returns a string containing a contiguous stream of digits.
+    /// @return a string containing a contiguous stream of digits.
     std::string toStr() const;
 
     /// @brief Sets the DHCID value based on the given string.
@@ -88,17 +88,27 @@ public:
     /// a contiguous stream of digits, with no delimiters. For example a string
     /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
     ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// @throw NcrMessageError if the input data contains non-digits
     /// or there is an odd number of digits.
     void fromStr(const std::string& data);
 
     /// @brief Returns a reference to the DHCID byte vector.
     ///
-    /// @return returns a reference to the vector.
+    /// @return a reference to the vector.
     const std::vector<uint8_t>& getBytes() {
         return (bytes_);
     }
 
+    /// @brief Compares two D2Dhcids for equality
+    bool operator==(const D2Dhcid& other) const {
+        return (this->bytes_ == other.bytes_);
+    }
+
+    /// @brief Compares two D2Dhcids for inequality
+    bool operator!=(const D2Dhcid other) const {
+        return (this->bytes_ != other.bytes_);
+}
+
 private:
     /// @brief Storage for the DHCID value in unsigned bytes.
     std::vector<uint8_t> bytes_;
@@ -163,9 +173,9 @@ public:
     /// @param format indicates the data format to use
     /// @param buffer is the input buffer containing the marshalled request
     ///
-    /// @return returns a pointer to the new NameChangeRequest
+    /// @return a pointer to the new NameChangeRequest
     ///
-    /// @throw throws NcrMessageError if an error occurs creating new
+    /// @throw NcrMessageError if an error occurs creating new
     /// request.
     static NameChangeRequestPtr fromFormat(const NameChangeFormat format,
                                            isc::util::InputBuffer& buffer);
@@ -194,15 +204,15 @@ public:
     ///
     /// @param json is a string containing the JSON text
     ///
-    /// @return returns a pointer to the new NameChangeRequest
+    /// @return a pointer to the new NameChangeRequest
     ///
-    /// @throw throws NcrMessageError if an error occurs creating new request.
+    /// @throw NcrMessageError if an error occurs creating new request.
     static NameChangeRequestPtr fromJSON(const std::string& json);
 
     /// @brief Instance method for marshalling the contents of the request
     /// into a string of JSON text.
     ///
-    /// @return returns a string containing the JSON rendition of the request
+    /// @return a string containing the JSON rendition of the request
     std::string toJSON() const;
 
     /// @brief Validates the content of a populated request.  This method is
@@ -221,13 +231,13 @@ public:
     /// of validation.  FQDN, DHCID, and IP Address members are all currently
     /// strings, these may be replaced with richer classes.
     ///
-    /// @throw throws a NcrMessageError if the request content violates any
+    /// @throw NcrMessageError if the request content violates any
     /// of the validation rules.
     void validateContent();
 
     /// @brief Fetches the request change type.
     ///
-    /// @return returns the change type
+    /// @return the change type
     NameChangeType getChangeType() const {
         return (change_type_);
     }
@@ -241,13 +251,13 @@ public:
     ///
     /// @param element is an integer Element containing the change type value.
     ///
-    /// @throw throws a NcrMessageError if the element is not an integer
+    /// @throw NcrMessageError if the element is not an integer
     /// Element or contains an invalid value.
     void setChangeType(isc::data::ConstElementPtr element);
 
     /// @brief Checks forward change flag.
     ///
-    /// @return returns a true if the forward change flag is true.
+    /// @return a true if the forward change flag is true.
     bool isForwardChange() const {
         return (forward_change_);
     }
@@ -263,13 +273,13 @@ public:
     /// @param element is a boolean Element containing the forward change flag
     /// value.
     ///
-    /// @throw throws a NcrMessageError if the element is not a boolean
+    /// @throw NcrMessageError if the element is not a boolean
     /// Element
     void setForwardChange(isc::data::ConstElementPtr element);
 
     /// @brief Checks reverse change flag.
     ///
-    /// @return returns a true if the reverse change flag is true.
+    /// @return a true if the reverse change flag is true.
     bool isReverseChange() const {
         return (reverse_change_);
     }
@@ -285,13 +295,13 @@ public:
     /// @param element is a boolean Element containing the reverse change flag
     /// value.
     ///
-    /// @throw throws a NcrMessageError if the element is not a boolean
+    /// @throw NcrMessageError if the element is not a boolean
     /// Element
     void setReverseChange(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request FQDN
     ///
-    /// @return returns a string containing the FQDN
+    /// @return a string containing the FQDN
     const std::string getFqdn() const {
         return (fqdn_);
     }
@@ -305,13 +315,13 @@ public:
     ///
     /// @param element is a string Element containing the FQDN
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw NcrMessageError if the element is not a string
     /// Element
     void setFqdn(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request IP address.
     ///
-    /// @return returns a string containing the IP address
+    /// @return a string containing the IP address
     const std::string& getIpAddress() const {
         return (ip_address_);
     }
@@ -325,13 +335,13 @@ public:
     ///
     /// @param element is a string Element containing the IP address
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw NcrMessageError if the element is not a string
     /// Element
     void setIpAddress(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request DHCID
     ///
-    /// @return returns a reference to the request's D2Dhcid
+    /// @return a reference to the request's D2Dhcid
     const D2Dhcid& getDhcid() const {
         return (dhcid_);
     }
@@ -342,7 +352,7 @@ public:
     /// a contiguous stream of digits, with no delimiters. For example a string
     /// containing "14A3" converts to a byte array containing:  0x14, 0xA3.
     ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// @throw NcrMessageError if the input data contains non-digits
     /// or there is an odd number of digits.
     void setDhcid(const std::string& value);
 
@@ -351,13 +361,13 @@ public:
     /// @param element is a string Element containing the string of hexadecimal
     /// digits. (See setDhcid(std::string&) above.)
     ///
-    /// @throw throws a NcrMessageError if the input data contains non-digits
+    /// @throw NcrMessageError if the input data contains non-digits
     /// or there is an odd number of digits.
     void setDhcid(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request lease expiration
     ///
-    /// @return returns the lease expiration as the number of seconds since
+    /// @return the lease expiration as the number of seconds since
     /// the (00:00:00 January 1, 1970)
     uint64_t getLeaseExpiresOn() const {
         return (lease_expires_on_);
@@ -372,7 +382,7 @@ public:
     /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
     /// NOTE This is always UTC time.
     ///
-    /// @return returns a ISO date-time string of the lease expiration.
+    /// @return a ISO date-time string of the lease expiration.
     std::string getLeaseExpiresOnStr() const;
 
     /// @brief Sets the lease expiration based on the given string.
@@ -385,20 +395,20 @@ public:
     /// Example: 18:54:54 June 26, 2013 would be: 20130626185455
     /// NOTE This is always UTC time.
     ///
-    /// @throw throws a NcrMessageError if the ISO string is invalid.
+    /// @throw NcrMessageError if the ISO string is invalid.
     void setLeaseExpiresOn(const std::string& value);
 
     /// @brief Sets the lease expiration based on the given Element.
     ///
     /// @param element is string Element containing a date-time string.
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw NcrMessageError if the element is not a string
     /// Element, or if the element value is an invalid date-time string.
     void setLeaseExpiresOn(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request lease length.
     ///
-    /// @return returns an integer containing the lease length
+    /// @return an integer containing the lease length
     uint32_t getLeaseLength() const {
         return (lease_length_);
     }
@@ -412,13 +422,13 @@ public:
     ///
     /// @param element is a integer Element containing the lease length
     ///
-    /// @throw throws a NcrMessageError if the element is not a string
+    /// @throw NcrMessageError if the element is not a string
     /// Element
     void setLeaseLength(isc::data::ConstElementPtr element);
 
     /// @brief Fetches the request status.
     ///
-    /// @return returns the request status as a NameChangeStatus
+    /// @return the request status as a NameChangeStatus
     NameChangeStatus getStatus() const {
         return (status_);
     }
@@ -434,8 +444,8 @@ public:
     /// @param name is the name of the desired element
     /// @param element_map is the map of elements to search
     ///
-    /// @return returns a pointer to the element if located
-    /// @throw throws a NcrMessageError if the element cannot be found within
+    /// @return a pointer to the element if located
+    /// @throw NcrMessageError if the element cannot be found within
     /// the map
     isc::data::ConstElementPtr getElement(const std::string& name,
                                           const ElementMap& element_map) const;
@@ -443,9 +453,12 @@ public:
     /// @brief Returns a text rendition of the contents of the request.
     /// This method is primarily for logging purposes.
     ///
-    /// @return returns a string containing the text.
+    /// @return a string containing the text.
     std::string toText() const;
 
+    bool operator == (const NameChangeRequest& b);
+    bool operator != (const NameChangeRequest& b);
+
 private:
     /// @brief Denotes the type of this change as either an Add or a Remove.
     NameChangeType change_type_;
diff --git a/src/bin/d2/ncr_udp.cc b/src/bin/d2/ncr_udp.cc
new file mode 100644
index 0000000..120f2b2
--- /dev/null
+++ b/src/bin/d2/ncr_udp.cc
@@ -0,0 +1,299 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <d2/d2_log.h>
+#include <d2/ncr_udp.h>
+
+#include <asio/ip/udp.hpp>
+#include <asio/error_code.hpp>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace d2 {
+
+//*************************** UDPCallback ***********************
+UDPCallback::UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+                          UDPEndpointPtr& data_source,
+                          const UDPCompletionHandler& handler)
+    : handler_(handler), data_(new Data(buffer, buf_size, data_source)) {
+    if (handler.empty()) {
+        isc_throw(NcrUDPError, "UDPCallback - handler can't be null");
+    }
+
+    if (!buffer) {
+        isc_throw(NcrUDPError, "UDPCallback - buffer can't be null");
+    }
+}
+
+void
+UDPCallback::operator ()(const asio::error_code error_code,
+                         const size_t bytes_transferred) {
+
+    // Save the result state and number of bytes transferred.
+    setErrorCode(error_code);
+    setBytesTransferred(bytes_transferred);
+
+    // Invoke the NameChangeRequest layer completion handler.
+    // First argument is a boolean indicating success or failure.
+    // The second is a pointer to "this" callback object. By passing
+    // ourself in, we make all of the service related data available
+    // to the completion handler.
+    handler_(!error_code, this);
+}
+
+void
+UDPCallback::putData(const uint8_t* src, size_t len) {
+    if (!src) {
+        isc_throw(NcrUDPError, "UDPCallback putData, data source is NULL");
+    }
+
+    if (len > data_->buf_size_) {
+        isc_throw(NcrUDPError, "UDPCallback putData, data length too large");
+    }
+
+    memcpy (data_->buffer_.get(), src, len);
+    data_->put_len_ = len;
+}
+
+
+//*************************** NameChangeUDPListener ***********************
+NameChangeUDPListener::
+NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+                      const uint32_t port, NameChangeFormat format,
+                      RequestReceiveHandler& ncr_recv_handler,
+                      const bool reuse_address)
+    : NameChangeListener(ncr_recv_handler), ip_address_(ip_address),
+      port_(port), format_(format), reuse_address_(reuse_address) {
+    // Instantiate the receive callback.  This gets passed into each receive.
+    // Note that the callback constructor is passed an instance method
+    // pointer to our completion handler method, receiveCompletionHandler.
+    RawBufferPtr buffer(new uint8_t[RECV_BUF_MAX]);
+    UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+    recv_callback_.reset(new
+                         UDPCallback(buffer, RECV_BUF_MAX, data_source,
+                                     boost::bind(&NameChangeUDPListener::
+                                     receiveCompletionHandler, this, _1, _2)));
+}
+
+NameChangeUDPListener::~NameChangeUDPListener() {
+    // Clean up.
+    stopListening();
+}
+
+void
+NameChangeUDPListener::open(isc::asiolink::IOService& io_service) {
+    // create our endpoint and bind the the low level socket to it.
+    isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+    // Create the low level socket.
+    try {
+        asio_socket_.reset(new asio::ip::udp::
+                           socket(io_service.get_io_service(),
+                                  (ip_address_.isV4() ? asio::ip::udp::v4() :
+                                   asio::ip::udp::v6())));
+
+        // Set the socket option to reuse addresses if it is enabled.
+        if (reuse_address_) {
+            asio_socket_->set_option(asio::socket_base::reuse_address(true));
+        }
+
+        // Bind the low level socket to our endpoint.
+        asio_socket_->bind(endpoint.getASIOEndpoint());
+    } catch (asio::system_error& ex) {
+        isc_throw (NcrUDPError, ex.code().message());
+    }
+
+    // Create the asiolink socket from the low level socket.
+    socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+}
+
+
+void
+NameChangeUDPListener::doReceive() {
+    // Call the socket's asychronous receiving, passing ourself in as callback.
+    RawBufferPtr recv_buffer = recv_callback_->getBuffer();
+    socket_->asyncReceive(recv_buffer.get(), recv_callback_->getBufferSize(),
+                          0, recv_callback_->getDataSource().get(),
+                          *recv_callback_);
+}
+
+void
+NameChangeUDPListener::close() {
+    // Whether we think we are listening or not, make sure we aren't.
+    // Since we are managing our own socket, we need to cancel and close
+    // it ourselves.
+    if (asio_socket_) {
+        try {
+            asio_socket_->cancel();
+            asio_socket_->close();
+        } catch (asio::system_error& ex) {
+            // It is really unlikely that this will occur.
+            // If we do reopen later it will be with a new socket instance.
+            // Repackage exception as one that is conformant with the interface.
+            isc_throw (NcrUDPError, ex.code().message());
+        }
+    }
+}
+
+void
+NameChangeUDPListener::receiveCompletionHandler(const bool successful,
+                                                const UDPCallback *callback) {
+    NameChangeRequestPtr ncr;
+    Result result = SUCCESS;
+
+    if (successful) {
+        // Make an InputBuffer from our internal array
+        isc::util::InputBuffer input_buffer(callback->getData(),
+                                            callback->getBytesTransferred());
+
+        try {
+            ncr = NameChangeRequest::fromFormat(format_, input_buffer);
+        } catch (const NcrMessageError& ex) {
+            // log it and go back to listening
+            LOG_ERROR(dctl_logger, DHCP_DDNS_INVALID_NCR).arg(ex.what());
+
+            // Queue up the next recieve.
+            doReceive();
+            return;
+        }
+    } else {
+        asio::error_code error_code = callback->getErrorCode();
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+                  .arg(error_code.message());
+        result = ERROR;
+    }
+
+    // Call the application's registered request receive handler.
+    invokeRecvHandler(result, ncr);
+}
+
+
+//*************************** NameChangeUDPSender ***********************
+
+NameChangeUDPSender::
+NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+                    const uint32_t port,
+                    const isc::asiolink::IOAddress& server_address,
+                    const uint32_t server_port, const NameChangeFormat format,
+                    RequestSendHandler& ncr_send_handler,
+                    const size_t send_que_max, const bool reuse_address)
+    : NameChangeSender(ncr_send_handler, send_que_max),
+      ip_address_(ip_address), port_(port), server_address_(server_address),
+      server_port_(server_port), format_(format),
+      reuse_address_(reuse_address) {
+    // Instantiate the send callback.  This gets passed into each send.
+    // Note that the callback constructor is passed the an instance method
+    // pointer to our completion handler, sendCompletionHandler.
+    RawBufferPtr buffer(new uint8_t[SEND_BUF_MAX]);
+    UDPEndpointPtr data_source(new asiolink::UDPEndpoint());
+    send_callback_.reset(new UDPCallback(buffer, SEND_BUF_MAX, data_source,
+                                         boost::bind(&NameChangeUDPSender::
+                                         sendCompletionHandler, this,
+                                         _1, _2)));
+}
+
+NameChangeUDPSender::~NameChangeUDPSender() {
+    // Clean up.
+    stopSending();
+}
+
+void
+NameChangeUDPSender::open(isc::asiolink::IOService& io_service) {
+    // create our endpoint and bind the the low level socket to it.
+    isc::asiolink::UDPEndpoint endpoint(ip_address_.getAddress(), port_);
+
+    // Create the low level socket.
+    try {
+        asio_socket_.reset(new asio::ip::udp::
+                           socket(io_service.get_io_service(),
+                                  (ip_address_.isV4() ? asio::ip::udp::v4() :
+                                   asio::ip::udp::v6())));
+
+        // Set the socket option to reuse addresses if it is enabled.
+        if (reuse_address_) {
+            asio_socket_->set_option(asio::socket_base::reuse_address(true));
+        }
+
+        // Bind the low leve socket to our endpoint.
+        asio_socket_->bind(endpoint.getASIOEndpoint());
+    } catch (asio::system_error& ex) {
+        isc_throw (NcrUDPError, ex.code().message());
+    }
+
+    // Create the asiolink socket from the low level socket.
+    socket_.reset(new NameChangeUDPSocket(*asio_socket_));
+
+    // Create the server endpoint
+    server_endpoint_.reset(new isc::asiolink::
+                           UDPEndpoint(server_address_.getAddress(),
+                                       server_port_));
+
+    send_callback_->setDataSource(server_endpoint_);
+}
+
+void
+NameChangeUDPSender::close() {
+    // Whether we think we are sending or not, make sure we aren't.
+    // Since we are managing our own socket, we need to cancel and close
+    // it ourselves.
+    if (asio_socket_) {
+        try {
+            asio_socket_->cancel();
+            asio_socket_->close();
+        } catch (asio::system_error& ex) {
+            // It is really unlikely that this will occur.
+            // If we do reopen later it will be with a new socket instance.
+            // Repackage exception as one that is conformant with the interface.
+            isc_throw (NcrUDPError, ex.code().message());
+        }
+    }
+}
+
+void
+NameChangeUDPSender::doSend(NameChangeRequestPtr& ncr) {
+    // Now use the NCR to write JSON to an output buffer.
+    isc::util::OutputBuffer ncr_buffer(SEND_BUF_MAX);
+    ncr->toFormat(format_, ncr_buffer);
+
+    // Copy the wire-ized request to callback.  This way we know after
+    // send completes what we sent (or attempted to send).
+    send_callback_->putData(static_cast<const uint8_t*>(ncr_buffer.getData()),
+                            ncr_buffer.getLength());
+
+    // Call the socket's asychronous send, passing our callback
+    socket_->asyncSend(send_callback_->getData(), send_callback_->getPutLen(),
+                       send_callback_->getDataSource().get(), *send_callback_);
+}
+
+void
+NameChangeUDPSender::sendCompletionHandler(const bool successful,
+                                           const UDPCallback *send_callback) {
+    Result result;
+    if (successful) {
+        result = SUCCESS;
+    }
+    else {
+        // On a failure, log the error and set the result to ERROR.
+        asio::error_code error_code = send_callback->getErrorCode();
+        LOG_ERROR(dctl_logger, DHCP_DDNS_NCR_UDP_RECV_ERROR)
+                  .arg(error_code.message());
+
+        result = ERROR;
+    }
+
+    // Call the application's registered request send handler.
+    invokeSendHandler(result);
+}
+}; // end of isc::d2 namespace
+}; // end of isc namespace
diff --git a/src/bin/d2/ncr_udp.h b/src/bin/d2/ncr_udp.h
new file mode 100644
index 0000000..c20d2f5
--- /dev/null
+++ b/src/bin/d2/ncr_udp.h
@@ -0,0 +1,562 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef NCR_UDP_LISTENER_H
+#define NCR_UDP_LISTENER_H
+
+/// @file ncr_udp.h
+/// @brief This file provides UDP socket based implementation for sending and
+/// receiving NameChangeRequests
+///
+/// These classes are derived from the abstract classes, NameChangeListener
+/// and NameChangeSender (see ncr_io.h).
+///
+/// The following discussion will refer to three layers of communications:
+///
+///    * Application layer - This is the business layer which needs to
+///    transport NameChangeRequests, and is unaware of the means by which
+///    they are transported.
+///
+///    * IO layer - This is the low-level layer that is directly responsible
+///    for sending and receiving data asynchronously and is supplied through
+///    other libraries.  This layer is largely unaware of the nature of the
+///    data being transmitted.  In other words, it doesn't know beans about
+///    NCRs.
+///
+///    * NameChangeRequest layer - This is the layer which acts as the
+///    intermediary between the Application layer and the IO layer.  It must
+///    be able to move NameChangeRequests to the IO layer as raw data and move
+///    raw data from the IO layer in the Application layer as
+///    NameChangeRequests.
+///
+/// This file defines NameChangeUDPListener class for receiving NCRs, and
+/// NameChangeUDPSender for sending NCRs.
+///
+/// Both the listener and sender implementations utilize the same underlying
+/// construct to move NCRs to and from a UDP socket. This construct consists
+/// of a set of classes centered around isc::asiolink::UDPSocket.  UDPSocket
+/// is a templated class that supports asio asynchronous event processing; and
+/// which accepts as its parameter, the name of a callback class.
+///
+/// The asynchronous services provided by UDPSocket typically accept a buffer
+/// for transferring data (either in or out depending on the service direction)
+/// and an object which supplies a callback to invoke upon completion of the
+/// service.
+///
+/// The callback class must provide an operator() with the following signature:
+/// @code
+///    void operator ()(const asio::error_code error_code,
+///                     const size_t bytes_transferred);
+/// @endcode
+///
+/// Upon completion of the service, the callback instance's operator() is
+/// invoked by the asio layer.  It is given both a outcome result and the
+/// number of bytes either read or written, to or from the buffer supplied
+/// to the service.
+///
+/// Typically, an asiolink based implementation would simply implement the
+/// callback operator directly.  However, the nature of the asiolink library
+/// is such that the callback object may be copied several times during course
+/// of a service invocation.  This implies that any class being used as a
+/// callback class must be copyable.  This is not always desirable.  In order
+/// to separate the callback class from the NameChangeRequest, the construct
+/// defines the UDPCallback class for use as a copyable, callback object.
+///
+/// The UDPCallback class provides the asiolink layer callback operator(),
+/// which is invoked by the asiolink layer upon service completion.  It
+/// contains:
+///    * a pointer to the transfer buffer
+///    * the capacity of the transfer buffer
+///    * a IO layer outcome result
+///    * the number of bytes transferred
+///    * a method pointer to a NameChangeRequest layer completion handler
+///
+/// This last item, is critical. It points to an instance method that
+/// will be invoked by the UDPCallback operator.  This provides access to
+/// the outcome of the service call to the NameChangeRequest layer without
+/// that layer being used as the actual callback object.
+///
+/// The completion handler method signature is codified in the typedef,
+/// UDPCompletionHandler, and must be as follows:
+///
+/// @code
+///     void(const bool, const UDPCallback*)
+/// @endcode
+///
+/// Note that is accepts two parameters.  The first is a boolean indicator
+/// which indicates if the service call completed successfully or not.  The
+/// second is a pointer to the callback object invoked by the IOService upon
+/// completion of the service.  The callback instance will contain all of the
+/// pertinent information about the invocation and outcome of the service.
+///
+/// Using the contents of the callback, it is the responsibility of the
+/// UDPCompletionHandler to interpret the results of the service invocation and
+/// pass the interpretation to the application layer via either
+/// NameChangeListener::invokeRecvHandler in the case of the UDP listener, or
+/// NameChangeSender::invokeSendHandler in the case of UDP sender.
+///
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/io_service.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <d2/ncr_io.h>
+#include <util/buffer.h>
+
+#include <boost/shared_array.hpp>
+
+/// responsibility of the completion handler to perform the steps necessary
+/// to interpret the raw data provided by the service outcome.   The
+/// UDPCallback operator implementation is mostly a pass through.
+///
+namespace isc {
+namespace d2 {
+
+/// @brief Thrown when a UDP level exception occurs.
+class NcrUDPError : public isc::Exception {
+public:
+    NcrUDPError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) { };
+};
+
+class UDPCallback;
+/// @brief Defines a function pointer for NameChangeRequest completion handlers.
+typedef boost::function<void(const bool, const UDPCallback*)>
+          UDPCompletionHandler;
+
+/// @brief Defines a dynamically allocated shared array.
+typedef boost::shared_array<uint8_t> RawBufferPtr;
+
+typedef boost::shared_ptr<asiolink::UDPEndpoint> UDPEndpointPtr;
+
+/// @brief Implements the callback class passed into UDPSocket calls.
+///
+/// It serves as the link between the asiolink::UDPSocket asynchronous services
+/// and the NameChangeRequest layer. The class provides the asiolink layer
+/// callback operator(), which is invoked by the asiolink layer upon service
+/// completion.  It contains all of the data pertinent to both the invocation
+/// and completion of a service, as well as a pointer to NameChangeRequest
+/// layer completion handler to invoke.
+///
+class UDPCallback {
+
+public:
+    /// @brief Container class which stores service invocation related data.
+    ///
+    /// Because the callback object may be copied numerous times during the
+    /// course of service invocation, it does not directly contain data values.
+    /// Rather it will retain a shared pointer to an instance of this structure
+    /// thus ensuring that all copies of the callback object, ultimately refer
+    /// to the same data values.
+    struct Data {
+
+        /// @brief Constructor
+        ///
+        /// @param buffer is a pointer to the data transfer buffer. This is
+        /// the buffer data will be written to on a read, or read from on a
+        /// send.
+        /// @param buf_size is the capacity of the buffer
+        /// @param data_source storage for UDP endpoint which supplied the data
+        Data(RawBufferPtr& buffer, const size_t buf_size,
+             UDPEndpointPtr& data_source)
+            : buffer_(buffer), buf_size_(buf_size), data_source_(data_source),
+              put_len_(0), error_code_(), bytes_transferred_(0) {
+        };
+
+        /// @brief A pointer to the data transfer buffer.
+        RawBufferPtr buffer_;
+
+        /// @brief Storage capacity of the buffer.
+        size_t buf_size_;
+
+        /// @brief The UDP endpoint that is the origin of the data transferred.
+        UDPEndpointPtr data_source_;
+
+        /// @brief Stores this size of the data within the buffer when written
+        /// there manually. (See UDPCallback::putData()) .
+        size_t put_len_;
+
+        /// @brief Stores the IO layer result code of the completed IO service.
+        asio::error_code error_code_;
+
+        /// @brief Stores the number of bytes transferred by completed IO
+        /// service.
+        /// For a read it is the number of bytes written into the
+        /// buffer.  For a write it is the number of bytes read from the
+        /// buffer.
+        size_t bytes_transferred_;
+
+    };
+
+    /// @brief Used as the callback object for UDPSocket services.
+    ///
+    /// @param buffer is a pointer to the data transfer buffer. This is
+    /// the buffer data will be written to on a read, or read from on a
+    /// send.
+    /// @param buf_size is the capacity of the buffer
+    /// @param data_source storage for UDP endpoint which supplied the data
+    /// @param handler is a method pointer to the completion handler that
+    /// is to be called by the operator() implementation.
+    ///
+    /// @throw NcrUDPError if either the handler or buffer pointers
+    /// are invalid.
+    UDPCallback (RawBufferPtr& buffer, const size_t buf_size,
+                 UDPEndpointPtr& data_source,
+                 const UDPCompletionHandler& handler);
+
+    /// @brief Operator that will be invoked by the asiolink layer.
+    ///
+    /// @param error_code is the IO layer result code of the
+    /// completed IO service.
+    /// @param bytes_transferred is the number of bytes transferred by
+    /// completed IO.
+    /// For a read it is the number of bytes written into the
+    /// buffer.  For a write it is the number of bytes read from the
+    /// buffer.
+    void operator ()(const asio::error_code error_code,
+                     const size_t bytes_transferred);
+
+    /// @brief Returns the number of bytes transferred by the completed IO
+    /// service.
+    ///
+    /// For a read it is the number of bytes written into the
+    /// buffer.  For a write it is the number of bytes read from the
+    /// buffer.
+    size_t getBytesTransferred() const {
+        return (data_->bytes_transferred_);
+    }
+
+    /// @brief Sets the number of bytes transferred.
+    ///
+    /// @param value is the new value to assign to bytes transferred.
+    void setBytesTransferred(const size_t value) {
+        data_->bytes_transferred_ = value;
+    }
+
+    /// @brief Returns the completed IO layer service outcome status.
+    asio::error_code getErrorCode() const {
+        return (data_->error_code_);
+    }
+
+    /// @brief Sets the completed IO layer service outcome status.
+    ///
+    /// @param value is the new value to assign to outcome status.
+    void setErrorCode(const asio::error_code value) {
+        data_->error_code_  = value;
+    }
+
+    /// @brief Returns the data transfer buffer.
+    RawBufferPtr getBuffer() const {
+        return (data_->buffer_);
+    }
+
+    /// @brief Returns the data transfer buffer capacity.
+    size_t getBufferSize() const {
+        return (data_->buf_size_);
+    }
+
+    /// @brief Returns a pointer the data transfer buffer content.
+    const uint8_t* getData() const {
+        return (data_->buffer_.get());
+    }
+
+    /// @brief Copies data into the data transfer buffer.
+    ///
+    /// Copies the given number of bytes from the given source buffer
+    /// into the data transfer buffer, and updates the value of put length.
+    /// This method may be used when performing sends to make a copy of
+    /// the "raw data" that was shipped (or attempted) accessible to the
+    /// upstream callback.
+    ///
+    /// @param src is a pointer to the data source from which to copy
+    /// @param len is the number of bytes to copy
+    ///
+    /// @throw NcrUDPError if the number of bytes to copy exceeds
+    /// the buffer capacity or if the source pointer is invalid.
+    void putData(const uint8_t* src, size_t len);
+
+    /// @brief Returns the number of bytes manually written into the
+    /// transfer buffer.
+    size_t getPutLen() const {
+        return (data_->put_len_);
+    }
+
+    /// @brief Sets the data source to the given endpoint.
+    ///
+    /// @param endpoint is the new value to assign to data source.
+    void setDataSource(UDPEndpointPtr& endpoint) {
+        data_->data_source_ = endpoint;
+    }
+
+    /// @brief Returns the UDP endpoint that provided the transferred data.
+    const UDPEndpointPtr& getDataSource() {
+        return (data_->data_source_);
+    }
+
+  private:
+    /// @brief NameChangeRequest layer completion handler to invoke.
+    UDPCompletionHandler handler_;
+
+    /// @brief Shared pointer to the service data container.
+    boost::shared_ptr<Data> data_;
+};
+
+/// @brief Convenience type for UDP socket based listener
+typedef isc::asiolink::UDPSocket<UDPCallback> NameChangeUDPSocket;
+
+/// @brief Provides the ability to receive NameChangeRequests via  UDP socket
+///
+/// This class is a derivation of the NameChangeListener which is capable of
+/// receiving NameChangeRequests through a UDP socket.  The caller need only
+/// supply network addressing and a RequestReceiveHandler instance to receive
+/// NameChangeRequests asynchronously.
+class NameChangeUDPListener : public NameChangeListener {
+public:
+    /// @brief Defines the maximum size packet that can be received.
+    static const size_t RECV_BUF_MAX = isc::asiolink::
+                                       UDPSocket<UDPCallback>::MIN_SIZE;
+
+    /// @brief Constructor
+    ///
+    /// @param ip_address is the network address on which to listen
+    /// @param port is the UDP port on which to listen
+    /// @param format is the wire format of the inbound requests. Currently
+    /// only JSON is supported
+    /// @param ncr_recv_handler the receive handler object to notify when
+    /// a receive completes.
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    /// @throw base class throws NcrListenerError if handler is invalid.
+    NameChangeUDPListener(const isc::asiolink::IOAddress& ip_address,
+                          const uint32_t port,
+                          const NameChangeFormat format,
+                          RequestReceiveHandler& ncr_recv_handler,
+                          const bool reuse_address = false);
+
+    /// @brief Destructor.
+    virtual ~NameChangeUDPListener();
+
+    /// @brief Opens a UDP socket using the given IOService.
+    ///
+    /// Creates a NameChangeUDPSocket bound to the listener's ip address
+    /// and port, that is monitored by the given IOService instance.
+    ///
+    /// @param io_service the IOService which will monitor the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void open(isc::asiolink::IOService& io_service);
+
+    /// @brief Closes the UDPSocket.
+    ///
+    /// It first invokes the socket's cancel method which should stop any
+    /// pending read and remove the socket callback from the IOService. It
+    /// then calls the socket's close method to actually close the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void close();
+
+    /// @brief Initiates an asynchronous read on the socket.
+    ///
+    /// Invokes the asyncReceive() method on the socket passing in the
+    /// recv_callback_ member's transfer buffer as the receive buffer, and
+    /// recv_callback_ itself as the callback object.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    void doReceive();
+
+    /// @brief Implements the NameChangeRequest level receive completion
+    /// handler.
+    ///
+    /// This method is invoked by the UPDCallback operator() implementation,
+    /// passing in the boolean success indicator and pointer to itself.
+    ///
+    /// If the indicator denotes success, then the method will attempt to
+    /// to construct a NameChangeRequest from the received data.  If the
+    /// construction was successful, it will send the new NCR to the
+    /// application layer by calling invokeRecvHandler() with a success
+    /// status and a pointer to the new NCR.
+    ///
+    /// If the buffer contains invalid data such that construction fails,
+    /// the method will log the failure and then call doReceive() to start a
+    /// initiate the next receive.
+    ///
+    /// If the indicator denotes failure the method will log the failure and
+    /// notify the application layer by calling invokeRecvHandler() with
+    /// an error status and an empty pointer.
+    ///
+    /// @param successful boolean indicator that should be true if the
+    /// socket receive completed without error, false otherwise.
+    /// @param recv_callback pointer to the callback instance which handled
+    /// the socket receive completion.
+    void receiveCompletionHandler(const bool successful,
+                                  const UDPCallback* recv_callback);
+private:
+    /// @brief IP address on which to listen for requests.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief Port number on which to listen for requests.
+    uint32_t port_;
+
+    /// @brief Wire format of the inbound requests.
+    NameChangeFormat format_;
+
+    /// @brief Low level socket underneath the listening socket
+    boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+    /// @brief NameChangeUDPSocket listening socket
+    boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+    /// @brief Pointer to the receive callback
+    boost::shared_ptr<UDPCallback> recv_callback_;
+
+    /// @brief Flag which enables the reuse address socket option if true.
+    bool reuse_address_;
+
+    ///
+    /// @name Copy and constructor assignment operator
+    ///
+    /// The copy constructor and assignment operator are private to avoid
+    /// potential issues with multiple listeners attempting to share sockets
+    /// and callbacks.
+private:
+    NameChangeUDPListener(const NameChangeUDPListener& source);
+    NameChangeUDPListener& operator=(const NameChangeUDPListener& source);
+    //@}
+};
+
+
+/// @brief Provides the ability to send NameChangeRequests via  UDP socket
+///
+/// This class is a derivation of the NameChangeSender which is capable of
+/// sending NameChangeRequests through a UDP socket.  The caller need only
+/// supply network addressing and a RequestSendHandler instance to send
+/// NameChangeRequests asynchronously.
+class NameChangeUDPSender : public NameChangeSender {
+public:
+
+    /// @brief Defines the maximum size packet that can be sent.
+    static const size_t SEND_BUF_MAX =  NameChangeUDPListener::RECV_BUF_MAX;
+
+    /// @brief Constructor
+    ///
+    /// @param ip_address the IP address from which to send
+    /// @param port the port from which to send
+    /// @param server_address the IP address of the target listener
+    /// @param server_port is the IP port  of the target listener
+    /// @param format is the wire format of the outbound requests.
+    /// @param ncr_send_handler the send handler object to notify when
+    /// when a send completes.
+    /// @param send_que_max sets the maximum number of entries allowed in
+    /// the send queue.
+    /// It defaults to NameChangeSender::MAX_QUEUE_DEFAULT
+    /// @param reuse_address enables IP address sharing when true
+    /// It defaults to false.
+    ///
+    NameChangeUDPSender(const isc::asiolink::IOAddress& ip_address,
+        const uint32_t port, const isc::asiolink::IOAddress& server_address,
+        const uint32_t server_port, const NameChangeFormat format,
+        RequestSendHandler& ncr_send_handler,
+        const size_t send_que_max = NameChangeSender::MAX_QUEUE_DEFAULT,
+        const bool reuse_address = false);
+
+    /// @brief Destructor
+    virtual ~NameChangeUDPSender();
+
+
+    /// @brief Opens a UDP socket using the given IOService.
+    ///
+    /// Creates a NameChangeUDPSocket bound to the sender's IP address
+    /// and port, that is monitored by the given IOService instance.
+    ///
+    /// @param io_service the IOService which will monitor the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void open(isc::asiolink::IOService& io_service);
+
+
+    /// @brief Closes the UDPSocket.
+    ///
+    /// It first invokes the socket's cancel method which should stop any
+    /// pending send and remove the socket callback from the IOService. It
+    /// then calls the socket's close method to actually close the socket.
+    ///
+    /// @throw NcrUDPError if the open fails.
+    virtual void close();
+
+    /// @brief Sends a given request asynchronously over the socket
+    ///
+    /// The given NameChangeRequest is converted to wire format and copied
+    /// into the send callback's transfer buffer.  Then the socket's
+    /// asyncSend() method is called, passing in send_callback_ member's
+    /// transfer buffer as the send buffer and the send_callback_ itself
+    /// as the callback object.
+    virtual void doSend(NameChangeRequestPtr& ncr);
+
+    /// @brief Implements the NameChangeRequest level send completion handler.
+    ///
+    /// This method is invoked by the UDPCallback operator() implementation,
+    /// passing in the boolean success indicator and pointer to itself.
+    ///
+    /// If the indicator denotes success, then the method will notify the
+    /// application layer by calling invokeSendHandler() with a success
+    /// status.
+    ///
+    /// If the indicator denotes failure the method will log the failure and
+    /// notify the application layer by calling invokeRecvHandler() with
+    /// an error status.
+    ///
+    /// @param successful boolean indicator that should be true if the
+    /// socket send completed without error, false otherwise.
+    /// @param send_callback pointer to the callback instance which handled
+    /// the socket receive completion.
+    void sendCompletionHandler(const bool successful,
+                               const UDPCallback* send_callback);
+
+private:
+    /// @brief IP address from which to send.
+    isc::asiolink::IOAddress ip_address_;
+
+    /// @brief Port from which to send.
+    uint32_t port_;
+
+    /// @brief IP address of the target listener.
+    isc::asiolink::IOAddress server_address_;
+
+    /// @brief Port of the target listener.
+    uint32_t server_port_;
+
+    /// @brief Wire format of the outbound requests.
+    NameChangeFormat format_;
+
+    /// @brief Low level socket underneath the sending socket.
+    boost::shared_ptr<asio::ip::udp::socket> asio_socket_;
+
+    /// @brief NameChangeUDPSocket sending socket.
+    boost::shared_ptr<NameChangeUDPSocket> socket_;
+
+    /// @brief Endpoint of the target listener.
+    boost::shared_ptr<isc::asiolink::UDPEndpoint> server_endpoint_;
+
+    /// @brief Pointer to the send callback
+    boost::shared_ptr<UDPCallback> send_callback_;
+
+    /// @brief Flag which enables the reuse address socket option if true.
+    bool reuse_address_;
+};
+
+} // namespace isc::d2
+} // namespace isc
+
+#endif
diff --git a/src/bin/d2/tests/Makefile.am b/src/bin/d2/tests/Makefile.am
index 00bd36f..81a6f56 100644
--- a/src/bin/d2/tests/Makefile.am
+++ b/src/bin/d2/tests/Makefile.am
@@ -62,7 +62,9 @@ d2_unittests_SOURCES += ../d2_cfg_mgr.cc ../d2_cfg_mgr.h
 d2_unittests_SOURCES += ../d2_update_message.cc ../d2_update_message.h
 d2_unittests_SOURCES += ../d2_zone.cc ../d2_zone.h
 d2_unittests_SOURCES += ../dns_client.cc ../dns_client.h
+d2_unittests_SOURCES += ../ncr_io.cc ../ncr_io.h
 d2_unittests_SOURCES += ../ncr_msg.cc ../ncr_msg.h
+d2_unittests_SOURCES += ../ncr_udp.cc ../ncr_udp.h
 d2_unittests_SOURCES += d_test_stubs.cc d_test_stubs.h
 d2_unittests_SOURCES += d2_unittests.cc
 d2_unittests_SOURCES += d2_process_unittests.cc
@@ -74,6 +76,7 @@ d2_unittests_SOURCES += d2_update_message_unittests.cc
 d2_unittests_SOURCES += d2_zone_unittests.cc
 d2_unittests_SOURCES += dns_client_unittests.cc
 d2_unittests_SOURCES += ncr_unittests.cc
+d2_unittests_SOURCES += ncr_udp_unittests.cc
 nodist_d2_unittests_SOURCES = ../d2_messages.h ../d2_messages.cc
 
 d2_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
diff --git a/src/bin/d2/tests/ncr_udp_unittests.cc b/src/bin/d2/tests/ncr_udp_unittests.cc
new file mode 100644
index 0000000..8287fc2
--- /dev/null
+++ b/src/bin/d2/tests/ncr_udp_unittests.cc
@@ -0,0 +1,498 @@
+// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <asiolink/interval_timer.h>
+#include <d2/ncr_io.h>
+#include <d2/ncr_udp.h>
+#include <util/time_utilities.h>
+
+#include <asio/ip/udp.hpp>
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <gtest/gtest.h>
+#include <algorithm>
+
+using namespace std;
+using namespace isc;
+using namespace isc::d2;
+
+namespace {
+
+/// @brief Defines a list of valid JSON NameChangeRequest test messages.
+const char *valid_msgs[] =
+{
+    // Valid Add.
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+    // Valid Remove.
+     "{"
+     " \"change_type\" : 1 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"192.168.2.1\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}",
+     // Valid Add with IPv6 address
+     "{"
+     " \"change_type\" : 0 , "
+     " \"forward_change\" : true , "
+     " \"reverse_change\" : false , "
+     " \"fqdn\" : \"walah.walah.com\" , "
+     " \"ip_address\" : \"fe80::2acf:e9ff:fe12:e56f\" , "
+     " \"dhcid\" : \"010203040A7F8E3D\" , "
+     " \"lease_expires_on\" : \"20130121132405\" , "
+     " \"lease_length\" : 1300 "
+     "}"
+};
+
+const char* TEST_ADDRESS = "127.0.0.1";
+const uint32_t LISTENER_PORT = 5301;
+const uint32_t SENDER_PORT = LISTENER_PORT+1;
+const long TEST_TIMEOUT = 5 * 1000;
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleListenHandler : public NameChangeListener::RequestReceiveHandler {
+public:
+    virtual void operator ()(const NameChangeListener::Result,
+                             NameChangeRequestPtr&) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPListener constructors.
+/// This test verifies that:
+/// 1. Given valid parameters, the listener constructor works
+TEST(NameChangeUDPListenerBasicTest, constructionTests) {
+    // Verify the default constructor works.
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = LISTENER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleListenHandler ncr_handler;
+    // Verify that valid constructor works.
+    EXPECT_NO_THROW(NameChangeUDPListener(ip_address, port, FMT_JSON,
+                                          ncr_handler));
+}
+
+/// @brief Tests NameChangeUDPListener starting and stopping listening .
+/// This test verifies that the listener will:
+/// 1. Enter listening state
+/// 2. If in the listening state, does not allow calls to start listening
+/// 3. Exist the listening state
+/// 4. Return to the listening state after stopping
+TEST(NameChangeUDPListenerBasicTest, basicListenTests) {
+    // Verify the default constructor works.
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = LISTENER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleListenHandler ncr_handler;
+
+    NameChangeListenerPtr listener;
+    ASSERT_NO_THROW(listener.reset(
+        new NameChangeUDPListener(ip_address, port, FMT_JSON, ncr_handler)));
+
+    // Verify that we can start listening.
+    EXPECT_NO_THROW(listener->startListening(io_service));
+    EXPECT_TRUE(listener->amListening());
+
+    // Verify that attempting to listen when we already are is an error.
+    EXPECT_THROW(listener->startListening(io_service), NcrListenerError);
+
+    // Verify that we can stop listening.
+    EXPECT_NO_THROW(listener->stopListening());
+    EXPECT_FALSE(listener->amListening());
+
+    // Verify that attempting to stop listening when we are not is ok.
+    EXPECT_NO_THROW(listener->stopListening());
+
+    // Verify that we can re-enter listening.
+    EXPECT_NO_THROW(listener->startListening(io_service));
+    EXPECT_TRUE(listener->amListening());
+}
+
+/// @brief Compares two NameChangeRequests for equality.
+bool checkSendVsReceived(NameChangeRequestPtr sent_ncr,
+                         NameChangeRequestPtr received_ncr) {
+    return ((sent_ncr && received_ncr) &&
+        (*sent_ncr == *received_ncr));
+}
+
+/// @brief Text fixture for testing NameChangeUDPListener
+class NameChangeUDPListenerTest : public virtual ::testing::Test,
+                                  NameChangeListener::RequestReceiveHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeListener::Result result_;
+    NameChangeRequestPtr sent_ncr_;
+    NameChangeRequestPtr received_ncr_;
+    NameChangeListenerPtr listener_;
+    isc::asiolink::IntervalTimer test_timer_;
+
+    /// @brief Constructor
+    //
+    // Instantiates the listener member and the test timer. The timer is used
+    // to ensure a test doesn't go awry and hang forever.
+    NameChangeUDPListenerTest()
+        : io_service_(), result_(NameChangeListener::SUCCESS),
+          test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        listener_.reset(new NameChangeUDPListener(addr, LISTENER_PORT,
+                                              FMT_JSON, *this, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&NameChangeUDPListenerTest::
+                                      testTimeoutHandler, this),
+                          TEST_TIMEOUT);
+    }
+
+    virtual ~NameChangeUDPListenerTest(){
+    }
+    
+
+    /// @brief Converts JSON string into an NCR and sends it to the listener.
+    ///
+    void sendNcr(const std::string& msg) {
+        // Build an NCR  from json string. This verifies that the
+        // test string is valid.
+        ASSERT_NO_THROW(sent_ncr_ = NameChangeRequest::fromJSON(msg));
+
+        // Now use the NCR to write JSON to an output buffer.
+        isc::util::OutputBuffer ncr_buffer(1024);
+        ASSERT_NO_THROW(sent_ncr_->toFormat(FMT_JSON, ncr_buffer));
+
+        // Create a UDP socket through which our "sender" will send the NCR.
+        asio::ip::udp::socket
+            udp_socket(io_service_.get_io_service(), asio::ip::udp::v4());
+
+        // Create an endpoint pointed at the listener.
+        asio::ip::udp::endpoint
+            listener_endpoint(asio::ip::address::from_string(TEST_ADDRESS),
+                              LISTENER_PORT);
+
+        // A response message is now ready to send. Send it!
+        // Note this uses a synchronous send so it ships immediately.
+        // If listener isn't in listening mode, it will get missed.
+        udp_socket.send_to(asio::buffer(ncr_buffer.getData(),
+                                     ncr_buffer.getLength()),
+                            listener_endpoint);
+    }
+
+    /// @brief RequestReceiveHandler operator implementation for receiving NCRs.
+    ///
+    /// The fixture acts as the "application" layer.  It derives from
+    /// RequestReceiveHandler and as such implements operator() in order to
+    /// receive NCRs.
+    virtual void operator ()(const NameChangeListener::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR we received
+        result_ = result;
+        received_ncr_ = ncr;
+    }
+    // @brief Handler invoked when test timeout is hit.
+    //
+    // This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief  Tests NameChangeUDPListener ability to receive NCRs.
+/// This test verifies that a listener can enter listening mode and
+/// receive NCRs in wire format on its UDP socket; reconstruct the
+/// NCRs and delivery them to the "application" layer.
+TEST_F(NameChangeUDPListenerTest, basicReceivetest) {
+    // Verify we can enter listening mode.
+    ASSERT_FALSE(listener_->amListening());
+    ASSERT_NO_THROW(listener_->startListening(io_service_));
+    ASSERT_TRUE(listener_->amListening());
+
+    // Iterate over a series of requests, sending and receiving one
+    /// at time.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+    for (int i = 0; i < num_msgs; i++) {
+        // We are not verifying ability to send, so if we can't test is over.
+        ASSERT_NO_THROW(sendNcr(valid_msgs[i]));
+
+        // Execute no more then one event, which should be receive complete.
+        EXPECT_NO_THROW(io_service_.run_one());
+
+        // Verify the "application" status value for a successful complete.
+        EXPECT_EQ(NameChangeListener::SUCCESS, result_);
+
+        // Verify the received request matches the sent request.
+        EXPECT_TRUE(checkSendVsReceived(sent_ncr_, received_ncr_));
+    }
+
+    // Verify we can gracefully stop listening.
+    EXPECT_NO_THROW(listener_->stopListening());
+    EXPECT_FALSE(listener_->amListening());
+}
+
+/// @brief A NOP derivation for constructor test purposes.
+class SimpleSendHandler : public NameChangeSender::RequestSendHandler {
+public:
+    virtual void operator ()(const NameChangeSender::Result,
+                             NameChangeRequestPtr&) {
+    }
+};
+
+/// @brief Tests the NameChangeUDPSender constructors.
+/// This test verifies that:
+/// 1. Constructing with a max queue size of 0 is not allowed
+/// 2. Given valid parameters, the sender constructor works
+/// 3. Default construction provides default max queue size
+/// 4. Construction with a custom max queue size works
+TEST(NameChangeUDPSenderBasicTest, constructionTests) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Verify that constructing with an queue size of zero is not allowed.
+    EXPECT_THROW(NameChangeUDPSender(ip_address, port,
+        ip_address, port, FMT_JSON, ncr_handler, 0), NcrSenderError);
+
+    NameChangeSenderPtr sender;
+    // Verify that valid constructor works.
+    EXPECT_NO_THROW(sender.reset(
+                    new NameChangeUDPSender(ip_address, port, ip_address, port,
+                                            FMT_JSON, ncr_handler)));
+
+    // Verify that send queue default max is correct.
+    size_t expected = NameChangeSender::MAX_QUEUE_DEFAULT;
+    EXPECT_EQ(expected, sender->getQueueMaxSize());
+
+    // Verify that constructor with a valid custom queue size works.
+    EXPECT_NO_THROW(sender.reset(
+                    new NameChangeUDPSender(ip_address, port, ip_address, port,
+                                            FMT_JSON, ncr_handler, 100)));
+
+    EXPECT_EQ(100, sender->getQueueMaxSize());
+}
+
+/// @brief Tests NameChangeUDPSender basic send functionality
+/// This test verifies that:
+TEST(NameChangeUDPSenderBasicTest, basicSendTests) {
+    isc::asiolink::IOAddress ip_address(TEST_ADDRESS);
+    uint32_t port = SENDER_PORT;
+    isc::asiolink::IOService io_service;
+    SimpleSendHandler ncr_handler;
+
+    // Tests are based on a list of messages, get the count now.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Create the sender, setting the queue max equal to the number of
+    // messages we will have in the list.
+    NameChangeUDPSender sender(ip_address, port, ip_address, port,
+                               FMT_JSON, ncr_handler, num_msgs);
+
+    // Verify that we can start sending.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Verify that attempting to send when we already are is an error.
+    EXPECT_THROW(sender.startSending(io_service), NcrSenderError);
+
+    // Verify that we can stop sending.
+    EXPECT_NO_THROW(sender.stopSending());
+    EXPECT_FALSE(sender.amSending());
+
+    // Verify that attempting to stop sending when we are not is ok.
+    EXPECT_NO_THROW(sender.stopSending());
+
+    // Verify that we can re-enter sending after stopping.
+    EXPECT_NO_THROW(sender.startSending(io_service));
+    EXPECT_TRUE(sender.amSending());
+
+    // Iterate over a series of messages, sending each one. Since we
+    // do not invoke IOService::run, then the messages should accumulate
+    // in the queue.
+    NameChangeRequestPtr ncr;
+    for (int i = 0; i < num_msgs; i++) {
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        EXPECT_NO_THROW(sender.sendRequest(ncr));
+        // Verify that the queue count increments in step with each send.
+        EXPECT_EQ(i+1, sender.getQueueSize());
+    }
+
+    // Verify that attempting to send an additional message results in a
+    // queue full exception.
+    EXPECT_THROW(sender.sendRequest(ncr), NcrSenderQueueFull);
+
+    // Loop for the number of valid messages and invoke IOService::run_one.
+    // This should send exactly one message and the queue count should
+    // decrement accordingly.
+    for (int i = num_msgs; i > 0; i--) {
+        io_service.run_one();
+        // Verify that the queue count decrements in step with each run.
+        EXPECT_EQ(i-1, sender.getQueueSize());
+    }
+
+    // Verify that the queue is empty.
+    EXPECT_EQ(0, sender.getQueueSize());
+
+    // Verify that we can add back to the queue
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that we can remove the current entry at the front of the queue.
+    EXPECT_NO_THROW(sender.skipNext());
+    EXPECT_EQ(0, sender.getQueueSize());
+
+    // Verify that flushing the queue is not allowed in sending state.
+    EXPECT_THROW(sender.clearSendQueue(), NcrSenderError);
+
+    // Put a message on the queue.
+    EXPECT_NO_THROW(sender.sendRequest(ncr));
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that we can gracefully stop sending.
+    EXPECT_NO_THROW(sender.stopSending());
+    EXPECT_FALSE(sender.amSending());
+
+    // Verify that the queue is preserved after leaving sending state.
+    EXPECT_EQ(1, sender.getQueueSize());
+
+    // Verify that flushing the queue works when not sending.
+    EXPECT_NO_THROW(sender.clearSendQueue());
+    EXPECT_EQ(0, sender.getQueueSize());
+}
+
+/// @brief Text fixture that allows testing a listener and sender together
+/// It derives from both the receive and send handler classes and contains
+/// and instance of UDP listener and UDP sender.
+class NameChangeUDPTest : public virtual ::testing::Test,
+                          NameChangeListener::RequestReceiveHandler,
+                          NameChangeSender::RequestSendHandler {
+public:
+    isc::asiolink::IOService io_service_;
+    NameChangeListener::Result recv_result_;
+    NameChangeSender::Result send_result_;
+    NameChangeListenerPtr listener_;
+    NameChangeSenderPtr   sender_;
+    isc::asiolink::IntervalTimer test_timer_;
+
+    std::vector<NameChangeRequestPtr> sent_ncrs_;
+    std::vector<NameChangeRequestPtr> received_ncrs_;
+
+    NameChangeUDPTest()
+        : io_service_(), recv_result_(NameChangeListener::SUCCESS),
+          send_result_(NameChangeSender::SUCCESS), test_timer_(io_service_) {
+        isc::asiolink::IOAddress addr(TEST_ADDRESS);
+        // Create our listener instance. Note that reuse_address is true.
+        listener_.reset(
+            new NameChangeUDPListener(addr, LISTENER_PORT, FMT_JSON,
+                                      *this, true));
+
+        // Create our sender instance. Note that reuse_address is true.
+        sender_.reset(
+            new NameChangeUDPSender(addr, SENDER_PORT, addr, LISTENER_PORT,
+                                    FMT_JSON, *this, 100, true));
+
+        // Set the test timeout to break any running tasks if they hang.
+        test_timer_.setup(boost::bind(&NameChangeUDPTest::testTimeoutHandler,
+                                      this),
+                          TEST_TIMEOUT);
+    }
+
+    void reset_results() {
+        sent_ncrs_.clear();
+        received_ncrs_.clear();
+    }
+
+    /// @brief Implements the receive completion handler.
+    virtual void operator ()(const NameChangeListener::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR received.
+        recv_result_ = result;
+        received_ncrs_.push_back(ncr);
+    }
+
+    /// @brief Implements the send completion handler.
+    virtual void operator ()(const NameChangeSender::Result result,
+                             NameChangeRequestPtr& ncr) {
+        // save the result and the NCR sent.
+        send_result_ = result;
+        sent_ncrs_.push_back(ncr);
+    }
+
+    // @brief Handler invoked when test timeout is hit.
+    //
+    // This callback stops all running (hanging) tasks on IO service.
+    void testTimeoutHandler() {
+        io_service_.stop();
+        FAIL() << "Test timeout hit.";
+    }
+};
+
+/// @brief Uses a sender and listener to test UDP-based NCR delivery
+/// Conducts a "round-trip" test using a sender to transmit a set of valid
+/// NCRs to a listener.  The test verifies that what was sent matches what
+/// was received both in quantity and in content.
+TEST_F (NameChangeUDPTest, roundTripTest) {
+    // Place the listener into listening state.
+    ASSERT_NO_THROW(listener_->startListening(io_service_));
+    EXPECT_TRUE(listener_->amListening());
+
+    // Get the number of messages in the list of test messages.
+    int num_msgs = sizeof(valid_msgs)/sizeof(char*);
+
+    // Place the sender into sending state.
+    ASSERT_NO_THROW(sender_->startSending(io_service_));
+    EXPECT_TRUE(sender_->amSending());
+
+    for (int i = 0; i < num_msgs; i++) {
+        NameChangeRequestPtr ncr;
+        ASSERT_NO_THROW(ncr = NameChangeRequest::fromJSON(valid_msgs[i]));
+        sender_->sendRequest(ncr);
+        EXPECT_EQ(i+1, sender_->getQueueSize());
+    }
+
+    // Execute callbacks until we have sent and received all of messages.
+    while (sender_->getQueueSize() > 0 || (received_ncrs_.size() < num_msgs)) {
+        EXPECT_NO_THROW(io_service_.run_one());
+    }
+
+    // Send queue should be empty.
+    EXPECT_EQ(0, sender_->getQueueSize());
+
+    // We should have the same number of sends and receives as we do messages.
+    ASSERT_EQ(num_msgs, sent_ncrs_.size());
+    ASSERT_EQ(num_msgs, received_ncrs_.size());
+
+    // Verify that what we sent matches what we received.
+    for (int i = 0; i < num_msgs; i++) {
+        EXPECT_TRUE (checkSendVsReceived(sent_ncrs_[i], received_ncrs_[i]));
+    }
+
+    // Verify that we can gracefully stop listening.
+    EXPECT_NO_THROW(listener_->stopListening());
+    EXPECT_FALSE(listener_->amListening());
+
+    // Verify that we can gracefully stop sending.
+    EXPECT_NO_THROW(sender_->stopSending());
+    EXPECT_FALSE(sender_->amSending());
+}
+
+} // end of anonymous namespace



More information about the bind10-changes mailing list