2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-29 04:57:52 +00:00

[trac554] Added IOFetch

IOFetch is a general upstream "fetch" class that should be able to
operate over TCP or UDP.  Related changes have been made in the
associated classes.  So far, only the unit tests for a UDP fetch
have been made (and passed).
This commit is contained in:
Stephen Morris 2011-02-18 14:31:20 +00:00
parent ad418dc785
commit 85b6fa72d6
23 changed files with 1440 additions and 602 deletions

370
INSTALL
View File

@ -1,9 +1,365 @@
To build "configure" file:
autoreconf
Installation Instructions
*************************
To then build from source:
./configure
make
Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005,
2006, 2007, 2008, 2009 Free Software Foundation, Inc.
Copying and distribution of this file, with or without modification,
are permitted in any medium without royalty provided the copyright
notice and this notice are preserved. This file is offered as-is,
without warranty of any kind.
Basic Installation
==================
Briefly, the shell commands `./configure; make; make install' should
configure, build, and install this package. The following
more-detailed instructions are generic; see the `README' file for
instructions specific to this package. Some packages provide this
`INSTALL' file but do not implement all of the features documented
below. The lack of an optional feature in a given package is not
necessarily a bug. More recommendations for GNU packages can be found
in *note Makefile Conventions: (standards)Makefile Conventions.
The `configure' shell script attempts to guess correct values for
various system-dependent variables used during compilation. It uses
those values to create a `Makefile' in each directory of the package.
It may also create one or more `.h' files containing system-dependent
definitions. Finally, it creates a shell script `config.status' that
you can run in the future to recreate the current configuration, and a
file `config.log' containing compiler output (useful mainly for
debugging `configure').
It can also use an optional file (typically called `config.cache'
and enabled with `--cache-file=config.cache' or simply `-C') that saves
the results of its tests to speed up reconfiguring. Caching is
disabled by default to prevent problems with accidental use of stale
cache files.
If you need to do unusual things to compile the package, please try
to figure out how `configure' could check whether to do them, and mail
diffs or instructions to the address given in the `README' so they can
be considered for the next release. If you are using the cache, and at
some point `config.cache' contains results you don't want to keep, you
may remove or edit it.
The file `configure.ac' (or `configure.in') is used to create
`configure' by a program called `autoconf'. You need `configure.ac' if
you want to change it or regenerate `configure' using a newer version
of `autoconf'.
The simplest way to compile this package is:
1. `cd' to the directory containing the package's source code and type
`./configure' to configure the package for your system.
Running `configure' might take a while. While running, it prints
some messages telling which features it is checking for.
2. Type `make' to compile the package.
3. Optionally, type `make check' to run any self-tests that come with
the package, generally using the just-built uninstalled binaries.
4. Type `make install' to install the programs and any data files and
documentation. When installing into a prefix owned by root, it is
recommended that the package be configured and built as a regular
user, and only the `make install' phase executed with root
privileges.
5. Optionally, type `make installcheck' to repeat any self-tests, but
this time using the binaries in their final installed location.
This target does not install anything. Running this target as a
regular user, particularly if the prior `make install' required
root privileges, verifies that the installation completed
correctly.
6. You can remove the program binaries and object files from the
source code directory by typing `make clean'. To also remove the
files that `configure' created (so you can compile the package for
a different kind of computer), type `make distclean'. There is
also a `make maintainer-clean' target, but that is intended mainly
for the package's developers. If you use it, you may have to get
all sorts of other programs in order to regenerate files that came
with the distribution.
7. Often, you can also type `make uninstall' to remove the installed
files again. In practice, not all packages have tested that
uninstallation works correctly, even though it is required by the
GNU Coding Standards.
8. Some packages, particularly those that use Automake, provide `make
distcheck', which can by used by developers to test that all other
targets like `make install' and `make uninstall' work correctly.
This target is generally not run by end users.
Compilers and Options
=====================
Some systems require unusual options for compilation or linking that
the `configure' script does not know about. Run `./configure --help'
for details on some of the pertinent environment variables.
You can give `configure' initial values for configuration parameters
by setting variables in the command line or in the environment. Here
is an example:
./configure CC=c99 CFLAGS=-g LIBS=-lposix
*Note Defining Variables::, for more details.
Compiling For Multiple Architectures
====================================
You can compile the package for more than one kind of computer at the
same time, by placing the object files for each architecture in their
own directory. To do this, you can use GNU `make'. `cd' to the
directory where you want the object files and executables to go and run
the `configure' script. `configure' automatically checks for the
source code in the directory that `configure' is in and in `..'. This
is known as a "VPATH" build.
With a non-GNU `make', it is safer to compile the package for one
architecture at a time in the source code directory. After you have
installed the package for one architecture, use `make distclean' before
reconfiguring for another architecture.
On MacOS X 10.5 and later systems, you can create libraries and
executables that work on multiple system types--known as "fat" or
"universal" binaries--by specifying multiple `-arch' options to the
compiler but only a single `-arch' option to the preprocessor. Like
this:
./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \
CPP="gcc -E" CXXCPP="g++ -E"
This is not guaranteed to produce working output in all cases, you
may have to build one architecture at a time and combine the results
using the `lipo' tool if you have problems.
Installation Names
==================
By default, `make install' installs the package's commands under
`/usr/local/bin', include files under `/usr/local/include', etc. You
can specify an installation prefix other than `/usr/local' by giving
`configure' the option `--prefix=PREFIX', where PREFIX must be an
absolute file name.
You can specify separate installation prefixes for
architecture-specific files and architecture-independent files. If you
pass the option `--exec-prefix=PREFIX' to `configure', the package uses
PREFIX as the prefix for installing programs and libraries.
Documentation and other data files still use the regular prefix.
In addition, if you use an unusual directory layout you can give
options like `--bindir=DIR' to specify different values for particular
kinds of files. Run `configure --help' for a list of the directories
you can set and what kinds of files go in them. In general, the
default for these options is expressed in terms of `${prefix}', so that
specifying just `--prefix' will affect all of the other directory
specifications that were not explicitly provided.
The most portable way to affect installation locations is to pass the
correct locations to `configure'; however, many packages provide one or
both of the following shortcuts of passing variable assignments to the
`make install' command line to change installation locations without
having to reconfigure or recompile.
The first method involves providing an override variable for each
affected directory. For example, `make install
prefix=/alternate/directory' will choose an alternate location for all
directory configuration variables that were expressed in terms of
`${prefix}'. Any directories that were specified during `configure',
but not in terms of `${prefix}', must each be overridden at install
time for the entire installation to be relocated. The approach of
makefile variable overrides for each directory variable is required by
the GNU Coding Standards, and ideally causes no recompilation.
However, some platforms have known limitations with the semantics of
shared libraries that end up requiring recompilation when using this
method, particularly noticeable in packages that use GNU Libtool.
The second method involves providing the `DESTDIR' variable. For
example, `make install DESTDIR=/alternate/directory' will prepend
`/alternate/directory' before all installation names. The approach of
`DESTDIR' overrides is not required by the GNU Coding Standards, and
does not work on platforms that have drive letters. On the other hand,
it does better at avoiding recompilation issues, and works well even
when some directory options were not specified in terms of `${prefix}'
at `configure' time.
Optional Features
=================
If the package supports it, you can cause programs to be installed
with an extra prefix or suffix on their names by giving `configure' the
option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
Some packages pay attention to `--enable-FEATURE' options to
`configure', where FEATURE indicates an optional part of the package.
They may also pay attention to `--with-PACKAGE' options, where PACKAGE
is something like `gnu-as' or `x' (for the X Window System). The
`README' should mention any `--enable-' and `--with-' options that the
package recognizes.
For packages that use the X Window System, `configure' can usually
find the X include and library files automatically, but if it doesn't,
you can use the `configure' options `--x-includes=DIR' and
`--x-libraries=DIR' to specify their locations.
Some packages offer the ability to configure how verbose the
execution of `make' will be. For these packages, running `./configure
--enable-silent-rules' sets the default to minimal output, which can be
overridden with `make V=1'; while running `./configure
--disable-silent-rules' sets the default to verbose, which can be
overridden with `make V=0'.
Particular systems
==================
On HP-UX, the default C compiler is not ANSI C compatible. If GNU
CC is not installed, it is recommended to use the following options in
order to use an ANSI C compiler:
./configure CC="cc -Ae -D_XOPEN_SOURCE=500"
and if that doesn't work, install pre-built binaries of GCC for HP-UX.
On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot
parse its `<wchar.h>' header file. The option `-nodtk' can be used as
a workaround. If GNU CC is not installed, it is therefore recommended
to try
./configure CC="cc"
and if that doesn't work, try
./configure CC="cc -nodtk"
On Solaris, don't put `/usr/ucb' early in your `PATH'. This
directory contains several dysfunctional programs; working variants of
these programs are available in `/usr/bin'. So, if you need `/usr/ucb'
in your `PATH', put it _after_ `/usr/bin'.
On Haiku, software installed for all users goes in `/boot/common',
not `/usr/local'. It is recommended to use the following options:
./configure --prefix=/boot/common
Specifying the System Type
==========================
There may be some features `configure' cannot figure out
automatically, but needs to determine by the type of machine the package
will run on. Usually, assuming the package is built to be run on the
_same_ architectures, `configure' can figure that out, but if it prints
a message saying it cannot guess the machine type, give it the
`--build=TYPE' option. TYPE can either be a short name for the system
type, such as `sun4', or a canonical name which has the form:
CPU-COMPANY-SYSTEM
where SYSTEM can have one of these forms:
OS
KERNEL-OS
See the file `config.sub' for the possible values of each field. If
`config.sub' isn't included in this package, then this package doesn't
need to know the machine type.
If you are _building_ compiler tools for cross-compiling, you should
use the option `--target=TYPE' to select the type of system they will
produce code for.
If you want to _use_ a cross compiler, that generates code for a
platform different from the build platform, you should specify the
"host" platform (i.e., that on which the generated programs will
eventually be run) with `--host=TYPE'.
Sharing Defaults
================
If you want to set default values for `configure' scripts to share,
you can create a site shell script called `config.site' that gives
default values for variables like `CC', `cache_file', and `prefix'.
`configure' looks for `PREFIX/share/config.site' if it exists, then
`PREFIX/etc/config.site' if it exists. Or, you can set the
`CONFIG_SITE' environment variable to the location of the site script.
A warning: not all `configure' scripts look for a site script.
Defining Variables
==================
Variables not defined in a site shell script can be set in the
environment passed to `configure'. However, some packages may run
configure again during the build, and the customized values of these
variables may be lost. In order to avoid this problem, you should set
them in the `configure' command line, using `VAR=value'. For example:
./configure CC=/usr/local2/bin/gcc
causes the specified `gcc' to be used as the C compiler (unless it is
overridden in the site shell script).
Unfortunately, this technique does not work for `CONFIG_SHELL' due to
an Autoconf bug. Until the bug is fixed you can use this workaround:
CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash
`configure' Invocation
======================
`configure' recognizes the following options to control how it
operates.
`--help'
`-h'
Print a summary of all of the options to `configure', and exit.
`--help=short'
`--help=recursive'
Print a summary of the options unique to this package's
`configure', and exit. The `short' variant lists options used
only in the top level, while the `recursive' variant lists options
also present in any nested packages.
`--version'
`-V'
Print the version of Autoconf used to generate the `configure'
script, and exit.
`--cache-file=FILE'
Enable the cache: use and save the results of the tests in FILE,
traditionally `config.cache'. FILE defaults to `/dev/null' to
disable caching.
`--config-cache'
`-C'
Alias for `--cache-file=config.cache'.
`--quiet'
`--silent'
`-q'
Do not print messages saying which checks are being made. To
suppress all normal output, redirect it to `/dev/null' (any error
messages will still be shown).
`--srcdir=DIR'
Look for the package's source code in directory DIR. Usually
`configure' can determine that directory automatically.
`--prefix=DIR'
Use DIR as the installation prefix. *note Installation Names::
for more details, including other options available for fine-tuning
the installation locations.
`--no-create'
`-n'
Run the configure checks, but stop before creating any output
files.
`configure' also accepts some other, not widely useful, options. Run
`configure --help' for more details.
For detailed installation directions, see the guide
at doc/guide/bind10-guide.html.

