Browse Source

[trac554] First stage of adding protocol-dependent upstream fetch

Admin tasks:
* Split out io_error.h from asiolink.h
* Made test files follow naming convention of main files
* More discriminatory includes in some files
* Updates tests/Makefile.am

Coding tasks:
* Add additional methods to io_socket.* and put in dummies in
  {tcp,udp}_socket.h
* Incorporated basic IO Fetch code from Scott
Stephen Morris 14 years ago
parent
commit
e01aeb058b

+ 1 - 0
src/lib/asiolink/Makefile.am

@@ -21,6 +21,7 @@ libasiolink_la_SOURCES += dns_answer.h
 libasiolink_la_SOURCES += simple_callback.h
 libasiolink_la_SOURCES += interval_timer.h interval_timer.cc
 libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
+libasiolink_la_SOURCES += io_error.h
 libasiolink_la_SOURCES += io_socket.cc io_socket.h
 libasiolink_la_SOURCES += io_message.h
 libasiolink_la_SOURCES += io_address.cc io_address.h

+ 1 - 14
src/lib/asiolink/asiolink.h

@@ -32,6 +32,7 @@
 #include <asiolink/io_endpoint.h>
 #include <asiolink/io_message.h>
 #include <asiolink/io_socket.h>
+#include <asiolink/io_error.h>
 
 /// \namespace asiolink
 /// \brief A wrapper interface for the ASIO library.
@@ -83,20 +84,6 @@
 /// the placeholder of callback handlers:
 /// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
 
-namespace asiolink {
-
-
-/// \brief An exception that is thrown if an error occurs within the IO
-/// module.  This is mainly intended to be a wrapper exception class for
-/// ASIO specific exceptions.
-class IOError : public isc::Exception {
-public:
-    IOError(const char* file, size_t line, const char* what) :
-        isc::Exception(file, line, what) {}
-};
-
-
-}      // asiolink
 #endif // __ASIOLINK_H
 
 // Local Variables: 

+ 87 - 0
src/lib/asiolink/io_completion_cb.h

@@ -0,0 +1,87 @@
+// Copyright (C) 2011  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 __IO_COMPLETION_CB_H
+#define __IO_COMPLETION_CB_H
+
+#include <asio/error_code.hpp>
+#include <coroutine.h>
+
+
+/// \brief Asynchronous I/O Completion Callback
+///
+/// The stackless coroutine code requires that there be an "entry function"
+/// containing the coroutine macros.  When the coroutine yields, its state is
+/// stored and when the "entry function" is called again, it jumps to the
+/// line when processing left off last time.  In BIND-10, that "entry function"
+/// is the Boost asynchronous I/O completion callback - in essence operator().
+///
+/// This class solves the problem of circularity in class definitions.  In
+/// BIND10, classes such as IOFetch contain the coroutine code.  These include
+/// objects of classes such as IOSocket, whose signature has to include the
+/// callback object - IOFetch.  By abstracting the I/O completion callback into
+/// this class, that circularity is broken.
+///
+/// One more point: the asynchronous I/O functions take the callback object by
+/// reference.  But if a derived class object is passed as a reference to its
+/// base class, "class slicing" takes place - the derived part of the class is
+/// lost and only the base class functionality remains.  By storing a pointer
+/// to the true object and having the base class method call the derived class
+/// method through that, the behaviour of class inheritance is restored. In
+/// other words:
+/// \code
+/// class derived: public class base {
+/// :
+/// };
+/// derived drv;
+///
+/// // Call with pointer to base class
+/// void f(base* b, asio::error_code& ec, size_t length) {
+///    b->operator()(ec, length);
+/// }
+///
+/// // Call with reference to base class
+/// void g(base& b, asio::error_code& ec, size_t length) {
+///    b.operator()(ec, length);
+/// }
+///
+/// void function xyz(derived *d, asio::error_code& ec, size_t length) {
+///    f(d, ec, length);  // Calls derived::operator()
+///    g(*d, ec, length); // Also calls derived::operator()
+/// }
+/// \endcode
+
+class IOCompletionCallback : public coroutine {
+public:
+
+    /// \brief Constructor
+    IOCompletionCallback() : self_(this)
+    {}
+
+    /// \brief Virtual Destructor
+    virtual ~IOCompletionCallback()
+    {}
+
+    /// \brief Callback Method
+    virtual void operator()(asio::error_code ec = asio::error_code(),
+        size_t length = 0) {
+        (*self_)(ec, length);
+    }
+
+private:
+    IOCompletionCallback*   self_;      ///< Pointer to real object
+};
+
+
+#endif // __IO_COMPLETION_CB_H

+ 35 - 0
src/lib/asiolink/io_error.h

