diff --git a/INSTALL b/INSTALL index 6ab63ea167..7d1c323bea 100644 --- a/INSTALL +++ b/INSTALL @@ -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 `' 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. diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index fe4d7b08a5..4b9b8f85f8 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -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) diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h index 9e402e32b0..03951ae9df 100644 --- a/src/lib/asiolink/asiolink.h +++ b/src/lib/asiolink/asiolink.h @@ -34,9 +34,6 @@ #include #include -#include -#include - /// \namespace asiolink /// \brief A wrapper interface for the ASIO library. /// diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc index 990524acfb..70e837456d 100644 --- a/src/lib/asiolink/io_address.cc +++ b/src/lib/asiolink/io_address.cc @@ -20,7 +20,10 @@ #include -#include +#include +#include +#include + using namespace asio; using asio::ip::udp; diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h index 98e6fe883a..0d2787f95f 100644 --- a/src/lib/asiolink/io_address.h +++ b/src/lib/asiolink/io_address.h @@ -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++ diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h new file mode 100644 index 0000000000..885a95cb35 --- /dev/null +++ b/src/lib/asiolink/io_asio_socket.h @@ -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 // for some network system calls + +#include +#include + +#include +#include + +#include +#include +#include + +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 +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& source); + IOAsioSocket& operator=(const IOAsioSocket& 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 +class DummyAsioSocket : public IOAsioSocket { +private: + DummyAsioSocket(const DummyAsioSocket& source); + DummyAsioSocket& operator=(const DummyAsioSocket& 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 diff --git a/src/lib/asiolink/io_completion_cb.h b/src/lib/asiolink/io_completion_cb.h index c6943af5b1..27b7407fa1 100644 --- a/src/lib/asiolink/io_completion_cb.h +++ b/src/lib/asiolink/io_completion_cb.h @@ -15,74 +15,33 @@ #ifndef __IO_COMPLETION_CB_H #define __IO_COMPLETION_CB_H +#include + #include #include -#include 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 diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc index 86e06070da..bf79f61868 100644 --- a/src/lib/asiolink/io_endpoint.cc +++ b/src/lib/asiolink/io_endpoint.cc @@ -20,7 +20,8 @@ #include -#include +#include +#include #include #include diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h index 37f9087086..62b9e47942 100644 --- a/src/lib/asiolink/io_endpoint.h +++ b/src/lib/asiolink/io_endpoint.h @@ -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++ diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 5ab64794ec..1a5c04d562 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -18,95 +18,38 @@ #include #include -#include -#include -#include - -#include #include -#include -#include #include #include -#include #include #include +#include -#include -#include -#include -#include -#include +#include +#include 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(new UDPSocket(io_service, address)) : - static_cast(new TCPSocket(io_service, address)) - ), - remote((protocol == IPPROTO_UDP) ? - static_cast(new UDPEndpoint(address, port)) : - static_cast(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(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(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 + diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index 00f276c0cc..69af83014e 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -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 +#ifndef __IO_FETCH_H +#define __IO_FETCH_H 1 #include -#include +#include +#include +#include // for some IPC/network system calls + #include #include +#include #include +#include + #include #include -#include -#include -#include -#include - - -#include -#include -#include - +#include +#include +#include +#include +#include +#include +#include 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 socket; ///< Socket to use for I/O + boost::shared_ptr > socket; + ///< Socket to use for I/O boost::shared_ptr 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 data; ///< Temporary array for the data - Callback* callback; ///< Called on I/O Completion - size_t rcv_amount; ///< Received amount + boost::shared_array 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*>( + new UDPSocket(service)) : + static_cast*>( + new TCPSocket(service)) + ), + remote((protocol == IPPROTO_UDP) ? + static_cast(new UDPEndpoint(address, port)) : + static_cast(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 data_; ///< Private data + boost::shared_ptr data_; ///< Private data + }; -} +} // namespace asiolink - -#endif // __IOFETCH_H +#endif // __IO_FETCH_H diff --git a/src/lib/asiolink/io_message.h b/src/lib/asiolink/io_message.h index 5ea99141d8..532f4492d9 100644 --- a/src/lib/asiolink/io_message.h +++ b/src/lib/asiolink/io_message.h @@ -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++ diff --git a/src/lib/asiolink/io_socket.cc b/src/lib/asiolink/io_socket.cc index 11b0194e1e..fb325e9172 100644 --- a/src/lib/asiolink/io_socket.cc +++ b/src/lib/asiolink/io_socket.cc @@ -14,10 +14,9 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -#include - #include "io_socket.h" +#include 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_; }; diff --git a/src/lib/asiolink/io_socket.h b/src/lib/asiolink/io_socket.h index 4fb68f8af0..bebc8b6bf8 100644 --- a/src/lib/asiolink/io_socket.h +++ b/src/lib/asiolink/io_socket.h @@ -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 #include -#include - -#include 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 diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc index 3928bc1e92..768f6e8860 100644 --- a/src/lib/asiolink/tcp_server.cc +++ b/src/lib/asiolink/tcp_server.cc @@ -20,9 +20,9 @@ #include +#include #include #include - #include @@ -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(*socket_)); io_message_.reset(new IOMessage(data_.get(), length, *iosock_, *peer_)); bytes_ = length; diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index abcc3d8c3d..c45501ec7d 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -19,55 +19,78 @@ #error "asio.hpp must be included before including this, see asiolink.h as to why" #endif -#include +#include +#include +#include +#include // for some IPC/network system calls + +#include +#include + +#include + +#include +#include +#include +#include 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 +class TCPSocket : public IOAsioSocket { 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 +TCPSocket::TCPSocket(asio::ip::tcp::socket& socket) : + socket_ptr_(NULL), socket_(socket), isopen_(true) +{ +} + +// Constructor - create socket on the fly + +template +TCPSocket::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 +TCPSocket::~TCPSocket() +{ + delete socket_ptr_; +} + +// Open the socket. Throws an error on failure +// TODO: Make the open more resilient + +template bool +TCPSocket::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 void +TCPSocket::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(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 void +TCPSocket::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(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 void +TCPSocket::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 void +TCPSocket::close() { + if (isopen_ && socket_ptr_) { + socket_.close(); + isopen_ = false; + } +} + +} // namespace asiolink -} // namespace asiolink #endif // __TCP_SOCKET_H diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index 7580065f1f..6077acb50a 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -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) diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc new file mode 100644 index 0000000000..0026a1df10 --- /dev/null +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -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 +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 diff --git a/src/lib/asiolink/tests/io_socket_unittest.cc b/src/lib/asiolink/tests/io_socket_unittest.cc index a964319be6..6538550bc8 100644 --- a/src/lib/asiolink/tests/io_socket_unittest.cc +++ b/src/lib/asiolink/tests/io_socket_unittest.cc @@ -15,6 +15,8 @@ #include #include +#include + #include #include diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc index 6950c6e865..7332d29ffe 100644 --- a/src/lib/asiolink/tests/udp_socket_unittest.cc +++ b/src/lib/asiolink/tests/udp_socket_unittest.cc @@ -52,14 +52,12 @@ #include -#include #include #include #include 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 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::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()); diff --git a/src/lib/asiolink/udp_server.cc b/src/lib/asiolink/udp_server.cc index 876433a5fd..58186e9007 100644 --- a/src/lib/asiolink/udp_server.cc +++ b/src/lib/asiolink/udp_server.cc @@ -20,10 +20,10 @@ #include +#include #include -#include - #include +#include 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(*data_->socket_)); + data_->io_message_.reset(new IOMessage(data_->data_.get(), data_->bytes_, *data_->iosock_, *data_->peer_)); diff --git a/src/lib/asiolink/udp_socket.cc b/src/lib/asiolink/udp_socket.cc deleted file mode 100644 index d1bd9aaa8a..0000000000 --- a/src/lib/asiolink/udp_socket.cc +++ /dev/null @@ -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 -#include -#include // for some IPC/network system calls -#include -#include - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -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(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(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 diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index 9b1af8786f..200de255ee 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -19,9 +19,19 @@ #error "asio.hpp must be included before including this, see asiolink.h as to why" #endif +#include +#include +#include +#include // for some IPC/network system calls + #include -#include + +#include + +#include +#include #include +#include 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 +class UDPSocket : public IOAsioSocket { 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 +UDPSocket::UDPSocket(asio::ip::udp::socket& socket) : + socket_ptr_(NULL), socket_(socket), isopen_(true) +{ +} + +// Constructor - create socket on the fly + +template +UDPSocket::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 +UDPSocket::~UDPSocket() +{ + delete socket_ptr_; +} + +// Open the socket. Throws an error on failure +// TODO: Make the open more resilient + +template bool +UDPSocket::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 void +UDPSocket::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(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 void +UDPSocket::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(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 void +UDPSocket::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 void +UDPSocket::close() { + if (isopen_ && socket_ptr_) { + socket_.close(); + isopen_ = false; + } +} + +} // namespace asiolink + #endif // __UDP_SOCKET_H