View File

@ -12,26 +12,28 @@ CLEANFILES = *.gcno *.gcda
# have some code fragments that would hit gcc's unused-parameter warning,
# which would make the build fail with -Werror (our default setting).
lib_LTLIBRARIES = libasiolink.la
libasiolink_la_SOURCES = asiolink.h
libasiolink_la_SOURCES += io_service.cc io_service.h
libasiolink_la_SOURCES += dns_service.cc dns_service.h
libasiolink_la_SOURCES += dns_server.h
libasiolink_la_SOURCES += dns_lookup.h
libasiolink_la_SOURCES = asiolink.h
libasiolink_la_SOURCES += dns_answer.h
libasiolink_la_SOURCES += simple_callback.h
libasiolink_la_SOURCES += dns_lookup.h
libasiolink_la_SOURCES += dns_server.h
libasiolink_la_SOURCES += dns_service.h dns_service.cc
libasiolink_la_SOURCES += interval_timer.h interval_timer.cc
libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
libasiolink_la_SOURCES += io_address.h io_address.cc
libasiolink_la_SOURCES += io_endpoint.h io_endpoint.cc
libasiolink_la_SOURCES += io_error.h
libasiolink_la_SOURCES += io_socket.cc io_socket.h
libasiolink_la_SOURCES += io_fetch.h io_fetch.cc
libasiolink_la_SOURCES += io_message.h
libasiolink_la_SOURCES += io_address.cc io_address.h
libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
libasiolink_la_SOURCES += udp_endpoint.h
libasiolink_la_SOURCES += udp_server.h udp_server.cc
libasiolink_la_SOURCES += udp_socket.h udp_socket.cc
libasiolink_la_SOURCES += udp_query.h udp_query.cc
libasiolink_la_SOURCES += tcp_endpoint.h tcp_socket.h
libasiolink_la_SOURCES += io_service.h io_service.cc
libasiolink_la_SOURCES += io_socket.h io_socket.cc
libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
libasiolink_la_SOURCES += simple_callback.h
libasiolink_la_SOURCES += tcp_endpoint.h
libasiolink_la_SOURCES += tcp_server.h tcp_server.cc
libasiolink_la_SOURCES += tcp_socket.h
libasiolink_la_SOURCES += udp_endpoint.h
libasiolink_la_SOURCES += udp_query.h udp_query.cc
libasiolink_la_SOURCES += udp_server.h udp_server.cc
libasiolink_la_SOURCES += udp_socket.h
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)

View File

@ -34,9 +34,6 @@
#include <asiolink/io_socket.h>
#include <asiolink/io_error.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
/// \namespace asiolink
/// \brief A wrapper interface for the ASIO library.
///

View File

@ -20,7 +20,10 @@
#include <asio.hpp>
#include <asiolink/asiolink.h>
#include <exceptions/exceptions.h>
#include <asiolink/io_address.h>
#include <asiolink/io_error.h>
using namespace asio;
using asio::ip::udp;

View File

@ -12,8 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef __IOADDRESS_H
#define __IOADDRESS_H 1
#ifndef __IO_ADDRESS_H
#define __IO_ADDRESS_H 1
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
@ -120,7 +120,7 @@ private:
};
} // asiolink
#endif // __IOADDRESS_H
#endif // __IO_ADDRESS_H
// Local Variables:
// mode: c++

View File