@@ -0,0 +1,35 @@
+// Copyright (C) 2011  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 __IO_ERROR_H
+#define __IO_ERROR_H
+
+#include <exceptions/exceptions.h>
+
+namespace asiolink {
+
+/// \brief An exception that is thrown if an error occurs within the IO
+/// module.  This is mainly intended to be a wrapper exception class for
+/// ASIO specific exceptions.
+class IOError : public isc::Exception {
+public:
+    IOError(const char* file, size_t line, const char* what) :
+        isc::Exception(file, line, what) {}
+};
+
+
+}      // asiolink
+
+#endif // __IO_ERROR_H

+ 213 - 0
src/lib/asiolink/io_fetch.cc

@@ -0,0 +1,213 @@
+// Copyright (C) 2011  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 <config.h>
+
+#include <unistd.h>             // for some IPC/network system calls
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <asio.hpp>
+#include <asio/deadline_timer.hpp>
+#include <asio/ip/address.hpp>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <log/dummylog.h>
+#include <dns/opcode.h>
+#include <dns/rcode.h>
+
+#include <asiolink.h>
+#include <coroutine.h>
+#include <internal/udpdns.h>
+#include <internal/tcpdns.h>
+#include <internal/iofetch.h>
+
+using namespace asio;
+using asio::ip::udp;
+using asio::ip::tcp;
+using isc::log::dlog;
+
+using namespace std;
+using namespace isc::dns;
+
+namespace asiolink {
+
+// Constructor for the IOFetchData member
+
+/// \brief Constructor
+///
+/// Just fills in the data members of the IOFetchData structure
+///
+/// \param io_service I/O Service object to handle the asynchronous
+///     operations.
+/// \param question DNS question to send to the upstream server.
+/// \param address IP address of upstream server
+/// \param port Port to use for the query
+/// \param buffer Output buffer into which the response (in wire format)
+///     is written (if a response is received).
+/// \param callback Callback object containing the callback to be called
+///     when we terminate.  The caller is responsible for managing this
+///     object and deleting it if necessary.
+/// \param timeout Timeout for the fetch (in ms).  The default value of
+///     -1 indicates no timeout.
+/// \param protocol Protocol to use for the fetch.  The default is UDP
+
+IOFetch::IOFetchData::IOFetchData(IOService& io_service,
+    const isc::dns::Question& query, const IOAddress& address, uint16_t port,
+    isc::dns::OutputBufferPtr buff, Callback* cb, int wait, int protocol)
+        :
+    socket((protocol == IPPROTO_UDP) ?
+        static_cast<IOSocket*>(new UDPSocket(io_service, address)) :
+        static_cast<IOSocket*>(new TCPSocket(io_service, address))
+    ),
+    remote((protocol == IPPROTO_UDP) ?
+        static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+        static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+    ),
+    question(query),
+    buffer(buff),
+    msgbuf(new OutputBuffer(512)),         // TODO: Why this number?
+    data(new char[IOFetch::MAX_LENGTH]),
+    callback(cb),
+    stopped(false),
+    timer(io_service.get_io_service()),
+    timeout(wait)
+{
+}
+
+
+
+/// IOFetch Constructor - just initialize the private data
+IOFetch::IOFetch(IOService& io_service, const Question& question,
+    const IOAddress& address, uint16_t port, OutputBufferPtr buffer,
+    Callback *callback, int timeout, int protocol) :
+        data_(new IOFetch::IOFetchData(io_service, question, address, port,
+            buffer, callback, timeout, protocol)
+        )
+{
+}
+
+/// The function operator is implemented with the "stackless coroutine"
+/// pattern; see internal/coroutine.h for details.
+void
+IOFetch::operator()(error_code ec, size_t length) {
+    if (ec || data_->stopped) {
+        return;
+    }
+
+    CORO_REENTER (this) {
+        /// Generate the upstream query and render it to wire format
+        /// This is done in a different scope to allow inline variable
+        /// declarations.
+        {
+            Message msg(Message::RENDER);
+            
+            // TODO: replace with boost::random or some other suitable PRNG
+            msg.setQid(0);
+            msg.setOpcode(Opcode::QUERY());
+            msg.setRcode(Rcode::NOERROR());
+            msg.setHeaderFlag(Message::HEADERFLAG_RD);
+            msg.addQuestion(data_->question);
+            MessageRenderer renderer(*data_->msgbuf);
+            msg.toWire(renderer);
+            dlog("Sending " + msg.toText() + " to " +
+                data_->remote->getAddress().toText());
+        }
+
+
+        // If we timeout, we stop, which will shutdown everything and
+        // cancel all other attempts to run inside the coroutine
+        if (data_->timeout != -1) {
+            data_->timer.expires_from_now(boost::posix_time::milliseconds(
+                data_->timeout));
+            data_->timer.async_wait(boost::bind(&IOFetch::stop, *this,
+                TIME_OUT));
+        }
+
+        // Open a connection to the target system.  For speed, if the operation
+        // was a no-op (i.e. UDP operation) we bypass the yield.
+        bool do_yield = data_->socket->open(data->remote.get(), *this);
+        if (do_yield) {
+            CORO_YIELD;
+        }
+
+        // Begin an asynchronous send, and then yield.  When the
+        // send completes, we will resume immediately after this point.
+        CORO_YIELD data_->socket->async_send(data_->msgbuf->getData(),
+            data_->msgbuf->getLength(), data_->remote.get(), *this);
+
+        /// Allocate space for the response.  (XXX: This should be
+        /// optimized by maintaining a free list of pre-allocated blocks)
+        data_->data.reset(new char[MAX_LENGTH]);
+
+        /// Begin an asynchronous receive, and yield.  When the receive
+        /// completes, we will resume immediately after this point.
+        CORO_YIELD data_->socket->async_receive(data_->data.get(),
+            static_cast<size_t>(MAX_LENGTH), data_->remote.get(), *this);
+
+        // The message is not rendered yet, so we can't print it easilly
+        dlog("Received response from " + data_->remote->getAddress().toText());
+
+        /// Copy the answer into the response buffer.  (TODO: If the
+        /// OutputBuffer object were made to meet the requirements of
+        /// a MutableBufferSequence, then it could be written to directly
+        /// by async_receive_from() and this additional copy step would
+        /// be unnecessary.)
+        data_->buffer->writeData(data_->data.get(), length);
+
+        /// We are done
+        stop(SUCCESS);
+    }
+}
+
+// Function that stops the coroutine sequence.  It is called either when the
+// query finishes or when the timer times out.  Either way, it sets the
+// "stopped_" flag and cancels anything that is in progress.
+//
+// As the function may be entered multiple times as things wind down, the
+// stopped_ flag checks if stop() has already been called.  If it has,
+// subsequent calls are no-ops.
+void
+IOFetch::stop(Result result) {
+    if (!data_->stopped) {
+        switch (result) {
+            case TIME_OUT:
+                dlog("Query timed out");
+                break;
+
+            case STOPPED:
+                dlog("Query stopped");
+                break;
+
+            default:
+                ;
+        }
+        data_->stopped = true;
+        data_->socket->cancel();    // Cancel outstanding I/O
+        data_->socket->close();     // ... and close the socket
+        data_->timer.cancel();      // Cancel timeout timer
+
+        if (data_->callback) {
+            (*(data_->callback))(result);   // Call callback
+        }
+    }
+}
+
+} // namespace asiolink