@ -0,0 +1,304 @@
// 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 __IO_ASIO_SOCKET_H
#define __IO_ASIO_SOCKET_H 1
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
// See the description of the namespace below.
#include <unistd.h> // for some network system calls
#include <functional>
#include <string>
#include <exceptions/exceptions.h>
#include <coroutine.h>
#include <asiolink/io_error.h>
#include <asiolink/io_socket.h>
#include <asiolink/io_completion_cb.h>
using namespace asio;
namespace asiolink {
/// \brief Socket not open
///
/// Thrown on an attempt to do read/write to a socket that is not open.
class SocketNotOpen : public IOError {
public:
SocketNotOpen(const char* file, size_t line, const char* what) :
IOError(file, line, what) {}
};
/// Forward declaration of an IOEndpoint
class IOEndpoint;
/// \brief I/O Socket with asynchronous operations
///
/// This class is a wrapper for the ASIO socket classes such as
/// \c ip::tcp::socket and \c ip::udp::socket.
///
/// This is the basic IOSocket with additional operations - open, send, receive
/// and close. Depending on how the asiolink code develops, it may be a
/// temporary class: its main use is to add the template parameter needed for
/// the derived classes UDPSocket and TCPSocket but without changing the
/// signature of the more basic IOSocket class.
///
/// We may revisit this decision when we generalize the wrapper and more
/// modules use it. Also, at that point we may define a separate (visible)
/// derived class for testing purposes rather than providing factory methods
/// (i.e., getDummy variants below).
///
/// TODO: Check if IOAsioSocket class is still needed
///
/// \param C Template parameter identifying type of the callback object.
template <typename C>
class IOAsioSocket : public IOSocket {
///
/// \name Constructors and Destructor
///
/// Note: The copy constructor and the assignment operator are
/// intentionally defined as private, making this class non-copyable.
//@{
private:
IOAsioSocket(const IOAsioSocket<C>& source);
IOAsioSocket& operator=(const IOAsioSocket<C>& source);
protected:
/// \brief The default constructor.
///
/// This is intentionally defined as \c protected as this base class
/// should never be instantiated (except as part of a derived class).
IOAsioSocket() {}
public:
/// The destructor.
virtual ~IOAsioSocket() {}
//@}
/// \brief Return the "native" representation of the socket.
///
/// In practice, this is the file descriptor of the socket for
/// UNIX-like systems so the current implementation simply uses
/// \c int as the type of the return value.
/// We may have to need revisit this decision later.
///
/// In general, the application should avoid using this method;
/// it essentially discloses an implementation specific "handle" that
/// can change the internal state of the socket (consider the
/// application closes it, for example).
/// But we sometimes need to perform very low-level operations that
/// requires the native representation. Passing the file descriptor
/// to a different process is one example.
/// This method is provided as a necessary evil for such limited purposes.
///
/// This method never throws an exception.
///
/// \return The native representation of the socket. This is the socket
/// file descriptor for UNIX-like systems.
virtual int getNative() const = 0;
/// \brief Return the transport protocol of the socket.
///
/// Currently, it returns \c IPPROTO_UDP for UDP sockets, and
/// \c IPPROTO_TCP for TCP sockets.
///
/// This method never throws an exception.
///
/// \return IPPROTO_UDP for UDP sockets
/// \return IPPROTO_TCP for TCP sockets
virtual int getProtocol() const = 0;
/// \brief Open AsioSocket
///
/// 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, C& 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 asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, C& 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 cumulative Amount of data that should already be in the buffer.
/// \param endpoint Source of the communication
/// \param callback Callback object
virtual void asyncReceive(void* data, size_t length, size_t cumulative,
IOEndpoint* endpoint, C& callback) = 0;
/// \brief Checks if the data received is complete.
///
/// This applies to TCP receives, where the data is a byte stream and a
/// receive is not guaranteed to receive the entire message. DNS messages
/// over TCP are prefixed by a two-byte count field. This method takes the
/// amount received so far and the amount received in this I/O and checks
/// if the message is complete, returning the appropriate indication. As
/// a side-effect, it also updates the amount received.
///
/// For a UDP receive, all the data is received in one I/O, so this is
/// effectively a no-op (although it does update the amount received).
///
/// \param data Data buffer containing data to date
/// \param length Amount of data received in last asynchronous I/O
/// \param cumulative On input, amount of data received before the last
/// I/O. On output, the total amount of data received to date.
///
/// \return true if the receive is complete, false if another receive is
/// needed.
virtual bool receiveComplete(void* data, size_t length,
size_t& cumulative) = 0;
/// \brief Cancel I/O On AsioSocket
virtual void cancel() = 0;
/// \brief Close socket
virtual void close() = 0;
};
#include "io_socket.h"
/// \brief The \c DummyAsioSocket class is a concrete derived class of
/// \c IOAsioSocket that is not associated with any real socket.
///
/// This main purpose of this class is tests, where it may be desirable to
/// instantiate an \c IOAsioSocket object without involving system resource
/// allocation such as real network sockets.
///
/// \param C Template parameter identifying type of the callback object.
template <typename C>
class DummyAsioSocket : public IOAsioSocket<C> {
private:
DummyAsioSocket(const DummyAsioSocket<C>& source);
DummyAsioSocket& operator=(const DummyAsioSocket<C>& source);
public:
/// \brief Constructor from the protocol number.
///
/// The protocol must validly identify a standard network protocol.
/// For example, to specify TCP \c protocol must be \c IPPROTO_TCP.
///
/// \param protocol The network protocol number for the socket.
DummyAsioSocket(const int protocol) : protocol_(protocol) {}
/// \brief A dummy derived method of \c IOAsioSocket::getNative().
///
/// \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 IOAsioSocket::getProtocol().
///
/// \return Protocol socket was created with
virtual int getProtocol() const { return (protocol_); }
/// \brief Open AsioSocket
///
/// 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*, C&) {
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 asyncSend(const void*, size_t, const IOEndpoint*, C&) {
}
/// \brief Receive Asynchronously
///
/// Must be supplied as it is abstract in the base class.
///
/// \param data Unused
/// \param length Unused
/// \param cumulative Unused
/// \param endpoint Unused
/// \param callback Unused
virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { }
/// \brief Checks if the data received is complete.
///
/// \param data Unused
/// \param length Unused
/// \param cumulative Unused
///
/// \return Always true
virtual bool receiveComplete(void*, size_t, size_t&) {
return (true);
}
/// \brief Cancel I/O On AsioSocket
///
/// 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_;
};
} // namespace asiolink
#endif // __IO_ASIO_SOCKET_H

View File

@ -15,74 +15,33 @@
#ifndef __IO_COMPLETION_CB_H
#define __IO_COMPLETION_CB_H
#include <iostream>
#include <asio/error.hpp>
#include <asio/error_code.hpp>
#include <coroutine.h>
namespace asiolink {
/// \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
/// The two socket classes (UDPSocket and TCPSocket) require that the I/O
/// completion callback function have an operator() method with the appropriate
/// signature. The classes are templates, any class with that method and
/// signature can be passed as the callback object - there is no need for a
/// base class defining the interface. However, some users of the socket
/// classes do not use the asynchronous I/O operations, yet have to supply a
/// template parameter. This is the reason for this class - it is the dummy
/// template parameter.
class IOCompletionCallback : public coroutine {
class IOCompletionCallback {
public:
/// \brief Constructor
IOCompletionCallback() : self_(this)
/// \brief Asynchronous I/O callback method
///
/// \param error Unused
/// \param length Unused
void operator()(asio::error_code, size_t)
{}
/// \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
};
} // namespace asiolink

View File

@ -20,7 +20,8 @@
#include <asio.hpp>
#include <asiolink/asiolink.h>
#include <asiolink/io_address.h>
#include <asiolink/io_error.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/udp_endpoint.h>

View File

@ -12,8 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef __IOENDPOINT_H
#define __IOENDPOINT_H 1
#ifndef __IO_ENDPOINT_H
#define __IO_ENDPOINT_H 1
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
@ -115,7 +115,7 @@ public:
};
} // asiolink
#endif // __IOENDPOINT_H
#endif // __IO_ENDPOINT_H
// Local Variables:
// mode: c++

View File