+ 194 - 0
src/lib/asiolink/io_fetch.h

@@ -0,0 +1,194 @@
+// Copyright (C) 2010  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 __IOFETCH_H
+#define __IOFETCH_H 1
+
+#include <netinet/in.h>
+
+#include <config.h>
+
+#include <asio.hpp>
+#include <boost/shared_array.hpp>
+#include <boost/shared_ptr.hpp>
+#include <asio/deadline_timer.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+
+#include <asiolink/asiolink.h>
+#include <asiolink/ioaddress.h>
+#include <asiolink/iocompletioncb.h>
+#include <asiolink/iocompletioncb.h>
+
+
+#include <asiolink/iosocket.h>
+#include <asiolink/ioendpoint.h>
+#include <coroutine.h>
+
+
+
+namespace asiolink {
+
+/// \brief Upstream Fetch Processing
+///
+/// IOFetch is the class used to send upstream fetches and to handle responses.
+/// It is a base class containing most of the logic, although the ASIO will
+/// actually instantiate one of the derived classes TCPFetch or UDPFetch.
+/// (These differ in the type of socket and endpoint.)
+class IOFetch : public IOCompletionCallback  {
+public:
+
+    /// \brief Result of Upstream Fetch
+    ///
+    /// Note that this applies to the status of I/Os in the fetch - a fetch
+    /// that resulted in a packet being received from the server is a SUCCESS,
+    /// even if the contents of the packet indicate that some error occurred.
+    enum Result {
+        SUCCESS = 0,        ///< Success, fetch completed
+        TIME_OUT,           ///< Failure, fetch timed out
+        STOPPED             ///< Control code, fetch has been stopped
+    };
+    // The next enum is a "trick" to allow constants to be defined in a class
+    // declaration.
+
+    /// \brief Integer Constants
+    enum {
+        MAX_LENGTH = 4096   ///< Maximum size of receive buffer
+    };
+    /// \brief I/O Fetch Callback
+    ///
+    /// Callback object for when the fetch itself has completed.  Note that this
+    /// is different to the IOCompletionCallback; that is used to signal the
+    /// completion of an asynchronous I/O call.  The IOFetch::Callback is called
+    /// when an upstream fetch - which may have involved several asynchronous
+    /// I/O operations - has completed.
+    ///
+    /// This is an abstract class.
+    class Callback {
+    public:
+        /// \brief Default Constructor
+        Callback()
+        {}
+
+        /// \brief Virtual Destructor
+        virtual ~Callback()
+        {}
+
+        /// \brief Callback method called when the fetch completes
+        ///
+        /// \brief result Result of the fetch
+        virtual void operator()(Result result) = 0;
+    };
+
+    /// \brief IOFetch Data
+    ///
+    /// The data for IOFetch is held in a separate struct pointed to by a
+    /// shared_ptr object.  This is because the IOFetch object will be copied
+    /// often (it is used as a coroutine and passed as callback to many async_*()
+    /// functions) and we want keep the same data).  Organising the data this
+    /// way keeps copying to a minimum.
+    struct IOFetchData {
+
+        // The next two members are shared pointers to a base class because what
+        // is actually instantiated depends on whether the fetch is over UDP or
+        // TCP, which is not known until construction of the IOFetch.  Use of
+        // a shared pointer here is merely to ensure deletion when the data
+        // object is deleted.
+        boost::shared_ptr<IOSocket> socket;     ///< Socket to use for I/O
+        boost::shared_ptr<IOEndpoint> remote;   ///< Where the fetch was sent
+
+        isc::dns::Question          question;   ///< Question to be asked
+        isc::dns::OutputBufferPtr   buffer;     ///< Received data held here
+        isc::dns::OutputBufferPtr   msgbuf;     ///< ... and here
+        boost::shared_array<char>   data;       ///< Temporary array for the data
+        Callback*                   callback;   ///< Called on I/O Completion
+        bool                        stopped;    ///< Have we stopped running?
+        asio::deadline_timer        timer;      ///< Timer to measure timeouts
+        int                         timeout;    ///< Timeout in ms
+
+        /// \brief Constructor
+        ///
+        /// Just fills in the data members of the IOFetchData structure
+        ///
+        /// \param io_service I/O Service object to handle the asynchronous
+        ///     operations.
+        /// \param query DNS question to send to the upstream server.
+        /// \param address IP address of upstream server
+        /// \param port Port to use for the query
+        /// \param buff Output buffer into which the response (in wire format)
+        ///     is written (if a response is received).
+        /// \param cb Callback object containing the callback to be called
+        ///     when we terminate.  The caller is responsible for managing this
+        ///     object and deleting it if necessary.
+        /// \param wait Timeout for the fetch (in ms).  The default value of
+        ///     -1 indicates no timeout.
+        /// \param protocol Protocol to use for the fetch.  The default is UDP
+
+        IOFetchData(IOService& io_service, const isc::dns::Question& query,
+            const IOAddress& address, uint16_t port,
+            isc::dns::OutputBufferPtr buff, Callback* cb, int wait = -1,
+            int protocol = IPPROTO_UDP);
+    };
+
+    /// \brief Constructor.
+    ///
+    /// Creates the object that will handle the upstream fetch.
+    ///
+    /// \param io_service I/O Service object to handle the asynchronous
+    ///     operations.
+    /// \param question DNS question to send to the upstream server.
+    /// \param address IP address of upstream server
+    /// \param port Port to use for the query
+    /// \param buffer Output buffer into which the response (in wire format)
+    ///     is written (if a response is received).
+    /// \param callback Callback object containing the callback to be called
+    ///     when we terminate.  The caller is responsible for managing this
+    ///     object and deleting it if necessary.
+    /// \param timeout Timeout for the fetch (in ms).  The default value of
+    ///     -1 indicates no timeout.
+    /// \param protocol Protocol to use for the fetch.  The default is UDP
+    IOFetch(IOService& io_service, const isc::dns::Question& question,
+        const IOAddress& address, uint16_t port,
+        isc::dns::OutputBufferPtr buffer, Callback* callback,
+        int timeout = -1, int protocol = IPPROTO_UDP);
+
+
+    /// \brief Coroutine entry point
+    ///
+    /// The operator() method is the method in which the coroutine code enters
+    /// this object when an operation has been completed.
+    ///
+    /// \param ec Error code, the result of the last asynchronous I/O operation.
+    /// \param length Amount of data received on the last asynchronous read
+    void operator()(asio::error_code ec = asio::error_code(),
+        size_t length = 0);
+
+
+    /// \brief Terminate query
+    ///
+    /// This method can be called at any point.  It terminates the current
+    /// query with the specified reason.
+    ///
+    /// \param reason Reason for terminating the query
+    void stop(Result reason = STOPPED);
+
+private:
+    boost::shared_ptr<IOFetchData> data_;   ///< Private data
+};
+
+}
+
+
+#endif // __IOFETCH_H

+ 1 - 0
src/lib/asiolink/io_message.h

@@ -46,6 +46,7 @@ class IOMessage {
     ///
     /// \name Constructors and Destructor
     ///
+
     /// Note: The copy constructor and the assignment operator are
     /// intentionally defined as private, making this class non-copyable.
     //@{

+ 57 - 3
src/lib/asiolink/io_socket.cc

@@ -14,9 +14,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <asio.hpp>
+
 #include "io_socket.h"
 
-#include <asio.hpp>
 
 using namespace asio;
 
@@ -43,11 +44,64 @@ public:
 
     /// \brief A dummy derived method of \c IOSocket::getNative().
     ///
-    /// This version of method always returns -1 as the object is not
-    /// associated with a real (native) socket.
+    /// \return Always returns -1 as the object is not associated with a real
+    /// (native) socket.
     virtual int getNative() const { return (-1); }
 
+    /// \brief A dummy derived method of \c IOSocket::getProtocol().
+    ///
+    /// \return Protocol socket was created with
     virtual int getProtocol() const { return (protocol_); }
+
+
+    /// \brief Open Socket
+    ///
+    /// A call that is a no-op on UDP sockets, this opens a connection to the
+    /// system identified by the given endpoint.
+    ///
+    /// \param endpoint Unused
+    /// \param callback Unused.
+    ///false indicating that the operation completed synchronously.
+    virtual bool open(const IOEndpoint*, IOCompletionCallback&) {
+        return (false);
+    }
+
+    /// \brief Send Asynchronously
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    ///
+    /// \param data Unused
+    /// \param length Unused
+    /// \param endpoint Unused
+    /// \param callback Unused
+    virtual void async_send(const void*, size_t, const IOEndpoint*,
+        IOCompletionCallback&) {
+    }
+
+    /// \brief Receive Asynchronously
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    ///
+    /// \param data Unused
+    /// \param length Unused
+    /// \param endpoint Unused
+    /// \param callback Unused
+    virtual void async_receive(void* data, size_t, IOEndpoint*,
+        IOCompletionCallback&) {
+    }
+
+    /// \brief Cancel I/O On Socket
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    virtual void cancel() {
+    }
+
+    /// \brief Close socket
+    ///
+    /// Must be supplied as it is abstract in the base class.
+    virtual void close() {
+    }
+
 private:
     const int protocol_;
 };