@ -18,95 +18,38 @@
#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 <log/dummylog.h>
#include <asiolink.h>
#include <coroutine.h>
#include <internal/udpdns.h>
#include <internal/tcpdns.h>
#include <internal/iofetch.h>
#include <asio.hpp>
#include <asiolink/io_fetch.h>
using namespace asio;
using asio::ip::udp;
using asio::ip::tcp;
using isc::log::dlog;
using namespace std;
using namespace isc::dns;
using namespace isc::log;
using namespace std;
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),
rcv_amount(0),
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)
)
IOFetch::IOFetch(int protocol, IOService& service,
const isc::dns::Question& question, const IOAddress& address, uint16_t port,
isc::dns::OutputBufferPtr& buff, Callback* cb, int wait)
:
data_(new IOFetch::IOFetchData(protocol, service, question, address,
port, buff, cb, wait))
{
}
/// 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) {
@ -114,6 +57,7 @@ IOFetch::operator()(error_code ec, size_t length) {
}
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.
@ -130,7 +74,7 @@ IOFetch::operator()(error_code ec, size_t length) {
msg.toWire(renderer);
// As this is a new fetch, clear the amount of data received
data_->rcv_amount = 0;
data_->cumulative = 0;
dlog("Sending " + msg.toText() + " to " +
data_->remote->getAddress().toText());
@ -148,22 +92,30 @@ IOFetch::operator()(error_code ec, size_t length) {
// Open a connection to the target system. For speed, if the operation
// was completed synchronously (i.e. UDP operation) we bypass the yield.
bool asynch = data_->socket->open(data->remote.get(), *this);
if (asynch) {
if (data_->socket->open(data_->remote.get(), *this)) {
CORO_YIELD;
}
// Begin an asynchronous send, and then yield. When the
// Begin an asynchronous send, and then yield. When the send completes
// send completes, we will resume immediately after this point.
CORO_YIELD data_->socket->async_send(data_->msgbuf->getData(),
CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
data_->msgbuf->getLength(), data_->remote.get(), *this);
/// 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);
// Now receive the response. Since TCP may not receive the entire
// message in one operation, we need to loop until we have received
// it. (This can't be done within the asyncReceive() method because
// each I/O operation will be done asynchronously and between each one
// we need to yield ... and we *really* don't want to set up another
// coroutine within that method.) So after each receive (and yield),
// we check if the operation is complete and if not, loop to read again.
do {
CORO_YIELD data_->socket->asyncReceive(data_->data.get(),
static_cast<size_t>(MAX_LENGTH), data_->cumulative,
data_->remote.get(), *this);
} while (!data_->socket->receiveComplete(data_->data.get(), length,
data_->cumulative));
// The message is not rendered yet, so we can't print it easilly
// The message is not rendered yet, so we can't print it easily
dlog("Received response from " + data_->remote->getAddress().toText());
/// Copy the answer into the response buffer. (TODO: If the
@ -188,6 +140,7 @@ IOFetch::operator()(error_code ec, size_t length) {
// 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) {
@ -203,15 +156,23 @@ IOFetch::stop(Result result) {
default:
;
}
data_->stopped = true;
data_->socket->cancel(); // Cancel outstanding I/O
data_->socket->close(); // ... and close the socket
data_->timer.cancel(); // Cancel timeout timer
// Stop requested, cancel and I/O's on the socket and shut it down,
// and cancel the timer.
data_->socket->cancel();
data_->socket->close();
data_->timer.cancel();
// Execute the I/O completion callback (if present).
if (data_->callback) {
(*(data_->callback))(result); // Call callback
(*(data_->callback))(result);
}
// Mark that stop() has now been called.
data_->stopped = true;
}
}
} // namespace asiolink

View File

@ -12,40 +12,44 @@
// 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>
#ifndef __IO_FETCH_H
#define __IO_FETCH_H 1
#include <config.h>
#include <asio.hpp>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <asio/deadline_timer.hpp>
#include <coroutine.h>
#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>
#include <asiolink/io_asio_socket.h>
#include <asiolink/io_endpoint.h>
#include <asiolink/io_service.h>
#include <asiolink/tcp_socket.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/udp_socket.h>
#include <asiolink/udp_endpoint.h>
namespace asiolink {
/// \brief Upstream Fetch Processing
///
/// IOFetch is the class used to send upstream fetches and to handle responses.
/// It is more or less transport-agnostic, although the
class IOFetch : public IOCompletionCallback {
///
/// \param E Endpoint type to use.
class IOFetch : public coroutine {
public:
/// \brief Result of Upstream Fetch
@ -56,8 +60,10 @@ public:
enum Result {
SUCCESS = 0, ///< Success, fetch completed
TIME_OUT, ///< Failure, fetch timed out
STOPPED ///< Control code, fetch has been stopped
STOPPED, ///< Control code, fetch has been stopped
NOTSET ///< For testing, indicates value not set
};
// The next enum is a "trick" to allow constants to be defined in a class
// declaration.
@ -65,8 +71,10 @@ public:
enum {
MAX_LENGTH = 4096 ///< Maximum size of receive buffer
};
/// \brief I/O Fetch Callback
///
/// TODO: change documentation
/// 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
@ -94,9 +102,9 @@ public:
///
/// 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.
/// 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 in this way keeps copying to a minimum.
struct IOFetchData {
// The next two members are shared pointers to a base class because what
@ -104,15 +112,15 @@ public:
// 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<IOAsioSocket<IOFetch> > 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 msgbuf; ///< Wire buffer for question
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
size_t rcv_amount; ///< Received amount
boost::shared_array<char> data; ///< Temporary array for data
IOFetch::Callback* callback; ///< Called on I/O Completion
size_t cumulative; ///< Cumulative received amount
bool stopped; ///< Have we stopped running?
asio::deadline_timer timer; ///< Timer to measure timeouts
int timeout; ///< Timeout in ms
@ -121,7 +129,8 @@ public:
///
/// Just fills in the data members of the IOFetchData structure
///
/// \param io_service I/O Service object to handle the asynchronous
/// \param protocol either IPPROTO_UDP or IPPROTO_TCP
/// \param 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
@ -131,38 +140,60 @@ public:
/// \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);
/// \param wait Timeout for the fetch (in ms).
IOFetchData(int protocol, IOService& service,
const isc::dns::Question& query, const IOAddress& address,
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
int wait)
:
socket((protocol == IPPROTO_UDP) ?
static_cast<IOAsioSocket<IOFetch>*>(
new UDPSocket<IOFetch>(service)) :
static_cast<IOAsioSocket<IOFetch>*>(
new TCPSocket<IOFetch>(service))
),
remote((protocol == IPPROTO_UDP) ?
static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
),
question(query),
msgbuf(new isc::dns::OutputBuffer(512)),
buffer(buff),
data(new char[IOFetch::MAX_LENGTH]),
callback(cb),
cumulative(0),
stopped(false),
timer(service.get_io_service()),
timeout(wait)
{}
};
/// \brief Constructor.
///
/// Creates the object that will handle the upstream fetch.
///
/// \param io_service I/O Service object to handle the asynchronous
/// TODO: Need to randomise the source port
///
/// \param protocol Fetch protocol, either IPPROTO_UDP or IPPROTO_TCP
/// \param 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)
/// \param buff 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
/// \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 timeout Timeout for the fetch (in ms). The default value of
/// \param address IP address of upstream server
/// \param port Port to which to connect on the upstream server
/// (default = 53)
/// \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
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);
IOFetch(int protocol, IOService& service,
const isc::dns::Question& question, const IOAddress& address,
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
int wait = -1);
// The default constructor and copy constructor are correct for this method.
/// \brief Coroutine entry point
///
@ -174,7 +205,6 @@ public:
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
@ -184,10 +214,10 @@ public:
void stop(Result reason = STOPPED);
private:
boost::shared_ptr<IOFetchData> data_; ///< Private data
boost::shared_ptr<IOFetchData> data_; ///< Private data
};
}
} // namespace asiolink
#endif // __IOFETCH_H
#endif // __IO_FETCH_H

View File