+ 61 - 0
src/lib/asiolink/io_socket.h

@@ -24,9 +24,16 @@
 #include <string>
 
 #include <exceptions/exceptions.h>
+#include <coroutine.h>
+
+#include <asiolink/io_completion_cb.h>
 
 namespace asiolink {
 
+/// Forward declaration of an IOEndpoint
+class IOEndpoint;
+
+
 /// \brief The \c IOSocket class is an abstract base class to represent
 /// various types of network sockets.
 ///
@@ -95,6 +102,60 @@ public:
     /// \return IPPROTO_TCP for TCP sockets
     virtual int getProtocol() const = 0;
 
+    /// \brief Open Socket
+    ///
+    /// A call that is a no-op on UDP sockets, this opens a connection to the
+    /// system identified by the given endpoint.
+    ///
+    /// \param endpoint Pointer to the endpoint object.  This is ignored for
+    /// a UDP socket (the target is specified in the send call), but should
+    /// be of type TCPEndpoint for a TCP connection.
+    /// \param callback I/O Completion callback, called when the connect has
+    /// completed.  In the stackless coroutines model, this will be the
+    /// method containing the call to this function, allowing the operation to
+    /// resume after the socket open has completed.
+    ///
+    /// \return true if an asynchronous operation was started and the caller
+    /// should yield and wait for completion, false if not. (i.e. The UDP
+    /// derived class will return false, the TCP class will return true).  This
+    /// optimisation avoids the overhead required to post a callback to the
+    /// I/O Service queue where nothing is done.
+    virtual bool open(const IOEndpoint* endpoint,
+        IOCompletionCallback& callback) = 0;
+
+    /// \brief Send Asynchronously
+    ///
+    /// This corresponds to async_send_to() for UDP sockets and async_send()
+    /// for TCP.  In both cases an endpoint argument is supplied indicating the
+    /// target of the send - this is ignored for TCP.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send
+    /// \param callback Callback object.
+    virtual void async_send(const void* data, size_t length,
+        const IOEndpoint* endpoint, IOCompletionCallback& callback) = 0;
+
+    /// \brief Receive Asynchronously
+    ///
+    /// This correstponds to async_receive_from() for UDP sockets and
+    /// async_receive() for TCP.  In both cases, an endpoint argument is
+    /// supplied to receive the source of the communication.  For TCP it will
+    /// be filled in with details of the connection.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void async_receive(void* data, size_t length, IOEndpoint* endpoint,
+        IOCompletionCallback& callback) = 0;
+
+    /// \brief Cancel I/O On Socket
+    virtual void cancel() = 0;
+
+    /// \brief Close socket
+    virtual void close() = 0;
+
     /// \brief Return a non-usable "dummy" UDP socket for testing.
     ///
     /// This is a class method that returns a "mock" of UDP socket.

+ 0 - 273
src/lib/asiolink/iofetch.cc