@ -12,8 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef __IOMESSAGE_H
#define __IOMESSAGE_H 1
#ifndef __IO_MESSAGE_H
#define __IO_MESSAGE_H 1
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
@ -97,7 +97,7 @@ private:
} // asiolink
#endif // __IOMESSAGE_H
#endif // __IO_MESSAGE_H
// Local Variables:
// mode: c++

View File

@ -14,10 +14,9 @@
// 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;
@ -44,76 +43,11 @@ public:
/// \brief A dummy derived method of \c IOSocket::getNative().
///
/// \return Always returns -1 as the object is not associated with a real
/// (native) socket.
/// This version of method 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 asyncSend(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 cumulative Unused
/// \param endpoint Unused
/// \param callback Unused
virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*,
IOCompletionCallback&) {
}
/// \brief Checks if the data received is complete.
///
/// \param data Unused
/// \param length Unused
/// \param cumulative Unused
///
/// \return Always true
virtual bool receiveComplete(void*, size_t, size_t&) {
return (true);
}
/// \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_;
};

View File

@ -12,8 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#ifndef __IOSOCKET_H
#define __IOSOCKET_H 1
#ifndef __IO_SOCKET_H
#define __IO_SOCKET_H 1
// IMPORTANT NOTE: only very few ASIO headers files can be included in
// this file. In particular, asio.hpp should never be included here.
@ -24,16 +24,9 @@
#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.
///
@ -102,82 +95,6 @@ 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 asyncSend(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 cumulative Amount of data that should already be in the buffer.
/// \param endpoint Source of the communication
/// \param callback Callback object
virtual void asyncReceive(void* data, size_t length, size_t cumulative,
IOEndpoint* endpoint, IOCompletionCallback& callback) = 0;
/// \brief Checks if the data received is complete.
///
/// This applies to TCP receives, where the data is a byte stream and a
/// receive is not guaranteed to receive the entire message. DNS messages
/// over TCP are prefixed by a two-byte count field. This method takes the
/// amount received so far and the amount received in this I/O and checks
/// if the message is complete, returning the appropriate indication. As
/// a side-effect, it also updates the amount received.
///
/// For a UDP receive, all the data is received in one I/O, so this is
/// effectively a no-op (although it does update the amount received).
///
/// \param data Data buffer containing data to date
/// \param length Amount of data received in last asynchronous I/O
/// \param cumulative On input, amount of data received before the last
/// I/O. On output, the total amount of data received to date.
///
/// \return true if the receive is complete, false if another receive is
/// needed.
virtual bool receiveComplete(void* data, size_t length, size_t& cumulative) = 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.
@ -202,9 +119,6 @@ public:
static IOSocket& getDummyTCPSocket();
};
} // asiolink
#endif // __IOSOCKET_H
} // namespace asiolink
// Local Variables:
// mode: c++
// End:
#endif // __IO_SOCKET_H

View File

@ -20,9 +20,9 @@
#include <log/dummylog.h>
#include <asiolink/io_completion_cb.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/tcp_socket.h>
#include <asiolink/tcp_server.h>
@ -115,7 +115,15 @@ TCPServer::operator()(error_code ec, size_t length) {
// that would quickly generate an IOMessage object without
// all these calls to "new".)
peer_.reset(new TCPEndpoint(socket_->remote_endpoint()));
iosock_.reset(new TCPSocket(*socket_));
// The TCP socket class has been extended with asynchronous functions
// and takes as a template parameter a completion callback class. As
// TCPServer does not use these extended functions (only those defined
// in the IOSocket base class) - but needs a TCPSocket to get hold of
// the underlying Boost TCP socket - use "IOCompletionCallback" -
// a basic callback class: it is not used but provides the appropriate
// signature.
iosock_.reset(new TCPSocket<IOCompletionCallback>(*socket_));
io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_));
bytes_ = length;

View File

@ -19,55 +19,78 @@
#error "asio.hpp must be included before including this, see asiolink.h as to why"
#endif
#include <asiolink/io_socket.h>
#include <log/dummylog.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
#include <iostream>
#include <cstddef>
#include <config.h>
#include <asiolink/io_asio_socket.h>
#include <asiolink/io_endpoint.h>
#include <asiolink/io_service.h>
#include <asiolink/tcp_endpoint.h>
namespace asiolink {
/// \brief The \c TCPSocket class is a concrete derived class of
/// \c IOSocket that represents a TCP socket.
///
/// In the current implementation, an object of this class is always
/// instantiated within the wrapper routines. Applications are expected to
/// get access to the object via the abstract base class, \c IOSocket.
/// This design may be changed when we generalize the wrapper interface.
class TCPSocket : public IOSocket {
/// Other notes about \c TCPSocket applies to this class, too.
///
/// \param C Callback type
template <typename C>
class TCPSocket : public IOAsioSocket<C> {
private:
TCPSocket(const TCPSocket& source);
TCPSocket& operator=(const TCPSocket& source);
/// \brief Class is non-copyable
TCPSocket(const TCPSocket&);
TCPSocket& operator=(const TCPSocket&);
public:
enum {
MAX_SIZE = 4096 // Send and receive size
};
/// \brief Constructor from an ASIO TCP socket.
///
/// \param socket The ASIO representation of the TCP socket.
TCPSocket(asio::ip::tcp::socket& socket) : socket_(socket) {}
/// \param socket The ASIO representation of the TCP socket. It
/// is assumed that the caller will open and close the socket, so
/// these operations are a no-op for that socket.
TCPSocket(asio::ip::tcp::socket& socket);
int getNative() const { return (socket_.native()); }
int getProtocol() const { return (IPPROTO_TCP); }
/// \brief Constructor
///
/// Used when the TCPSocket is being asked to manage its own internal
/// socket. It is assumed that open() and close() will not be used.
///
/// \param service I/O Service object used to manage the socket.
TCPSocket(IOService& service);
/// \brief Destructor
virtual ~TCPSocket();
virtual int getNative() const { return (socket_.native()); }
virtual 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.
/// Opens the TCP socket. In the model for transport-layer agnostic I/O,
/// an "open" operation includes a connection to the remote end (which
/// may take time). This does not happen for TCP, so the method returns
/// "false" to indicate that the operation completed synchronously.
///
/// \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.
/// \param endpoint Endpoint to which the socket will connect to.
/// \param callback Unused.
///
/// \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;
}
/// \return false to indicate that the "operation" completed synchronously.
virtual bool open(const IOEndpoint* endpoint, C&);
/// \brief Send Asynchronously
///
/// This corresponds to async_send_to() for UDP sockets and async_send()
/// This corresponds to async_send_to() for TCP 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.
///
@ -75,13 +98,12 @@ public:
/// \param length Length of data to send
/// \param endpoint Target of the send
/// \param callback Callback object.
virtual void asyncSend(const void*, size_t,
const IOEndpoint*, IOCompletionCallback&) {
}
virtual void asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, C& callback);
/// \brief Receive Asynchronously
///
/// This correstponds to async_receive_from() for UDP sockets and
/// This correstponds to async_receive_from() for TCP 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.
@ -89,18 +111,19 @@ public:
/// \param data Buffer to receive incoming message
/// \param length Length of the data buffer
/// \param cumulative Amount of data that should already be in the buffer.
/// (This is ignored - every UPD receive fills the buffer from the start.)
/// \param endpoint Source of the communication
/// \param callback Callback object
virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*,
IOCompletionCallback&) {
}
virtual void asyncReceive(void* data, size_t length, size_t cumulative,
IOEndpoint* endpoint, C& callback);
/// \brief Checks if the data received is complete.
///
/// Checks that the total data received is the amount expected by the
/// two-byte header to the message.
/// As all the data is received in one I/O, so this is, this is effectively
/// a no-op (although it does update the amount of data received).
///
/// \param data Data buffer containing data to date
/// \param data Data buffer containing data to date. (This is ignored
/// for TCP receives.)
/// \param length Amount of data received in last asynchronous I/O
/// \param cumulative On input, amount of data received before the last
/// I/O. On output, the total amount of data received to date.
@ -113,17 +136,147 @@ public:
}
/// \brief Cancel I/O On Socket
virtual void cancel() {
}
virtual void cancel();
/// \brief Close socket
virtual void close() {
}
virtual void close();
private:
asio::ip::tcp::socket& socket_;
// Two variables to hold the socket - a socket and a pointer to it. This
// handles the case where a socket is passed to the TCPSocket on
// construction, or where it is asked to manage its own socket.
asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket
asio::ip::tcp::socket& socket_; ///< Socket
bool isopen_; ///< true when socket is open
};
// Constructor - caller manages socket
template <typename C>
TCPSocket<C>::TCPSocket(asio::ip::tcp::socket& socket) :
socket_ptr_(NULL), socket_(socket), isopen_(true)
{
}
// Constructor - create socket on the fly
template <typename C>
TCPSocket<C>::TCPSocket(IOService& service) :
socket_ptr_(new asio::ip::tcp::socket(service.get_io_service())),
socket_(*socket_ptr_), isopen_(false)
{
}
// Destructor. Only delete the socket if we are managing it.
template <typename C>
TCPSocket<C>::~TCPSocket()
{
delete socket_ptr_;
}
// Open the socket. Throws an error on failure
// TODO: Make the open more resilient
template <typename C> bool
TCPSocket<C>::open(const IOEndpoint* endpoint, C&) {
// Ignore opens on already-open socket. Don't throw a failure because
// of uncertainties as to what precedes whan when using asynchronous I/O.
// At also allows us a treat a passed-in socket as a self-managed socket.
if (!isopen_) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(asio::ip::tcp::v4());
}
else {
socket_.open(asio::ip::tcp::v6());
}
isopen_ = true;
// TODO: Complete TCPSocket::open()
}
return (false);
}
// Send a message. Should never do this if the socket is not open, so throw
// an exception if this is the case.
template <typename C> void
TCPSocket<C>::asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
// Upconvert to a TCPEndpoint. We need to do this because although
// IOEndpoint is the base class of TCPEndpoint and TCPEndpoint, it
// doing cont contain a method for getting at the underlying endpoint
// type - those are in the derived class and the two classes differ on
// return type.
assert(endpoint->getProtocol() == IPPROTO_TCP);
const TCPEndpoint* tcp_endpoint =
static_cast<const TCPEndpoint*>(endpoint);
std::cerr << "TCPSocket::asyncSend(): sending to " <<
tcp_endpoint->getAddress().toText() <<
", port " << tcp_endpoint->getPort() << "\n";
// TODO: Complete TCPSocket::asyncSend()
} else {
isc_throw(SocketNotOpen,
"attempt to send on a TCP socket that is not open");
}
}
// Receive a message. Note that the "cumulative" argument is ignored - every TCP
// receive is put into the buffer beginning at the start - there is no concept
// receiving a subsequent part of a message. Same critera as before concerning
// the need for the socket to be open.
template <typename C> void
TCPSocket<C>::asyncReceive(void* data, size_t length, size_t,
IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
// Upconvert the endpoint again.
assert(endpoint->getProtocol() == IPPROTO_TCP);
const TCPEndpoint* tcp_endpoint =
static_cast<const TCPEndpoint*>(endpoint);
std::cerr << "TCPSocket::asyncReceive(): receiving from " <<
tcp_endpoint->getAddress().toText() <<
", port " << tcp_endpoint->getPort() << "\n";
// TODO: Complete TCPSocket::asyncReceive()
} else {
isc_throw(SocketNotOpen,
"attempt to receive from a TCP socket that is not open");
}
}
// Cancel I/O on the socket. No-op if the socket is not open.
template <typename C> void
TCPSocket<C>::cancel() {
if (isopen_) {
socket_.cancel();
}
}
// Close the socket down. Can only do this if the socket is open and we are
// managing it ourself.
template <typename C> void
TCPSocket<C>::close() {
if (isopen_ && socket_ptr_) {
socket_.close();
isopen_ = false;
}
}
} // namespace asiolink
} // namespace asiolink
#endif // __TCP_SOCKET_H

View File

@ -15,10 +15,12 @@ CLEANFILES = *.gcno *.gcda
TESTS =
if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES = run_unittests.cc
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 += io_address_unittest.cc
run_unittests_SOURCES += io_endpoint_unittest.cc
run_unittests_SOURCES += io_fetch_unittest.cc
run_unittests_SOURCES += io_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
run_unittests_SOURCES += interval_timer_unittest.cc
@ -26,15 +28,18 @@ run_unittests_SOURCES += recursive_query_unittest.cc
run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_query_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS)
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
run_unittests_CXXFLAGS = $(AM_CXXFLAGS)

View File

@ -0,0 +1,189 @@
// 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 <gtest/gtest.h>
#include <boost/bind.hpp>
#include <cstdlib>
#include <string>
#include <string.h>
#include <asio.hpp>
#include <dns/buffer.h>
#include <dns/question.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
#include <dns/opcode.h>
#include <dns/name.h>
#include <dns/rcode.h>
#include <asiolink/io_fetch.h>
#include <asiolink/io_completion_cb.h>
#include <asiolink/io_service.h>
using namespace asio;
using namespace isc::dns;
using asio::ip::udp;
namespace asiolink {
const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
const uint16_t TEST_PORT(5301);
// FIXME Shouldn't we send something that is real message?
const char TEST_DATA[] = "TEST DATA";
/// \brief Test fixture for the asiolink::IOFetch.
class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
{
public:
IOService service_; ///< Service to run the query
IOFetch::Result expected_; ///< Expected result of the callback
bool run_; ///< Did the callback run already?
Question question_; ///< What to ask
OutputBufferPtr buff_; ///< Buffer to hold result
IOFetch udp_fetch_; ///< For UDP query test
//IOFetch tcp_fetch_; ///< For TCP query test
// The next member is the buffer iin which the "server" (implemented by the
// response handler method) receives the question sent by the fetch object.
char server_buff_[512]; ///< Server buffer
/// \brief Constructor
IOFetchTest() :
service_(),
expected_(IOFetch::NOTSET),
run_(false),
question_(Name("example.net"), RRClass::IN(), RRType::A()),
buff_(new OutputBuffer(512)),
udp_fetch_(IPPROTO_UDP, service_, question_, IOAddress(TEST_HOST),
TEST_PORT, buff_, this, 100)
// tcp_fetch_(service_, question_, IOAddress(TEST_HOST), TEST_PORT,
// buff_, this, 100, IPPROTO_UDP)
{ }
/// \brief Fetch completion callback
///
/// This is the callback's operator() method which is called when the fetch
/// is complete. Check that the data received is the wire format of the
/// question, then send back an arbitrary response.
void operator()(IOFetch::Result result) {
EXPECT_EQ(expected_, result); // Check correct result returned
EXPECT_FALSE(run_); // Check it is run only once
run_ = true; // Note success
service_.stop(); // ... and exit run loop
}
/// \brief Response handler, pretending to be remote DNS server
///
/// This checks that the data sent is what we expected to receive, and
/// sends back a test answer.
void respond(udp::endpoint* remote, udp::socket* socket,
asio::error_code ec = asio::error_code(), size_t length = 0) {
// Construct the data buffer for question we expect to receive.
OutputBuffer msgbuf(512);
Message msg(Message::RENDER);
msg.setQid(0);
msg.setOpcode(Opcode::QUERY());
msg.setRcode(Rcode::NOERROR());
msg.setHeaderFlag(Message::HEADERFLAG_RD);
msg.addQuestion(question_);
MessageRenderer renderer(msgbuf);
msg.toWire(renderer);
// The QID in the incoming data is random so set it to 0 for the
// data comparison check. (It was set to 0 when the buffer containing
// the expected data was constructed above.)
server_buff_[0] = server_buff_[1] = 0;
// Check that lengths are identical.
EXPECT_EQ(msgbuf.getLength(), length);
EXPECT_TRUE(memcmp(msgbuf.getData(), server_buff_, length) == 0);
// ... and return a message back.
socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA), *remote);
}
};
/// Test that when we run the query and stop it after it was run,
/// it returns "stopped" correctly.
///
/// That is why stop() is posted to the service_ as well instead
/// of calling it.
TEST_F(IOFetchTest, UdpStop) {
expected_ = IOFetch::STOPPED;
// Post the query
service_.get_io_service().post(udp_fetch_);
// Post query_.stop() (yes, the boost::bind thing is just
// query_.stop()).
service_.get_io_service().post(
boost::bind(&IOFetch::stop, udp_fetch_, IOFetch::STOPPED));
// Run both of them. run() returns when everything in the I/O service
// queue has completed.
service_.run();
EXPECT_TRUE(run_);
}
// Test that when we queue the query to service_ and call stop() before it gets
// executed, it acts sanely as well (eg. has the same result as running stop()
// after - calls the callback).
TEST_F(IOFetchTest, UdpPrematureStop) {
expected_ = IOFetch::STOPPED;
// Stop before it is started
udp_fetch_.stop();
service_.get_io_service().post(udp_fetch_);
service_.run();
EXPECT_TRUE(run_);
}
// Test that it will timeout when no answer arrives.
TEST_F(IOFetchTest, UdpTimeout) {
expected_ = IOFetch::TIME_OUT;
service_.get_io_service().post(udp_fetch_);
service_.run();
EXPECT_TRUE(run_);
}
// Test that it will succeed when we fake an answer and stores the same data we
// send. This is done through a real socket on the loopback address.
TEST_F(IOFetchTest, UdpReceive) {
expected_ = IOFetch::SUCCESS;
udp::socket socket(service_.get_io_service(), udp::v4());
socket.set_option(socket_base::reuse_address(true));
socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
udp::endpoint remote;
socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)),
remote,
boost::bind(&IOFetchTest::respond, this, &remote, &socket, _1, _2));
service_.get_io_service().post(udp_fetch_);
service_.run();
socket.close();
EXPECT_TRUE(run_);
ASSERT_EQ(sizeof TEST_DATA, buff_->getLength());
EXPECT_EQ(0, memcmp(TEST_DATA, buff_->getData(), sizeof TEST_DATA));
}
} // namespace asiolink