@@ -1,273 +0,0 @@
-// Copyright (C) 2011  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 <config.h>
-
-#include <unistd.h>             // for some IPC/network system calls
-#include <sys/socket.h>
-#include <netinet/in.h>
-
-#include <boost/bind.hpp>
-
-#include <asio.hpp>
-#include <asio/deadline_timer.hpp>
-#include <asio/ip/address.hpp>
-
-#include <boost/shared_ptr.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-#include <log/dummylog.h>
-#include <dns/opcode.h>
-#include <dns/rcode.h>
-
-#include <asiolink.h>
-#include <internal/coroutine.h>
-#include <internal/udpdns.h>
-#include <internal/tcpdns.h>
-#include <internal/iofetch.h>
-
-using namespace asio;
-using asio::ip::udp;
-using asio::ip::tcp;
-using isc::log::dlog;
-
-using namespace std;
-using namespace isc::dns;
-
-namespace asiolink {
-
-struct TcpFetch::UdpData {
-    // UDP Socket we send query to and expect reply from there
-    udp::socket socket;
-    // Where was the query sent
-    udp::endpoint remote;
-    // What we ask the server
-    Question question;
-    // We will store the answer here
-    OutputBufferPtr buffer;
-    OutputBufferPtr msgbuf;
-    // Temporary buffer for answer
-    boost::shared_array<char> data;
-    // This will be called when the data arrive or timeouts
-    Callback* callback;
-    //Callback* callback;
-    // Did we already stop operating (data arrived, we timed out, someone
-    // called stop). This can be so when we are cleaning up/there are
-    // still pointers to us.
-    bool stopped;
-    // Timer to measure timeouts.
-    deadline_timer timer;
-    // How many milliseconds are we willing to wait for answer?
-    int timeout;
-
-    UdpData(io_service& service,
-        const udp::socket::protocol_type& protocol,
-        const Question &q,
-        OutputBufferPtr b, Callback *c) :
-          socket(service, protocol),
-          question(q),
-          buffer(b),
-          msgbuf(new OutputBuffer(512)),
-          callback(c),
-          stopped(false),
-          timer(service)
-    { }
-
-
-};
-
-struct TcpFetch::TcpData {
-    // TCP Socket
-    tcp::socket socket;
-    // tcp endpoint
-    tcp::endpoint remote;
-    // What we ask the server
-    Question question;
-    // We will store the answer here
-    OutputBufferPtr buffer;
-    OutputBufferPtr msgbuf;
-    // Temporary buffer for answer
-    boost::shared_array<char> data;
-    // This will be called when the data arrive or timeouts
-    Callback* callback;
-    // Did we already stop operating (data arrived, we timed out, someone
-    // called stop). This can be so when we are cleaning up/there are
-    // still pointers to us.
-    bool stopped;
-    // Timer to measure timeouts.
-    deadline_timer timer;
-    // How many milliseconds are we willing to wait for answer?
-    int timeout;
-
-    TcpData(io_service& service,
-        const tcp::socket::protocol_type& protocol,
-        const Question &q,
-        OutputBufferPtr b, Callback *c) :
-          socket(service, protocol),
-          question(q),
-          buffer(b),
-          msgbuf(new OutputBuffer(512)),
-          callback(c),
-          stopped(false),
-          timer(service)
-    { }
-
-
-};
-
-UdpFetch::UdpFetch(io_service& io_service, const Question& q,
-                 const IOAddress& addr, uint16_t port,
-                 OutputBufferPtr buffer, Callback *callback, int timeout) :
-    data_(new UdpData(io_service, 
-          addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), 
-          q, buffer, callback))
-{
-    data_->remote = UDPEndpoint(addr, port).getASIOEndpoint();
-    data_->timeout = timeout;
-}
-TcpFetch::TcpFetch(io_service& io_service, const Question& q,
-                 const IOAddress& addr, uint16_t port,
-                 OutputBufferPtr buffer, Callback *callback, int timeout) :
-    tcp_data_(new TcpData(io_service, 
-          addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), 
-          q, buffer, callback))
-{
-    tcp_data_->remote = TCPEndpoint(addr, port).getASIOEndpoint();
-    tcp_data_->timeout = timeout;
-}
-
-//
-/// The following functions implement the \c IOFetch class.
-///
-/// The constructor
-IOFetch::IOFetch(io_service& io_service, const Question& q, 
-                 const IOAddress& addr, uint16_t port,
-                 OutputBufferPtr buffer, Callback *callback, int timeout,
-                 int protocol)
-{
-    if (protocol == IPPROTO_TCP)
-    {
-        TcpFetch(io_service, q, addr, port, buffer, callback, timeout);
-/*
-        tcp_data_ = new TcpData(io_service, 
-          addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), 
-          q, buffer, callback);
-        tcp_data_->remote = TCPEndpoint(addr, port).getASIOEndpoint();
-        tcp_data_->timeout = timeout;
-*/
-    }
-    else
-    {
-        UdpFetch(io_service, q, addr, port, buffer, callback, timeout);
-/*
-        data_(new UdpData(io_service, 
-          addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), 
-          q, buffer, callback));
-        data_->remote = UDPEndpoint(addr, port).getASIOEndpoint();
-        data_->timeout = timeout;
-*/
-    }
-}
-
-/// The function operator is implemented with the "stackless coroutine"
-/// pattern; see internal/coroutine.h for details.
-void
-IOFetch::operator()(error_code ec, size_t length) {
-    if (ec || data_->stopped) {
-        return;
-    }
-
-    CORO_REENTER (this) {
-        /// Generate the upstream query and render it to wire format
-        /// This is done in a different scope to allow inline variable
-        /// declarations.
-        {
-            Message msg(Message::RENDER);
-            
-            // XXX: replace with boost::random or some other suitable PRNG
-            msg.setQid(0);
-            msg.setOpcode(Opcode::QUERY());
-            msg.setRcode(Rcode::NOERROR());
-            msg.setHeaderFlag(Message::HEADERFLAG_RD);
-            msg.addQuestion(data_->question);
-            MessageRenderer renderer(*data_->msgbuf);
-            msg.toWire(renderer);
-            dlog("Sending " + msg.toText() + " to " +
-                data_->remote.address().to_string());
-        }
-
-
-        // If we timeout, we stop, which will shutdown everything and
-        // cancel all other attempts to run inside the coroutine
-        if (data_->timeout != -1) {
-            data_->timer.expires_from_now(boost::posix_time::milliseconds(
-                data_->timeout));
-            data_->timer.async_wait(boost::bind(&IOFetch::stop, *this,
-                TIME_OUT));
-        }
-
-        // Begin an asynchronous send, and then yield.  When the
-        // send completes, we will resume immediately after this point.
-        CORO_YIELD data_->socket.async_send_to(buffer(data_->msgbuf->getData(),
-            data_->msgbuf->getLength()), data_->remote, *this);
-
-        /// Allocate space for the response.  (XXX: This should be
-        /// optimized by maintaining a free list of pre-allocated blocks)
-        data_->data.reset(new char[MAX_LENGTH]);
-
-        /// Begin an asynchronous receive, and yield.  When the receive
-        /// completes, we will resume immediately after this point.
-        CORO_YIELD data_->socket.async_receive_from(buffer(data_->data.get(),
-            MAX_LENGTH), data_->remote, *this);
-        // The message is not rendered yet, so we can't print it easilly
-        dlog("Received response from " + data_->remote.address().to_string());
-
-        /// Copy the answer into the response buffer.  (XXX: If the
-        /// OutputBuffer object were made to meet the requirements of
-        /// a MutableBufferSequence, then it could be written to directly
-        /// by async_recieve_from() and this additional copy step would
-        /// be unnecessary.)
-        data_->buffer->writeData(data_->data.get(), length);
-
-        /// We are done
-        stop(SUCCESS);
-    }
-}
-
-void
-IOFetch::stop(Result result) {
-    if (!data_->stopped) {
-        switch (result) {
-            case TIME_OUT:
-                dlog("Query timed out");
-                break;
-            case STOPPED:
-                dlog("Query stopped");
-                break;
-            default:;
-        }
-        data_->stopped = true;
-        data_->socket.cancel();
-        data_->socket.close();
-        data_->timer.cancel();
-        if (data_->callback) {
-            (*data_->callback)(result);
-        }
-    }
-}
-
-}

+ 59 - 0
src/lib/asiolink/tcp_socket.h

@@ -43,6 +43,65 @@ public:
     int getNative() const { return (socket_.native()); }
     int getProtocol() const { return (IPPROTO_TCP); }
 
+    /// \brief Open Socket
+    ///
+    /// A call that is a no-op on UDP sockets, this opens a connection to the
+    /// system identified by the given endpoint.
+    ///
+    /// \param endpoint Pointer to the endpoint object.  This is ignored for
+    /// a UDP socket (the target is specified in the send call), but should
+    /// be of type TCPEndpoint for a TCP connection.
+    /// \param callback I/O Completion callback, called when the connect has
+    /// completed.  In the stackless coroutines model, this will be the
+    /// method containing the call to this function, allowing the operation to
+    /// resume after the socket open has completed.
+    ///
+    /// \return true if an asynchronous operation was started and the caller
+    /// should yield and wait for completion, false if not. (i.e. The UDP
+    /// derived class will return false, the TCP class will return true).  This
+    /// optimisation avoids the overhead required to post a callback to the
+    /// I/O Service queue where nothing is done.
+    virtual bool open(const IOEndpoint*, IOCompletionCallback&) {
+        return false;
+    }
+
+    /// \brief Send Asynchronously
+    ///
+    /// This corresponds to async_send_to() for UDP sockets and async_send()
+    /// for TCP.  In both cases an endpoint argument is supplied indicating the
+    /// target of the send - this is ignored for TCP.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send
+    /// \param callback Callback object.
+    virtual void async_send(const void*, size_t,
+        const IOEndpoint*, IOCompletionCallback&) {
+    }
+
+    /// \brief Receive Asynchronously
+    ///
+    /// This correstponds to async_receive_from() for UDP sockets and
+    /// async_receive() for TCP.  In both cases, an endpoint argument is
+    /// supplied to receive the source of the communication.  For TCP it will
+    /// be filled in with details of the connection.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void async_receive(void* data, size_t, IOEndpoint*,
+        IOCompletionCallback&) {
+    }
+
+    /// \brief Cancel I/O On Socket
+    virtual void cancel() {
+    }
+
+    /// \brief Close socket
+    virtual void close() {
+    }
+
 private:
     asio::ip::tcp::socket& socket_;
 };