View File

@ -15,6 +15,8 @@
#include <config.h>
#include <gtest/gtest.h>
#include <netinet/in.h>
#include <asio.hpp>
#include <asiolink/io_socket.h>

View File

@ -52,14 +52,12 @@
#include <asio.hpp>
#include <asiolink/io_completion_cb.h>
#include <asiolink/io_service.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
using namespace asio;
using namespace asiolink;
using asio::ip::udp;
using namespace std;
namespace {
@ -77,7 +75,7 @@ const char INBOUND_DATA[] = "Returned data from server to client";
/// and the operator() method is called when when an asynchronous I/O
/// completes. The arguments to the completion callback are stored for later
/// retrieval.
class UDPCallback : public IOCompletionCallback {
class UDPCallback {
public:
struct PrivateData {
@ -187,33 +185,38 @@ private:
TEST(UDPSocket, SequenceTest) {
// Common objects.
IOAddress server_address(SERVER_ADDRESS); // Address of target server
UDPEndpoint endpoint(server_address, SERVER_PORT); // Endpoint of target server
IOService service; // Service object for async control
// Server
IOAddress server_address(SERVER_ADDRESS); // Address of target server
UDPCallback server_cb("Server"); // Server callback
UDPEndpoint server_endpoint( // Endpoint describing server
server_address, SERVER_PORT);
UDPEndpoint server_remote_endpoint; // Address where server received message from
// The client - the UDPSocket being tested
UDPSocket client(service); // Socket under test
UDPSocket<UDPCallback> client(service);// Socket under test
UDPCallback client_cb("Client"); // Async I/O callback function
UDPEndpoint client_remote_endpoint; // Where client receives message from
size_t client_cumulative = 0; // Cumulative data received
// The server - with which the client communicates. For convenience, we
// use the same io_service, and use the endpoint object created for
// the client to send to as the endpoint object in the constructor.
UDPCallback server_cb("Server");
udp::socket server(service.get_io_service(), endpoint.getASIOEndpoint());
asio::ip::udp::socket server(service.get_io_service(),
server_endpoint.getASIOEndpoint());
server.set_option(socket_base::reuse_address(true));
// Assertion to ensure that the server buffer is large enough
char data[UDPSocket::MAX_SIZE];
char data[UDPSocket<UDPCallback>::MAX_SIZE];
ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
// Open the client socket - the operation should be synchronous
EXPECT_FALSE(client.open(&endpoint, client_cb));
EXPECT_FALSE(client.open(&server_endpoint, client_cb));
// Issue read on the server. Completion callback should not have run.
server_cb.setCalled(false);
server_cb.setCode(42); // Answer to Life, the Universe and Everything!
UDPEndpoint server_remote_endpoint;
server.async_receive_from(buffer(data, sizeof(data)),
server_remote_endpoint.getASIOEndpoint(), server_cb);
EXPECT_FALSE(server_cb.getCalled());
@ -222,7 +225,7 @@ TEST(UDPSocket, SequenceTest) {
// be called until we call the io_service.run() method.
client_cb.setCalled(false);
client_cb.setCode(7); // Arbitrary number
client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &endpoint, client_cb);
client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
EXPECT_FALSE(client_cb.getCalled());
// Execute the two callbacks.
@ -244,7 +247,6 @@ TEST(UDPSocket, SequenceTest) {
client_cb.setLength(12345); // Arbitrary number
client_cb.setCalled(false);
client_cb.setCode(32); // Arbitrary number
UDPEndpoint client_remote_endpoint; // To receive address of remote system
client.asyncReceive(data, sizeof(data), client_cumulative,
&client_remote_endpoint, client_cb);
@ -256,8 +258,8 @@ TEST(UDPSocket, SequenceTest) {
server_remote_endpoint.getASIOEndpoint(), server_cb);
// Expect two callbacks to run
service.run_one();
service.run_one();
service.get_io_service().poll();
//service.run_one();
EXPECT_TRUE(client_cb.getCalled());
EXPECT_EQ(0, client_cb.getCode());

View File

@ -20,10 +20,10 @@
#include <log/dummylog.h>
#include <asiolink/io_completion_cb.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
#include <asiolink/udp_server.h>
#include <asiolink/udp_socket.h>
using namespace asio;
using asio::ip::udp;
@ -203,7 +203,17 @@ UDPServer::operator()(error_code ec, size_t length) {
// that would quickly generate an IOMessage object without
// all these calls to "new".)
data_->peer_.reset(new UDPEndpoint(*data_->sender_));
data_->iosock_.reset(new UDPSocket(*data_->socket_));
// The TCP socket class has been extended with asynchronous functions
// and takes as a template parameter a completion callback class. As
// TCPServer does not use these extended functions (only those defined
// in the IOSocket base class) - but needs a TCPSocket to get hold of
// the underlying Boost TCP socket - use "IOCompletionCallback" -
// a basic callback class: it is not used but provides the appropriate
// signature.
data_->iosock_.reset(
new UDPSocket<IOCompletionCallback>(*data_->socket_));
data_->io_message_.reset(new IOMessage(data_->data_.get(),
data_->bytes_, *data_->iosock_, *data_->peer_));

View File

@ -1,131 +0,0 @@
// 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.
#include <config.h>
#include <iostream>
#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 <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 <coroutine.h>
#include <asiolink/asiolink.h>
using namespace asio;
using asio::ip::udp;
using namespace std;
using namespace isc::dns;
namespace asiolink {
// Constructor - create socket on the fly
UDPSocket::UDPSocket(IOService& service) :
socket_ptr_(new asio::ip::udp::socket(service.get_io_service())),
socket_(*socket_ptr_)
{
}
// Destructor
UDPSocket::~UDPSocket()
{
delete socket_ptr_;
}
// Open the socket. Throws an error on failure
// TODO: Make the open more resolient
bool
UDPSocket::open(const IOEndpoint* endpoint, IOCompletionCallback&) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(asio::ip::udp::v4());
}
else {
socket_.open(asio::ip::udp::v6());
}
// Ensure it can send and receive 4K buffers.
socket_.set_option(asio::socket_base::send_buffer_size(MAX_SIZE));
socket_.set_option(asio::socket_base::receive_buffer_size(MAX_SIZE));
;
// Allow reuse of an existing port/address
socket_.set_option(asio::socket_base::reuse_address(true));
return (false);
}
// Send a message.
void
UDPSocket::asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, IOCompletionCallback& callback)
{
// Upconverting. Not nice, but we have the problem that in the abstract
// layer we are given an IOEndpoint. For UDP code it is a UDPEndpoint
// and for TCP code a TCPEndpoint. However the member that we are
// after - the asio endpoint - is different for UPD and TCP and there is
// no common ancestor. Hence the promotion here.
assert(endpoint->getProtocol() == IPPROTO_UDP);
const UDPEndpoint* udp_endpoint = static_cast<const UDPEndpoint*>(endpoint);
socket_.async_send_to(buffer(data, length), udp_endpoint->getASIOEndpoint(),
callback);
}
// Receive a message. Note that the "cumulative" argument is ignored - every UDP
// receive is put into the buffer beginning at the start - there is no concept
// receiving a subsequent part of a message.
void
UDPSocket::asyncReceive(void* data, size_t length, size_t, IOEndpoint* endpoint,
IOCompletionCallback& callback)
{
// Upconvert the endpoint again.
assert(endpoint->getProtocol() == IPPROTO_UDP);
UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
socket_.async_receive_from(buffer(data, length),
udp_endpoint->getASIOEndpoint(), callback);
}
// Cancel I/O on the socket
void
UDPSocket::cancel() {
socket_.cancel();
}
// Close the socket down
void
UDPSocket::close() {
socket_.close();
}
} // namespace asiolink

View File

@ -19,9 +19,19 @@
#error "asio.hpp must be included before including this, see asiolink.h as to why"
#endif
#include <log/dummylog.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
#include <cstddef>
#include <asiolink/io_socket.h>
#include <config.h>
#include <asiolink/io_asio_socket.h>
#include <asiolink/io_endpoint.h>
#include <asiolink/io_service.h>
#include <asiolink/udp_endpoint.h>
namespace asiolink {
@ -29,7 +39,10 @@ namespace asiolink {
/// \c IOSocket that represents a UDP socket.
///
/// Other notes about \c TCPSocket applies to this class, too.
class UDPSocket : public IOSocket {
///
/// \param C Callback type
template <typename C>
class UDPSocket : public IOAsioSocket<C> {
private:
/// \brief Class is non-copyable
UDPSocket(const UDPSocket&);
@ -42,15 +55,17 @@ public:
/// \brief Constructor from an ASIO UDP socket.
///
/// \param socket The ASIO representation of the UDP socket.
UDPSocket(asio::ip::udp::socket& socket) :
socket_ptr_(NULL), socket_(socket)
{}
/// \param socket The ASIO representation of the UDP socket. It
/// is assumed that the caller will open and close the socket, so
/// these operations are a no-op for that socket.
UDPSocket(asio::ip::udp::socket& socket);
/// \brief Constructor
///
/// Used when the UDPSocket is being asked to manage its own internal
/// socket.
/// socket. It is assumed that open() and close() will not be used.
///
/// \param service I/O Service object used to manage the socket.
UDPSocket(IOService& service);
/// \brief Destructor
@ -70,7 +85,7 @@ public:
/// \param callback Unused.
///
/// \return false to indicate that the "operation" completed synchronously.
virtual bool open(const IOEndpoint* endpoint, IOCompletionCallback&);
virtual bool open(const IOEndpoint* endpoint, C&);
/// \brief Send Asynchronously
///
@ -83,7 +98,7 @@ public:
/// \param endpoint Target of the send
/// \param callback Callback object.
virtual void asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, IOCompletionCallback& callback);
const IOEndpoint* endpoint, C& callback);
/// \brief Receive Asynchronously
///
@ -99,7 +114,7 @@ public:
/// \param endpoint Source of the communication
/// \param callback Callback object
virtual void asyncReceive(void* data, size_t length, size_t cumulative,
IOEndpoint* endpoint, IOCompletionCallback& callback);
IOEndpoint* endpoint, C& callback);
/// \brief Checks if the data received is complete.
///
@ -130,9 +145,133 @@ private:
// Two variables to hold the socket - a socket and a pointer to it. This
// handles the case where a socket is passed to the UDPSocket on
// construction, or where it is asked to manage its own socket.
asio::ip::udp::socket* socket_ptr_; ///< Pointer to the socket
asio::ip::udp::socket* socket_ptr_; ///< Pointer to own socket
asio::ip::udp::socket& socket_; ///< Socket
bool isopen_; ///< true when socket is open
};
} // namespace asiolink
// Constructor - caller manages socket
template <typename C>
UDPSocket<C>::UDPSocket(asio::ip::udp::socket& socket) :
socket_ptr_(NULL), socket_(socket), isopen_(true)
{
}
// Constructor - create socket on the fly
template <typename C>
UDPSocket<C>::UDPSocket(IOService& service) :
socket_ptr_(new asio::ip::udp::socket(service.get_io_service())),
socket_(*socket_ptr_), isopen_(false)
{
}
// Destructor. Only delete the socket if we are managing it.
template <typename C>
UDPSocket<C>::~UDPSocket()
{
delete socket_ptr_;
}
// Open the socket. Throws an error on failure
// TODO: Make the open more resilient
template <typename C> bool
UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
// Ignore opens on already-open socket. Don't throw a failure because
// of uncertainties as to what precedes whan when using asynchronous I/O.
// At also allows us a treat a passed-in socket as a self-managed socket.
if (!isopen_) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(asio::ip::udp::v4());
}
else {
socket_.open(asio::ip::udp::v6());
}
isopen_ = true;
// Ensure it can send and receive 4K buffers.
socket_.set_option(asio::socket_base::send_buffer_size(MAX_SIZE));
socket_.set_option(asio::socket_base::receive_buffer_size(MAX_SIZE));
;
// Allow reuse of an existing port/address
socket_.set_option(asio::socket_base::reuse_address(true));
}
return (false);
}
// Send a message. Should never do this if the socket is not open, so throw
// an exception if this is the case.
template <typename C> void
UDPSocket<C>::asyncSend(const void* data, size_t length,
const IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
// Upconvert to a UDPEndpoint. We need to do this because although
// IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
// doing cont contain a method for getting at the underlying endpoint
// type - those are in the derived class and the two classes differ on
// return type.
assert(endpoint->getProtocol() == IPPROTO_UDP);
const UDPEndpoint* udp_endpoint =
static_cast<const UDPEndpoint*>(endpoint);
socket_.async_send_to(buffer(data, length),
udp_endpoint->getASIOEndpoint(), callback);
} else {
isc_throw(SocketNotOpen,
"attempt to send on a UDP socket that is not open");
}
}
// Receive a message. Note that the "cumulative" argument is ignored - every UDP
// receive is put into the buffer beginning at the start - there is no concept
// receiving a subsequent part of a message. Same critera as before concerning
// the need for the socket to be open.
template <typename C> void
UDPSocket<C>::asyncReceive(void* data, size_t length, size_t,
IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
// Upconvert the endpoint again.
assert(endpoint->getProtocol() == IPPROTO_UDP);
UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
socket_.async_receive_from(buffer(data, length),
udp_endpoint->getASIOEndpoint(), callback);
} else {
isc_throw(SocketNotOpen,
"attempt to receive from a UDP socket that is not open");
}
}
// Cancel I/O on the socket. No-op if the socket is not open.
template <typename C> void
UDPSocket<C>::cancel() {
if (isopen_) {
socket_.cancel();
}
}
// Close the socket down. Can only do this if the socket is open and we are
// managing it ourself.
template <typename C> void
UDPSocket<C>::close() {
if (isopen_ && socket_ptr_) {
socket_.close();
isopen_ = false;
}
}
} // namespace asiolink
#endif // __UDP_SOCKET_H