+ 3 - 3
src/lib/asiolink/tests/Makefile.am

@@ -18,9 +18,9 @@ TESTS += run_unittests
 run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
 run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
 run_unittests_SOURCES += udp_query_unittest.cc
-run_unittests_SOURCES += ioaddress_unittest.cc
-run_unittests_SOURCES += ioendpoint_unittest.cc
-run_unittests_SOURCES += iosocket_unittest.cc
+run_unittests_SOURCES += io_address_unittest.cc
+run_unittests_SOURCES += io_endpoint_unittest.cc
+run_unittests_SOURCES += io_socket_unittest.cc
 run_unittests_SOURCES += io_service_unittest.cc
 run_unittests_SOURCES += interval_timer_unittest.cc
 run_unittests_SOURCES += recursive_query_unittest.cc

+ 1 - 0
src/lib/asiolink/tests/interval_timer_unittest.cc

@@ -15,6 +15,7 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
+#include <asio.hpp>
 #include <asiolink/asiolink.h>
 
 #include <boost/date_time/posix_time/posix_time_types.hpp>

+ 2 - 1
src/lib/asiolink/tests/ioaddress_unittest.cc

@@ -15,7 +15,8 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
-#include <asiolink/asiolink.h>
+#include <asiolink/io_error.h>
+#include <asiolink/io_address.h>
 
 using namespace asiolink;
 

+ 2 - 1
src/lib/asiolink/tests/ioendpoint_unittest.cc

@@ -15,7 +15,8 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
-#include <asiolink/asiolink.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
 
 using namespace asiolink;
 

+ 1 - 0
src/lib/asiolink/tests/io_service_unittest.cc

@@ -15,6 +15,7 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
+#include <asio.hpp>
 #include <asiolink/asiolink.h>
 
 using namespace asiolink;

+ 2 - 1
src/lib/asiolink/tests/iosocket_unittest.cc

@@ -15,7 +15,8 @@
 #include <config.h>
 #include <gtest/gtest.h>
 
-#include <asiolink/asiolink.h>
+#include <asio.hpp>
+#include <asiolink/io_socket.h>
 
 using namespace asiolink;
 

+ 50 - 0
src/lib/asiolink/udp_socket.h

@@ -40,6 +40,56 @@ public:
     virtual int getNative() const { return (socket_.native()); }
     virtual int getProtocol() const { return (IPPROTO_UDP); }
 
+    /// \brief Open Socket
+    ///
+    /// No-op for UDP sockets
+    ///
+    /// \param endpoint Unused.
+    /// \param callback Unused.
+    ///
+    /// \return false to indicate that the "operation" completed synchronously.
+    virtual bool open(const IOEndpoint*, IOCompletionCallback&) {
+        return false;
+    }
+
+    /// \brief Send Asynchronously
+    ///
+    /// This corresponds to async_send_to() for UDP sockets and async_send()
+    /// for TCP.  In both cases an endpoint argument is supplied indicating the
+    /// target of the send - this is ignored for TCP.
+    ///
+    /// \param data Data to send
+    /// \param length Length of data to send
+    /// \param endpoint Target of the send
+    /// \param callback Callback object.
+    virtual void async_send(const void*, size_t,
+        const IOEndpoint*, IOCompletionCallback&) {
+    }
+
+    /// \brief Receive Asynchronously
+    ///
+    /// This correstponds to async_receive_from() for UDP sockets and
+    /// async_receive() for TCP.  In both cases, an endpoint argument is
+    /// supplied to receive the source of the communication.  For TCP it will
+    /// be filled in with details of the connection.
+    ///
+    /// \param data Buffer to receive incoming message
+    /// \param length Length of the data buffer
+    /// \param endpoint Source of the communication
+    /// \param callback Callback object
+    virtual void async_receive(void* data, size_t, IOEndpoint*,
+        IOCompletionCallback&) {
+    }
+
+    /// \brief Cancel I/O On Socket
+    virtual void cancel() {
+    }
+
+    /// \brief Close socket
+    virtual void close() {
+    }
+
+
 private:
     asio::ip::udp::socket& socket_;
 };