From 8a18de79026a2620f82662d308c4f87e9a54faa3 Mon Sep 17 00:00:00 2001 From: Scott Mann Date: Thu, 27 Jan 2011 11:20:22 -0700 Subject: [PATCH 001/113] [trac499] Initial entrypoint logic for handling TC queries --- src/lib/asiolink/asiolink.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib/asiolink/asiolink.cc b/src/lib/asiolink/asiolink.cc index a87afa65c5..ef5d670f04 100644 --- a/src/lib/asiolink/asiolink.cc +++ b/src/lib/asiolink/asiolink.cc @@ -417,6 +417,11 @@ private: // returns true if we are done // returns false if we are not done bool handleRecursiveAnswer(const Message& incoming) { + //temporary code to grab TC enabled responses + if(incoming.getHeaderFlag(Message::HEADERFLAG_TC)) { + //TC (truncated) bit is set, which means we need to use TCP + // need to check if TCP conn already open (RFC 5966) + } if (incoming.getRRCount(Message::SECTION_ANSWER) > 0) { dlog("Got final result, copying answer."); copyAnswerMessage(incoming, answer_message_); From 74d131f92fbcd1fe21335a743dde205063b63d00 Mon Sep 17 00:00:00 2001 From: Scott Mann Date: Sun, 30 Jan 2011 13:38:01 -0700 Subject: [PATCH 002/113] [trac499] First pass at new file/header for abstract IOQuery --- src/lib/asiolink/Makefile.am | 1 + src/lib/asiolink/asiolink.cc | 1 + src/lib/asiolink/internal/ioquery.h | 100 +++++++++ .../internal/tests/udpdns_unittest.cc | 1 + src/lib/asiolink/internal/udpdns.h | 58 ------ src/lib/asiolink/ioquery.cc | 191 ++++++++++++++++++ src/lib/asiolink/udpdns.cc | 138 ------------- 7 files changed, 294 insertions(+), 196 deletions(-) create mode 100644 src/lib/asiolink/internal/ioquery.h create mode 100644 src/lib/asiolink/ioquery.cc diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index 26d9a6875c..b2affe18e9 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -20,6 +20,7 @@ libasiolink_la_SOURCES += ioendpoint.cc ioendpoint.h libasiolink_la_SOURCES += udpdns.cc internal/udpdns.h libasiolink_la_SOURCES += tcpdns.cc internal/tcpdns.h libasiolink_la_SOURCES += internal/coroutine.h +libasiolink_la_SOURCES += ioquery.cc internal/ioquery.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.cc b/src/lib/asiolink/asiolink.cc index ef5d670f04..5c18ff8652 100644 --- a/src/lib/asiolink/asiolink.cc +++ b/src/lib/asiolink/asiolink.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include diff --git a/src/lib/asiolink/internal/ioquery.h b/src/lib/asiolink/internal/ioquery.h new file mode 100644 index 0000000000..7c0a8a0826 --- /dev/null +++ b/src/lib/asiolink/internal/ioquery.h @@ -0,0 +1,100 @@ +// 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 __IOQUERY_H +#define __IOQUERY_H 1 + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + +// This file contains UDP-specific implementations of generic classes +// defined in asiolink.h. It is *not* intended to be part of the public +// API. + +namespace asiolink { +// +// Asynchronous UDP coroutine for upstream queries +// +class UDPQuery : public coroutine { +public: + // TODO Maybe this should be more generic than just for UDPQuery? + /// + /// \brief Result of the query + /// + /// This is related only to contacting the remote server. If the answer + ///indicates error, it is still counted as SUCCESS here, if it comes back. + /// + enum Result { + SUCCESS, + TIME_OUT, + STOPPED + }; + /// Abstract callback for the UDPQuery. + class Callback { + public: + virtual ~Callback() {} + + /// This will be called when the UDPQuery is completed + virtual void operator()(Result result) = 0; + }; + /// + /// \brief Constructor. + /// + /// It creates the query. + /// @param callback will be called when we terminate. It is your task to + /// delete it if allocated on heap. + ///@param timeout in ms. + /// + explicit UDPQuery(asio::io_service& io_service, + const isc::dns::Question& q, + const IOAddress& addr, uint16_t port, + isc::dns::OutputBufferPtr buffer, + Callback* callback, int timeout = -1); + void operator()(asio::error_code ec = asio::error_code(), + size_t length = 0); + /// Terminate the query. + void stop(Result reason = STOPPED); +private: + enum { MAX_LENGTH = 4096 }; + + /// + /// \short Private data + /// + /// They are not private because of stability of the + /// interface (this is private class anyway), but because this class + /// will be copyed often (it is used as a coroutine and passed as callback + /// to many async_*() functions) and we want keep the same data. Some of + /// the data is not copyable too. + /// + struct PrivateData; + boost::shared_ptr data_; +}; +} + + +#endif // __IOQUERY_H + +// Local Variables: +// mode: c++ +// End: diff --git a/src/lib/asiolink/internal/tests/udpdns_unittest.cc b/src/lib/asiolink/internal/tests/udpdns_unittest.cc index 099071c41a..0368511a80 100644 --- a/src/lib/asiolink/internal/tests/udpdns_unittest.cc +++ b/src/lib/asiolink/internal/tests/udpdns_unittest.cc @@ -20,6 +20,7 @@ #include #include +#include using namespace asio; using namespace isc::dns; diff --git a/src/lib/asiolink/internal/udpdns.h b/src/lib/asiolink/internal/udpdns.h index f29d5c8ec0..6c6a886422 100644 --- a/src/lib/asiolink/internal/udpdns.h +++ b/src/lib/asiolink/internal/udpdns.h @@ -234,64 +234,6 @@ private: boost::shared_ptr peer_; boost::shared_ptr iosock_; }; - -// -// Asynchronous UDP coroutine for upstream queries -// -class UDPQuery : public coroutine { -public: - // TODO Maybe this should be more generic than just for UDPQuery? - /// - /// \brief Result of the query - /// - /// This is related only to contacting the remote server. If the answer - ///indicates error, it is still counted as SUCCESS here, if it comes back. - /// - enum Result { - SUCCESS, - TIME_OUT, - STOPPED - }; - /// Abstract callback for the UDPQuery. - class Callback { - public: - virtual ~Callback() {} - - /// This will be called when the UDPQuery is completed - virtual void operator()(Result result) = 0; - }; - /// - /// \brief Constructor. - /// - /// It creates the query. - /// @param callback will be called when we terminate. It is your task to - /// delete it if allocated on heap. - ///@param timeout in ms. - /// - explicit UDPQuery(asio::io_service& io_service, - const isc::dns::Question& q, - const IOAddress& addr, uint16_t port, - isc::dns::OutputBufferPtr buffer, - Callback* callback, int timeout = -1); - void operator()(asio::error_code ec = asio::error_code(), - size_t length = 0); - /// Terminate the query. - void stop(Result reason = STOPPED); -private: - enum { MAX_LENGTH = 4096 }; - - /// - /// \short Private data - /// - /// They are not private because of stability of the - /// interface (this is private class anyway), but because this class - /// will be copyed often (it is used as a coroutine and passed as callback - /// to many async_*() functions) and we want keep the same data. Some of - /// the data is not copyable too. - /// - struct PrivateData; - boost::shared_ptr data_; -}; } diff --git a/src/lib/asiolink/ioquery.cc b/src/lib/asiolink/ioquery.cc new file mode 100644 index 0000000000..08b82f7b02 --- /dev/null +++ b/src/lib/asiolink/ioquery.cc @@ -0,0 +1,191 @@ +// 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 // 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 + +using namespace asio; +using asio::ip::udp; +using asio::ip::tcp; +using isc::log::dlog; + +using namespace std; +using namespace isc::dns; + +namespace asiolink { + +// Private UDPQuery data (see internal/udpdns.h for reasons) +struct UDPQuery::PrivateData { + // UDP Socket we send query to and expect reply from there + udp::socket socket; + // Where was the query sent + udp::endpoint remote; + // What we ask the server + Question question; + // We will store the answer here + OutputBufferPtr buffer; + OutputBufferPtr msgbuf; + // Temporary buffer for answer + boost::shared_array data; + // This will be called when the data arrive or timeouts + Callback* callback; + // Did we already stop operating (data arrived, we timed out, someone + // called stop). This can be so when we are cleaning up/there are + // still pointers to us. + bool stopped; + // Timer to measure timeouts. + deadline_timer timer; + // How many milliseconds are we willing to wait for answer? + int timeout; + + PrivateData(io_service& service, + const udp::socket::protocol_type& protocol, const Question &q, + OutputBufferPtr b, Callback *c) : + socket(service, protocol), + question(q), + buffer(b), + msgbuf(new OutputBuffer(512)), + callback(c), + stopped(false), + timer(service) + { } +}; + +/// The following functions implement the \c UDPQuery class. +/// +/// The constructor +UDPQuery::UDPQuery(io_service& io_service, + const Question& q, const IOAddress& addr, uint16_t port, + OutputBufferPtr buffer, Callback *callback, int timeout) : + data_(new PrivateData(io_service, + addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), q, buffer, + callback)) +{ + data_->remote = UDPEndpoint(addr, port).getASIOEndpoint(); + data_->timeout = timeout; +} + +/// The function operator is implemented with the "stackless coroutine" +/// pattern; see internal/coroutine.h for details. +void +UDPQuery::operator()(error_code ec, size_t length) { + if (ec || data_->stopped) { + return; + } + + CORO_REENTER (this) { + /// Generate the upstream query and render it to wire format + /// This is done in a different scope to allow inline variable + /// declarations. + { + Message msg(Message::RENDER); + + // XXX: replace with boost::random or some other suitable PRNG + msg.setQid(0); + msg.setOpcode(Opcode::QUERY()); + msg.setRcode(Rcode::NOERROR()); + msg.setHeaderFlag(Message::HEADERFLAG_RD); + msg.addQuestion(data_->question); + MessageRenderer renderer(*data_->msgbuf); + msg.toWire(renderer); + dlog("Sending " + msg.toText() + " to " + + data_->remote.address().to_string()); + } + + + // If we timeout, we stop, which will shutdown everything and + // cancel all other attempts to run inside the coroutine + if (data_->timeout != -1) { + data_->timer.expires_from_now(boost::posix_time::milliseconds( + data_->timeout)); + data_->timer.async_wait(boost::bind(&UDPQuery::stop, *this, + TIME_OUT)); + } + + // Begin an asynchronous send, and then yield. When the + // send completes, we will resume immediately after this point. + CORO_YIELD data_->socket.async_send_to(buffer(data_->msgbuf->getData(), + data_->msgbuf->getLength()), data_->remote, *this); + + /// Allocate space for the response. (XXX: This should be + /// optimized by maintaining a free list of pre-allocated blocks) + data_->data.reset(new char[MAX_LENGTH]); + + /// Begin an asynchronous receive, and yield. When the receive + /// completes, we will resume immediately after this point. + CORO_YIELD data_->socket.async_receive_from(buffer(data_->data.get(), + MAX_LENGTH), data_->remote, *this); + // The message is not rendered yet, so we can't print it easilly + dlog("Received response from " + data_->remote.address().to_string()); + + /// Copy the answer into the response buffer. (XXX: If the + /// OutputBuffer object were made to meet the requirements of + /// a MutableBufferSequence, then it could be written to directly + /// by async_recieve_from() and this additional copy step would + /// be unnecessary.) + data_->buffer->writeData(data_->data.get(), length); + + /// We are done + stop(SUCCESS); + } +} + +void +UDPQuery::stop(Result result) { + if (!data_->stopped) { + switch (result) { + case TIME_OUT: + dlog("Query timed out"); + break; + case STOPPED: + dlog("Query stopped"); + break; + default:; + } + data_->stopped = true; + data_->socket.cancel(); + data_->socket.close(); + data_->timer.cancel(); + if (data_->callback) { + (*data_->callback)(result); + } + } +} + +} diff --git a/src/lib/asiolink/udpdns.cc b/src/lib/asiolink/udpdns.cc index 5641802ab7..adb07b28f1 100644 --- a/src/lib/asiolink/udpdns.cc +++ b/src/lib/asiolink/udpdns.cc @@ -178,142 +178,4 @@ UDPServer::resume(const bool done) { io_.post(*this); } -// Private UDPQuery data (see internal/udpdns.h for reasons) -struct UDPQuery::PrivateData { - // Socket we send query to and expect reply from there - udp::socket socket; - // Where was the query sent - udp::endpoint remote; - // What we ask the server - Question question; - // We will store the answer here - OutputBufferPtr buffer; - OutputBufferPtr msgbuf; - // Temporary buffer for answer - boost::shared_array data; - // This will be called when the data arrive or timeouts - Callback* callback; - // Did we already stop operating (data arrived, we timed out, someone - // called stop). This can be so when we are cleaning up/there are - // still pointers to us. - bool stopped; - // Timer to measure timeouts. - deadline_timer timer; - // How many milliseconds are we willing to wait for answer? - int timeout; - - PrivateData(io_service& service, - const udp::socket::protocol_type& protocol, const Question &q, - OutputBufferPtr b, Callback *c) : - socket(service, protocol), - question(q), - buffer(b), - msgbuf(new OutputBuffer(512)), - callback(c), - stopped(false), - timer(service) - { } -}; - -/// The following functions implement the \c UDPQuery class. -/// -/// The constructor -UDPQuery::UDPQuery(io_service& io_service, - const Question& q, const IOAddress& addr, uint16_t port, - OutputBufferPtr buffer, Callback *callback, int timeout) : - data_(new PrivateData(io_service, - addr.getFamily() == AF_INET ? udp::v4() : udp::v6(), q, buffer, - callback)) -{ - data_->remote = UDPEndpoint(addr, port).getASIOEndpoint(); - data_->timeout = timeout; -} - -/// The function operator is implemented with the "stackless coroutine" -/// pattern; see internal/coroutine.h for details. -void -UDPQuery::operator()(error_code ec, size_t length) { - if (ec || data_->stopped) { - return; - } - - CORO_REENTER (this) { - /// Generate the upstream query and render it to wire format - /// This is done in a different scope to allow inline variable - /// declarations. - { - Message msg(Message::RENDER); - - // XXX: replace with boost::random or some other suitable PRNG - msg.setQid(0); - msg.setOpcode(Opcode::QUERY()); - msg.setRcode(Rcode::NOERROR()); - msg.setHeaderFlag(Message::HEADERFLAG_RD); - msg.addQuestion(data_->question); - MessageRenderer renderer(*data_->msgbuf); - msg.toWire(renderer); - dlog("Sending " + msg.toText() + " to " + - data_->remote.address().to_string()); - } - - - // If we timeout, we stop, which will shutdown everything and - // cancel all other attempts to run inside the coroutine - if (data_->timeout != -1) { - data_->timer.expires_from_now(boost::posix_time::milliseconds( - data_->timeout)); - data_->timer.async_wait(boost::bind(&UDPQuery::stop, *this, - TIME_OUT)); - } - - // Begin an asynchronous send, and then yield. When the - // send completes, we will resume immediately after this point. - CORO_YIELD data_->socket.async_send_to(buffer(data_->msgbuf->getData(), - data_->msgbuf->getLength()), data_->remote, *this); - - /// Allocate space for the response. (XXX: This should be - /// optimized by maintaining a free list of pre-allocated blocks) - data_->data.reset(new char[MAX_LENGTH]); - - /// Begin an asynchronous receive, and yield. When the receive - /// completes, we will resume immediately after this point. - CORO_YIELD data_->socket.async_receive_from(buffer(data_->data.get(), - MAX_LENGTH), data_->remote, *this); - // The message is not rendered yet, so we can't print it easilly - dlog("Received response from " + data_->remote.address().to_string()); - - /// Copy the answer into the response buffer. (XXX: If the - /// OutputBuffer object were made to meet the requirements of - /// a MutableBufferSequence, then it could be written to directly - /// by async_recieve_from() and this additional copy step would - /// be unnecessary.) - data_->buffer->writeData(data_->data.get(), length); - - /// We are done - stop(SUCCESS); - } -} - -void -UDPQuery::stop(Result result) { - if (!data_->stopped) { - switch (result) { - case TIME_OUT: - dlog("Query timed out"); - break; - case STOPPED: - dlog("Query stopped"); - break; - default:; - } - data_->stopped = true; - data_->socket.cancel(); - data_->socket.close(); - data_->timer.cancel(); - if (data_->callback) { - (*data_->callback)(result); - } - } -} - } From e321d8b344152b558cb42856deb5e8798f153d1b Mon Sep 17 00:00:00 2001 From: Scott Mann Date: Mon, 31 Jan 2011 04:46:07 -0700 Subject: [PATCH 003/113] [trac554]Initial name change from UDPQuery to IOFetch --- src/lib/asiolink/Makefile.am | 2 +- src/lib/asiolink/asiolink.cc | 2 +- src/lib/asiolink/internal/{ioquery.h => iofetch.h} | 0 src/lib/asiolink/internal/tests/udpdns_unittest.cc | 2 +- src/lib/asiolink/{ioquery.cc => iofetch.cc} | 6 +++++- 5 files changed, 8 insertions(+), 4 deletions(-) rename src/lib/asiolink/internal/{ioquery.h => iofetch.h} (100%) rename src/lib/asiolink/{ioquery.cc => iofetch.cc} (98%) diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index b2affe18e9..c8790394b5 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -20,7 +20,7 @@ libasiolink_la_SOURCES += ioendpoint.cc ioendpoint.h libasiolink_la_SOURCES += udpdns.cc internal/udpdns.h libasiolink_la_SOURCES += tcpdns.cc internal/tcpdns.h libasiolink_la_SOURCES += internal/coroutine.h -libasiolink_la_SOURCES += ioquery.cc internal/ioquery.h +libasiolink_la_SOURCES += iofetch.cc internal/iofetch.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.cc b/src/lib/asiolink/asiolink.cc index 5c18ff8652..15235f3239 100644 --- a/src/lib/asiolink/asiolink.cc +++ b/src/lib/asiolink/asiolink.cc @@ -35,7 +35,7 @@ #include #include #include -#include +#include #include diff --git a/src/lib/asiolink/internal/ioquery.h b/src/lib/asiolink/internal/iofetch.h similarity index 100% rename from src/lib/asiolink/internal/ioquery.h rename to src/lib/asiolink/internal/iofetch.h diff --git a/src/lib/asiolink/internal/tests/udpdns_unittest.cc b/src/lib/asiolink/internal/tests/udpdns_unittest.cc index 0368511a80..1e36e4a188 100644 --- a/src/lib/asiolink/internal/tests/udpdns_unittest.cc +++ b/src/lib/asiolink/internal/tests/udpdns_unittest.cc @@ -20,7 +20,7 @@ #include #include -#include +#include using namespace asio; using namespace isc::dns; diff --git a/src/lib/asiolink/ioquery.cc b/src/lib/asiolink/iofetch.cc similarity index 98% rename from src/lib/asiolink/ioquery.cc rename to src/lib/asiolink/iofetch.cc index 08b82f7b02..668a980af6 100644 --- a/src/lib/asiolink/ioquery.cc +++ b/src/lib/asiolink/iofetch.cc @@ -38,7 +38,7 @@ #include #include #include -#include +#include using namespace asio; using asio::ip::udp; @@ -56,6 +56,10 @@ struct UDPQuery::PrivateData { udp::socket socket; // Where was the query sent udp::endpoint remote; + // TCP Socket + //tcp::socket tsocket; + // tcp endpoint + //tcp::endpoint tremote; // What we ask the server Question question; // We will store the answer here From af01e66fde8c0055319c47d775b0af1408a3e18a Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Thu, 24 Feb 2011 08:58:58 +0000 Subject: [PATCH 004/113] [trac499] Add logging of errors to IOFetch --- src/lib/asiolink/Makefile.am | 24 +++++++------ src/lib/asiolink/io_fetch.cc | 64 ++++++++++++++++++++++++++++++++--- src/lib/asiolink/io_fetch.h | 37 ++++++++++++++++---- src/lib/asiolink/udp_socket.h | 11 ++++-- src/lib/log/Makefile.am | 2 ++ 5 files changed, 116 insertions(+), 22 deletions(-) diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index b3968f0661..b7bf530c24 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -13,28 +13,32 @@ CLEANFILES = *.gcno *.gcda # which would make the build fail with -Werror (our default setting). lib_LTLIBRARIES = libasiolink.la libasiolink_la_SOURCES = asiolink.h +libasiolink_la_SOURCES += asiodef.cc asiodef.h libasiolink_la_SOURCES += dns_answer.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 += dns_service.cc dns_service.h libasiolink_la_SOURCES += dummy_io_cb.h -libasiolink_la_SOURCES += interval_timer.h interval_timer.cc -libasiolink_la_SOURCES += io_address.h io_address.cc +libasiolink_la_SOURCES += interval_timer.cc interval_timer.h +libasiolink_la_SOURCES += io_address.cc io_address.h libasiolink_la_SOURCES += io_asio_socket.h -libasiolink_la_SOURCES += io_endpoint.h io_endpoint.cc +libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h libasiolink_la_SOURCES += io_error.h -libasiolink_la_SOURCES += io_fetch.h io_fetch.cc +libasiolink_la_SOURCES += io_fetch.cc io_fetch.h libasiolink_la_SOURCES += io_message.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 += io_service.cc io_service.h +libasiolink_la_SOURCES += io_socket.cc io_socket.h +libasiolink_la_SOURCES += recursive_query.cc recursive_query.h 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_server.cc tcp_server.h libasiolink_la_SOURCES += tcp_socket.h libasiolink_la_SOURCES += udp_endpoint.h -libasiolink_la_SOURCES += udp_server.h udp_server.cc +libasiolink_la_SOURCES += udp_server.cc udp_server.h libasiolink_la_SOURCES += udp_socket.h + +EXTRA_DIST = asiodef.msg + # Note: the ordering matters: -Wno-... must follow -Wextra (defined in # B10_CXXFLAGS) libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index d1f722cf86..307ae8f498 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -25,8 +25,12 @@ #include #include #include +#include #include +#include +#include +#include #include using namespace asio; @@ -36,6 +40,10 @@ using namespace std; namespace asiolink { +/// Use the ASIO logger + +isc::log::Logger logger("asio"); + /// IOFetch Constructor - just initialize the private data IOFetch::IOFetch(int protocol, IOService& service, @@ -52,7 +60,10 @@ IOFetch::IOFetch(int protocol, IOService& service, void IOFetch::operator()(error_code ec, size_t length) { - if (ec || data_->stopped) { + if (data_->stopped) { + return; + } else if (ec) { + logIOFailure(ec); return; } @@ -93,11 +104,13 @@ 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. if (data_->socket->open(data_->remote.get(), *this)) { + data_->origin = OPEN; CORO_YIELD; } // Begin an asynchronous send, and then yield. When the send completes // send completes, we will resume immediately after this point. + data_->origin = SEND; CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(), data_->msgbuf->getLength(), data_->remote.get(), *this); @@ -108,6 +121,7 @@ IOFetch::operator()(error_code ec, size_t length) { // 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. + data_->origin = RECEIVE; do { CORO_YIELD data_->socket->asyncReceive(data_->data.get(), static_cast(MAX_LENGTH), data_->cumulative, @@ -156,20 +170,30 @@ IOFetch::stop(Result result) { // variable should be done inside a mutex (and the stopped_ variable // declared as "volatile"). // + // Although Logger::debug checks the debug flag internally, doing it + // in below avoids the overhead of a string conversion in the common + // case when debug is not enabled. + // // TODO: Update testing of stopped_ if threads are used. data_->stopped = true; switch (result) { case TIME_OUT: - dlog("Query timed out"); + if (logger.isDebugEnabled(1)) { + logger.debug(1, ASIO_RECVTMO, + data_->remote->getAddress().toText().c_str()); + } break; case STOPPED: - dlog("Query stopped"); + if (logger.isDebugEnabled(50)) { + logger.debug(50, ASIO_FETCOMP, + data_->remote->getAddress().toText().c_str()); + } break; default: - ; + logger.error(ASIO_UNKRESULT, static_cast(result)); } // Stop requested, cancel and I/O's on the socket and shut it down, @@ -189,5 +213,37 @@ IOFetch::stop(Result result) { } } +// Log an error - called on I/O failure + +void IOFetch::logIOFailure(asio::error_code& ec) { + + // Get information that will be in all messages + static const char* PROTOCOL[2] = {"TCP", "UDP"}; + const char* prot = (data_->remote->getProtocol() == IPPROTO_TCP) ? + PROTOCOL[0] : PROTOCOL[1]; + + int errcode = ec.value(); + + std::string str_address = data_->remote->getAddress().toText(); + const char* address = str_address.c_str(); + + switch (data_->origin) { + case OPEN: + logger.error(ASIO_OPENSOCK, errcode, prot, address); + break; + + case SEND: + logger.error(ASIO_SENDSOCK, errcode, prot, address); + break; + + case RECEIVE: + logger.error(ASIO_RECVSOCK, errcode, prot, address); + break; + + default: + logger.error(ASIO_UNKORIGIN, errcode, prot, address); + } +} + } // namespace asiolink diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index 8158c6c05c..957831d67f 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -51,6 +51,17 @@ namespace asiolink { class IOFetch : public coroutine { public: + /// \brief Origin of Asynchronous I/O Call + /// + /// Indicates what initiated an asynchronous I/O call and used in deciding + /// what error message to output if the I/O fails. + enum Origin { + NONE = 0, ///< No asynchronous call outstanding + OPEN = 1, + SEND = 2, + RECEIVE = 3, + CLOSE = 4 + }; /// \brief Result of Upstream Fetch /// @@ -95,7 +106,12 @@ public: virtual ~Callback() {} - /// \brief Callback method called when the fetch completes + /// \brief Callback method called when the fetch completes /// \brief Origin of Asynchronous I/O Call + /// + + // The next enum is a "trick" to allow constants to be defined in a class + // declaration. + /// /// \brief result Result of the fetch virtual void operator()(Result result) = 0; @@ -127,12 +143,13 @@ public: bool stopped; ///< Have we stopped running? asio::deadline_timer timer; ///< Timer to measure timeouts int timeout; ///< Timeout in ms + Origin origin; ///< Origin of last asynchronous I/O /// \brief Constructor /// /// Just fills in the data members of the IOFetchData structure /// - /// \param protocol either IPPROTO_UDP or IPPROTO_TCP + /// \param proto 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. @@ -146,18 +163,18 @@ public: /// \param wait Timeout for the fetch (in ms). /// /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554) - IOFetchData(int protocol, IOService& service, + IOFetchData(int proto, 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) ? + socket((proto == IPPROTO_UDP) ? static_cast*>( new UDPSocket(service)) : static_cast*>( new TCPSocket(service)) ), - remote((protocol == IPPROTO_UDP) ? + remote((proto == IPPROTO_UDP) ? static_cast(new UDPEndpoint(address, port)) : static_cast(new TCPEndpoint(address, port)) ), @@ -169,7 +186,8 @@ public: cumulative(0), stopped(false), timer(service.get_io_service()), - timeout(wait) + timeout(wait), + origin(NONE) {} }; @@ -217,6 +235,13 @@ public: void stop(Result reason = STOPPED); private: + /// \brief Log I/O Failure + /// + /// Records an I/O failure to the log file + /// + /// \param ec ASIO error code + void logIOFailure(asio::error_code& ec); + boost::shared_ptr data_; ///< Private data }; diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index bb94ad56f2..8cb2232cfa 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -70,8 +70,15 @@ public: /// \brief Destructor virtual ~UDPSocket(); - virtual int getNative() const { return (socket_.native()); } - virtual int getProtocol() const { return (IPPROTO_UDP); } + /// \brief Return file descriptor of underlying socket + virtual int getNative() const { + return (socket_.native()); + } + + /// \brief Return protocol of socket + virtual int getProtocol() const { + return (IPPROTO_UDP); + } /// \brief Open Socket /// diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am index 416fd06d0e..584ae5a9ca 100644 --- a/src/lib/log/Makefile.am +++ b/src/lib/log/Makefile.am @@ -24,6 +24,8 @@ liblog_la_SOURCES += message_types.h liblog_la_SOURCES += root_logger_name.cc root_logger_name.h liblog_la_SOURCES += strutil.h strutil.cc +EXTRA_DIST = messagedef.mes + # Note: the ordering matters: -Wno-... must follow -Wextra (defined in # B10_CXXFLAGS) liblog_la_CXXFLAGS = $(AM_CXXFLAGS) From 24ef7a16425e696fbb794605a89ff8d7bdd7172c Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 24 Feb 2011 12:49:56 +0100 Subject: [PATCH 005/113] [trac607] quick fix for trac607 (see comment there) --- src/bin/resolver/resolver.cc | 34 +++++++++++++++------ src/bin/resolver/tests/resolver_unittest.cc | 21 +++++++++++++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc index 95a417d285..11b51b78db 100644 --- a/src/bin/resolver/resolver.cc +++ b/src/bin/resolver/resolver.cc @@ -185,8 +185,8 @@ public: // TODO: REMOVE, USE isc::resolve::MakeErrorMessage? void -makeErrorMessage(MessagePtr message, OutputBufferPtr buffer, - const Rcode& rcode) +makeErrorMessage(MessagePtr message, MessagePtr answer_message, + OutputBufferPtr buffer, const Rcode& rcode) { // extract the parameters that should be kept. // XXX: with the current implementation, it's not easy to set EDNS0 @@ -197,6 +197,12 @@ makeErrorMessage(MessagePtr message, OutputBufferPtr buffer, const Opcode& opcode = message->getOpcode(); vector questions; + // answer_message is actually ignored right now, + // see the comment in #607 + answer_message->setRcode(rcode); + answer_message->setOpcode(opcode); + answer_message->setQid(qid); + // If this is an error to a query or notify, we should also copy the // question section. if (opcode == Opcode::QUERY() || opcode == Opcode::NOTIFY()) { @@ -385,12 +391,14 @@ Resolver::processMessage(const IOMessage& io_message, } catch (const DNSProtocolError& error) { dlog(string("returning ") + error.getRcode().toText() + ": " + error.what()); - makeErrorMessage(query_message, buffer, error.getRcode()); + makeErrorMessage(query_message, answer_message, + buffer, error.getRcode()); server->resume(true); return; } catch (const Exception& ex) { dlog(string("returning SERVFAIL: ") + ex.what()); - makeErrorMessage(query_message, buffer, Rcode::SERVFAIL()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::SERVFAIL()); server->resume(true); return; } // other exceptions will be handled at a higher layer. @@ -400,28 +408,34 @@ Resolver::processMessage(const IOMessage& io_message, // Perform further protocol-level validation. bool sendAnswer = true; if (query_message->getOpcode() == Opcode::NOTIFY()) { - makeErrorMessage(query_message, buffer, Rcode::NOTAUTH()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::NOTAUTH()); dlog("Notify arrived, but we are not authoritative"); } else if (query_message->getOpcode() != Opcode::QUERY()) { dlog("Unsupported opcode (got: " + query_message->getOpcode().toText() + ", expected: " + Opcode::QUERY().toText()); - makeErrorMessage(query_message, buffer, Rcode::NOTIMP()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::NOTIMP()); } else if (query_message->getRRCount(Message::SECTION_QUESTION) != 1) { dlog("The query contained " + boost::lexical_cast(query_message->getRRCount( Message::SECTION_QUESTION) + " questions, exactly one expected")); - makeErrorMessage(query_message, buffer, Rcode::FORMERR()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::FORMERR()); } else { ConstQuestionPtr question = *query_message->beginQuestion(); const RRType &qtype = question->getType(); if (qtype == RRType::AXFR()) { if (io_message.getSocket().getProtocol() == IPPROTO_UDP) { - makeErrorMessage(query_message, buffer, Rcode::FORMERR()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::FORMERR()); } else { - makeErrorMessage(query_message, buffer, Rcode::NOTIMP()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::NOTIMP()); } } else if (qtype == RRType::IXFR()) { - makeErrorMessage(query_message, buffer, Rcode::NOTIMP()); + makeErrorMessage(query_message, answer_message, + buffer, Rcode::NOTIMP()); } else { // The RecursiveQuery object will post the "resume" event to the // DNSServer when an answer arrives, so we don't have to do it now. diff --git a/src/bin/resolver/tests/resolver_unittest.cc b/src/bin/resolver/tests/resolver_unittest.cc index a4f11f5d65..97edf1237c 100644 --- a/src/bin/resolver/tests/resolver_unittest.cc +++ b/src/bin/resolver/tests/resolver_unittest.cc @@ -96,6 +96,27 @@ TEST_F(ResolverTest, AXFRFail) { QR_FLAG, 1, 0, 0, 0); } +TEST_F(ResolverTest, IXFRFail) { + UnitTestUtil::createRequestMessage(request_message, opcode, default_qid, + Name("example.com"), RRClass::IN(), + RRType::IXFR()); + createRequestPacket(request_message, IPPROTO_TCP); + // IXFR is not implemented and should always send NOTIMP. + server.processMessage(*io_message, + parse_message, + response_message, + response_obuffer, + &dnsserv); + EXPECT_TRUE(dnsserv.hasAnswer()); + // the second check is what we'll need in the end (with the values + // from the first one), but right now the first one is for what + // will actually be returned to the client + headerCheck(*parse_message, default_qid, Rcode::NOTIMP(), opcode.getCode(), + QR_FLAG, 1, 0, 0, 0); + headerCheck(*response_message, default_qid, Rcode::NOTIMP(), opcode.getCode(), + 0, 0, 0, 0, 0); +} + TEST_F(ResolverTest, notifyFail) { // Notify should always return NOTAUTH request_message.clear(Message::RENDER); From 7c419681601df9c3a453f0e46756dd751344b1a8 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Thu, 24 Feb 2011 12:02:59 +0000 Subject: [PATCH 006/113] [trac499] Mischellaneous minor preparator changes * Extend logging in IOFetch (and add message definitions) * Update buffer size setting for UDP socket * remove spaces at end of lines in other files --- src/lib/asiolink/Makefile.am | 3 +- src/lib/asiolink/asiodef.cc | 37 ++++++++++++++ src/lib/asiolink/asiodef.h | 21 ++++++++ src/lib/asiolink/asiodef.msg | 56 +++++++++++++++++++++ src/lib/asiolink/asiolink.h | 6 +-- src/lib/asiolink/dns_server.h | 12 ++--- src/lib/asiolink/dns_service.h | 2 +- src/lib/asiolink/interval_timer.h | 2 +- src/lib/asiolink/io_address.h | 6 +-- src/lib/asiolink/io_asio_socket.h | 13 ++++- src/lib/asiolink/io_endpoint.h | 6 +-- src/lib/asiolink/io_fetch.cc | 27 +++++++--- src/lib/asiolink/io_fetch.h | 20 +++++--- src/lib/asiolink/io_message.h | 6 +-- src/lib/asiolink/recursive_query.cc | 4 +- src/lib/asiolink/tests/Makefile.am | 7 +-- src/lib/asiolink/tests/io_fetch_unittest.cc | 2 +- src/lib/asiolink/udp_socket.h | 24 ++++++--- 18 files changed, 197 insertions(+), 57 deletions(-) create mode 100644 src/lib/asiolink/asiodef.cc create mode 100644 src/lib/asiolink/asiodef.h create mode 100644 src/lib/asiolink/asiodef.msg diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index b7bf530c24..71d31f9a6c 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -50,7 +50,8 @@ if USE_CLANGPP libasiolink_la_CXXFLAGS += -Wno-error endif libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS) -libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la +libasiolink_la_LIBADD = libasiolink_la_LIBADD += $(top_builddir)/src/lib/resolve/libresolve.la libasiolink_la_LIBADD += $(top_builddir)/src/lib/cache/libcache.la libasiolink_la_LIBADD += $(top_builddir)/src/lib/nsas/libnsas.la +libasiolink_la_LIBADD += $(top_builddir)/src/lib/log/liblog.la diff --git a/src/lib/asiolink/asiodef.cc b/src/lib/asiolink/asiodef.cc new file mode 100644 index 0000000000..5f7a02a0a4 --- /dev/null +++ b/src/lib/asiolink/asiodef.cc @@ -0,0 +1,37 @@ +// File created from asiodef.msg on Thu Feb 24 11:52:42 2011 + +#include +#include +#include + +namespace asiolink { + +extern const isc::log::MessageID ASIO_FETCHCOMP = "FETCHCOMP"; +extern const isc::log::MessageID ASIO_FETCHSTOP = "FETCHSTOP"; +extern const isc::log::MessageID ASIO_OPENSOCK = "OPENSOCK"; +extern const isc::log::MessageID ASIO_RECVSOCK = "RECVSOCK"; +extern const isc::log::MessageID ASIO_RECVTMO = "RECVTMO"; +extern const isc::log::MessageID ASIO_SENDSOCK = "SENDSOCK"; +extern const isc::log::MessageID ASIO_UNKORIGIN = "UNKORIGIN"; +extern const isc::log::MessageID ASIO_UNKRESULT = "UNKRESULT"; + +} // namespace asiolink + +namespace { + +const char* values[] = { + "FETCHCOMP", "upstream fetch to %s has now completed", + "FETCHSTOP", "upstream fetch to %s has been stopped", + "OPENSOCK", "error %d opening %s socket to %s", + "RECVSOCK", "error %d reading data from %s via a %s socket", + "RECVTMO", "receive timeout while waiting for data from %s", + "SENDSOCK", "error %d sending data to %s via a %s socket", + "UNKORIGIN", "unknown origin for ASIO error code %d (protocol: %s, address %s)", + "UNKRESULT", "unknown result (%d) when IOFetch::stop() was executed for I/O to %s", + NULL +}; + +const isc::log::MessageInitializer initializer(values); + +} // Anonymous namespace + diff --git a/src/lib/asiolink/asiodef.h b/src/lib/asiolink/asiodef.h new file mode 100644 index 0000000000..c36026ec7d --- /dev/null +++ b/src/lib/asiolink/asiodef.h @@ -0,0 +1,21 @@ +// File created from asiodef.msg on Thu Feb 24 11:52:42 2011 + +#ifndef __ASIODEF_H +#define __ASIODEF_H + +#include + +namespace asiolink { + +extern const isc::log::MessageID ASIO_FETCHCOMP; +extern const isc::log::MessageID ASIO_FETCHSTOP; +extern const isc::log::MessageID ASIO_OPENSOCK; +extern const isc::log::MessageID ASIO_RECVSOCK; +extern const isc::log::MessageID ASIO_RECVTMO; +extern const isc::log::MessageID ASIO_SENDSOCK; +extern const isc::log::MessageID ASIO_UNKORIGIN; +extern const isc::log::MessageID ASIO_UNKRESULT; + +} // namespace asiolink + +#endif // __ASIODEF_H diff --git a/src/lib/asiolink/asiodef.msg b/src/lib/asiolink/asiodef.msg new file mode 100644 index 0000000000..2e9de05bbb --- /dev/null +++ b/src/lib/asiolink/asiodef.msg @@ -0,0 +1,56 @@ +# 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. + +$PREFIX ASIO_ +$NAMESPACE asiolink + +FETCHCOMP upstream fetch to %s has now completed ++ A debug message, this records the the upstream fetch (a query made by the ++ resolver on behalf of its client) to the specified address has completed. + +FETCHSTOP upstream fetch to %s has been stopped ++ An external component has requested the halting of an upstream fetch. This ++ is an allowed operation, and the message should only appear if debug is ++ enabled. + +OPENSOCK error %d opening %s socket to %s ++ The asynchronous I/O code encountered an error when trying to open a socket ++ of the specified protocol in order to send a message to the target address. ++ The the number of the system error that cause the problem is given in the ++ message. + +RECVSOCK error %d reading data from %s via a %s socket ++ The asynchronous I/O code encountered an error when trying read data from ++ the specified address on the given protocol. The the number of the system ++ error that cause the problem is given in the message. + +SENDSOCK error %d sending data to %s via a %s socket ++ The asynchronous I/O code encountered an error when trying send data to ++ the specified address on the given protocol. The the number of the system ++ error that cause the problem is given in the message. + +RECVTMO receive timeout while waiting for data from %s ++ An upstream fetch from the specified address timed out. This may happen for ++ any number of reasons and is most probably a problem at the remote server ++ or a problem on the network. The message will only appear if debug is ++ enabled. + +UNKORIGIN unknown origin for ASIO error code %d (protocol: %s, address %s) ++ This message should not appear and indicates an internal error if it does. ++ Please enter a bug report. + +UNKRESULT unknown result (%d) when IOFetch::stop() was executed for I/O to %s ++ The termination method of the resolver's upstream fetch class was called with ++ an unknown result code (which is given in the message). This message should ++ not appear and may indicate an internal error. Please enter a bug report. diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h index 03951ae9df..86c55dc758 100644 --- a/src/lib/asiolink/asiolink.h +++ b/src/lib/asiolink/asiolink.h @@ -84,8 +84,4 @@ /// the placeholder of callback handlers: /// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html -#endif // __ASIOLINK_H - -// Local Variables: -// mode: c++ -// End: +#endif // __ASIOLINK_H \ No newline at end of file diff --git a/src/lib/asiolink/dns_server.h b/src/lib/asiolink/dns_server.h index 65452755fb..3d79ebd004 100644 --- a/src/lib/asiolink/dns_server.h +++ b/src/lib/asiolink/dns_server.h @@ -21,7 +21,7 @@ namespace asiolink { /// \brief The \c DNSServer class is a wrapper (and base class) for /// classes which provide DNS server functionality. -/// +/// /// The classes derived from this one, \c TCPServer and \c UDPServer, /// act as the interface layer between clients sending queries, and /// functions defined elsewhere that provide answers to those queries. @@ -42,10 +42,10 @@ namespace asiolink { /// when "forking", and that instances will be posted as ASIO handler /// objects, which are always copied. /// -/// Because these objects are frequently copied, it is recommended +/// Because these objects are frequently copied, it is recommended /// that derived classes be kept small to reduce copy overhead. class DNSServer { -protected: +protected: /// /// \name Constructors and destructors /// @@ -66,7 +66,7 @@ public: /// the ones in the derived class. This makes it possible to pass /// instances of derived classes as references to this base class /// without losing access to derived class data. - /// + /// //@{ /// \brief The funtion operator virtual void operator()(asio::error_code ec = asio::error_code(), @@ -75,7 +75,7 @@ public: (*self_)(ec, length); } - /// \brief Resume processing of the server coroutine after an + /// \brief Resume processing of the server coroutine after an /// asynchronous call (e.g., to the DNS Lookup provider) has completed. /// /// \param done If true, this signals the system there is an answer @@ -84,7 +84,7 @@ public: /// \brief Indicate whether the server is able to send an answer /// to a query. - /// + /// /// This is presently used only for testing purposes. virtual bool hasAnswer() { return (self_->hasAnswer()); } diff --git a/src/lib/asiolink/dns_service.h b/src/lib/asiolink/dns_service.h index 84aa5fbfd8..e1583c04a5 100644 --- a/src/lib/asiolink/dns_service.h +++ b/src/lib/asiolink/dns_service.h @@ -26,13 +26,13 @@ class DNSLookup; class DNSAnswer; class DNSServiceImpl; +/// \brief Handle DNS Queries /// /// DNSService is the service that handles DNS queries and answers with /// a given IOService. This class is mainly intended to hold all the /// logic that is shared between the authoritative and the recursive /// server implementations. As such, it handles asio, including config /// updates (through the 'Checkinprovider'), and listening sockets. -/// class DNSService { /// /// \name Constructors and Destructor diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h index d805cd7c39..6c4332764d 100644 --- a/src/lib/asiolink/interval_timer.h +++ b/src/lib/asiolink/interval_timer.h @@ -37,7 +37,7 @@ struct IntervalTimerImpl; /// The function calls the call back function set by \c setup() and updates /// the timer to expire in (now + interval) milliseconds. /// The type of call back function is \c void(void). -/// +/// /// The call back function will not be called if the instance of this class is /// destroyed before the timer is expired. /// diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h index 0d2787f95f..07caafe524 100644 --- a/src/lib/asiolink/io_address.h +++ b/src/lib/asiolink/io_address.h @@ -120,8 +120,4 @@ private: }; } // asiolink -#endif // __IO_ADDRESS_H - -// Local Variables: -// mode: c++ -// End: +#endif // __IO_ADDRESS_H \ No newline at end of file diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index eae9b32509..d485610bc2 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -41,7 +41,14 @@ public: IOError(file, line, what) {} }; - +/// \brief Socket not open +/// +/// Thrown if attempt to change socket options fails. +class SocketSetError : public IOError { +public: + SocketSetError(const char* file, size_t line, const char* what) : + IOError(file, line, what) {} +}; /// Forward declaration of an IOEndpoint class IOEndpoint; @@ -276,7 +283,9 @@ public: /// \param cumulative Unused /// \param endpoint Unused /// \param callback Unused - virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { } + virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { + } + /// \brief Checks if the data received is complete. /// /// \param data Unused diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h index 62b9e47942..20bf0f84f4 100644 --- a/src/lib/asiolink/io_endpoint.h +++ b/src/lib/asiolink/io_endpoint.h @@ -115,8 +115,4 @@ public: }; } // asiolink -#endif // __IO_ENDPOINT_H - -// Local Variables: -// mode: c++ -// End: +#endif // __IO_ENDPOINT_H \ No newline at end of file diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 307ae8f498..7fce6074ed 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -46,7 +46,7 @@ isc::log::Logger logger("asio"); /// IOFetch Constructor - just initialize the private data -IOFetch::IOFetch(int protocol, IOService& service, +IOFetch::IOFetch(Protocol protocol, IOService& service, const isc::dns::Question& question, const IOAddress& address, uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb, int wait) : @@ -140,6 +140,7 @@ IOFetch::operator()(error_code ec, size_t length) { data_->buffer->writeData(data_->data.get(), length); // Finished with this socket, so close it. + data_->origin = CLOSE; data_->socket->close(); /// We are done @@ -170,9 +171,14 @@ IOFetch::stop(Result result) { // variable should be done inside a mutex (and the stopped_ variable // declared as "volatile"). // + // The numeric arguments indicate the debug level, with the lower + // numbers indicating the most important information. The relative + // values are somewhat arbitrary. + // // Although Logger::debug checks the debug flag internally, doing it - // in below avoids the overhead of a string conversion in the common - // case when debug is not enabled. + // below before calling Logger::debug avoids the overhead of a string + // conversion in the common paths and in the common case when debug is + // not enabled. // // TODO: Update testing of stopped_ if threads are used. data_->stopped = true; @@ -185,15 +191,24 @@ IOFetch::stop(Result result) { } break; - case STOPPED: + case SUCCESS: if (logger.isDebugEnabled(50)) { - logger.debug(50, ASIO_FETCOMP, + logger.debug(50, ASIO_FETCHCOMP, data_->remote->getAddress().toText().c_str()); } break; + case STOPPED: + // Fetch has been stopped for some other reason. This is + // allowed but as it is unusual it is logged, but with a lower + // debug level than a timeout (which is totally normal). + logger.debug(10, ASIO_FETCHSTOP, + data_->remote->getAddress().toText().c_str()); + break; + default: - logger.error(ASIO_UNKRESULT, static_cast(result)); + logger.error(ASIO_UNKRESULT, static_cast(result), + data_->remote->getAddress().toText().c_str()); } // Stop requested, cancel and I/O's on the socket and shut it down, diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index 957831d67f..369e057156 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -51,6 +51,12 @@ namespace asiolink { class IOFetch : public coroutine { public: + /// \brief Protocol to use on the fetch + enum Protocol { + UDP = 0, + TCP = 1 + }; + /// \brief Origin of Asynchronous I/O Call /// /// Indicates what initiated an asynchronous I/O call and used in deciding @@ -149,7 +155,7 @@ public: /// /// Just fills in the data members of the IOFetchData structure /// - /// \param proto either IPPROTO_UDP or IPPROTO_TCP + /// \param proto Protocol: either IOFetch::TCP or IOFetch::UDP /// \param service I/O Service object to handle the asynchronous /// operations. /// \param query DNS question to send to the upstream server. @@ -163,18 +169,18 @@ public: /// \param wait Timeout for the fetch (in ms). /// /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554) - IOFetchData(int proto, IOService& service, + IOFetchData(Protocol proto, IOService& service, const isc::dns::Question& query, const IOAddress& address, uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb, int wait) : - socket((proto == IPPROTO_UDP) ? + socket((proto == UDP) ? static_cast*>( new UDPSocket(service)) : static_cast*>( new TCPSocket(service)) ), - remote((proto == IPPROTO_UDP) ? + remote((proto == UDP) ? static_cast(new UDPEndpoint(address, port)) : static_cast(new TCPEndpoint(address, port)) ), @@ -197,7 +203,7 @@ public: /// /// TODO: Need to randomise the source port /// - /// \param protocol Fetch protocol, either IPPROTO_UDP or IPPROTO_TCP + /// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP /// \param service I/O Service object to handle the asynchronous /// operations. /// \param question DNS question to send to the upstream server. @@ -211,11 +217,11 @@ public: /// (default = 53) /// \param wait Timeout for the fetch (in ms). The default value of /// -1 indicates no timeout. - IOFetch(int protocol, IOService& service, + IOFetch(Protocol protocol, IOService& service, const isc::dns::Question& question, const IOAddress& address, uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb, int wait = -1); - + /// \brief Coroutine entry point /// /// The operator() method is the method in which the coroutine code enters diff --git a/src/lib/asiolink/io_message.h b/src/lib/asiolink/io_message.h index 532f4492d9..dac380dff8 100644 --- a/src/lib/asiolink/io_message.h +++ b/src/lib/asiolink/io_message.h @@ -97,8 +97,4 @@ private: } // asiolink -#endif // __IO_MESSAGE_H - -// Local Variables: -// mode: c++ -// End: +#endif // __IO_MESSAGE_H \ No newline at end of file diff --git a/src/lib/asiolink/recursive_query.cc b/src/lib/asiolink/recursive_query.cc index 5b767a8980..0ea8f0e94f 100644 --- a/src/lib/asiolink/recursive_query.cc +++ b/src/lib/asiolink/recursive_query.cc @@ -163,7 +163,7 @@ private: int serverIndex = rand() % uc; dlog("Sending upstream query (" + question_.toText() + ") to " + upstream_->at(serverIndex).first); - IOFetch query(IPPROTO_UDP, io_, question_, + IOFetch query(IOFetch::UDP, io_, question_, upstream_->at(serverIndex).first, upstream_->at(serverIndex).second, buffer_, this, query_timeout_); @@ -173,7 +173,7 @@ private: int serverIndex = rand() % zs; dlog("Sending query to zone server (" + question_.toText() + ") to " + zone_servers_.at(serverIndex).first); - IOFetch query(IPPROTO_IDP, io_, question_, + IOFetch query(IOFetch::UDP, io_, question_, zone_servers_.at(serverIndex).first, zone_servers_.at(serverIndex).second, buffer_, this, query_timeout_); diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index d47527874f..ff4a745ffe 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -32,12 +32,13 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) 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/dns/libdns++.la run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la -run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la +run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la +run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la +run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS) $(LOG4CXX_LDFLAGS) diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index 57f61b2777..d21f03f1ae 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -66,7 +66,7 @@ public: run_(false), question_(Name("example.net"), RRClass::IN(), RRType::A()), buff_(new OutputBuffer(512)), - udp_fetch_(IPPROTO_UDP, service_, question_, IOAddress(TEST_HOST), + udp_fetch_(IOFetch::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) diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index 8cb2232cfa..56a9bb0972 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -28,7 +28,6 @@ #include - #include #include #include @@ -51,7 +50,7 @@ public: enum { MAX_SIZE = 4096 // Send and receive size }; - + /// \brief Constructor from an ASIO UDP socket. /// /// \param socket The ASIO representation of the UDP socket. It @@ -201,12 +200,23 @@ UDPSocket::open(const IOEndpoint* endpoint, C&) { 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)); + + asio::ip::udp::socket::send_buffer_size snd_size; + socket_.get_option(snd_size); + if (snd_size.value() < MAX_SIZE) { + snd_size = MAX_SIZE; + socket_.set_option(snd_size); + } + + asio::ip::udp::socket::receive_buffer_size rcv_size; + socket_.get_option(rcv_size); + if (rcv_size.value() < MAX_SIZE) { + rcv_size = MAX_SIZE; + socket_.set_option(rcv_size); + } } + + // Nothing was done asynchronously, so tell the caller that. return (false); } From 2e46317ec615067947983aa9ebcce371a6a8fd2f Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Fri, 25 Feb 2011 09:10:35 +0800 Subject: [PATCH 007/113] [trac595] Refactor function MessageEntry::getRRsetTrustLevel() --- .../cache/tests/testdata/message_fromWire7 | 26 +++++++++++++++++++ .../cache/tests/testdata/message_fromWire8 | 24 +++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/lib/cache/tests/testdata/message_fromWire7 create mode 100644 src/lib/cache/tests/testdata/message_fromWire8 diff --git a/src/lib/cache/tests/testdata/message_fromWire7 b/src/lib/cache/tests/testdata/message_fromWire7 new file mode 100644 index 0000000000..c4f04a799f --- /dev/null +++ b/src/lib/cache/tests/testdata/message_fromWire7 @@ -0,0 +1,26 @@ +# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1060 +# ;; flags: qr aa rd; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0 +# ;; WARNING: recursion requested but not available +# +# ;; QUESTION SECTION: +# ;test.zlk.cn. IN A +# +# ;; ANSWER SECTION: +# test.zlk.cn. 21600 IN CNAME cname.a.dname.test.zlk.cn. +# a.dname.test.zlk.cn. 21600 IN DNAME dname.zlk.org. +# cname.a.dname.test.zlk.cn. 21600 IN CNAME cname.dname.zlk.org. +# dname.zlk.org. 21600 IN DNAME dname.zlk.com. +# cname.dname.zlk.org. 21600 IN CNAME cname.dname.zlk.com. + +0424 8500 + 00 01 00 05 00 00 00 00 04 74 65 73 + 74 03 7a 6c 6b 02 63 6e 00 00 01 00 01 c0 0c 00 + 05 00 01 00 00 54 60 00 10 05 63 6e 61 6d 65 01 + 61 05 64 6e 61 6d 65 c0 0c c0 2f 00 27 00 01 00 + 00 54 60 00 0f 05 64 6e 61 6d 65 03 7a 6c 6b 03 + 6f 72 67 00 c0 29 00 05 00 01 00 00 54 60 00 08 + 05 63 6e 61 6d 65 c0 45 c0 45 00 27 00 01 00 00 + 54 60 00 0f 05 64 6e 61 6d 65 03 7a 6c 6b 03 63 + 6f 6d 00 c0 60 00 05 00 01 00 00 54 60 00 08 05 + 63 6e 61 6d 65 c0 74 + diff --git a/src/lib/cache/tests/testdata/message_fromWire8 b/src/lib/cache/tests/testdata/message_fromWire8 new file mode 100644 index 0000000000..4a0a9c3910 --- /dev/null +++ b/src/lib/cache/tests/testdata/message_fromWire8 @@ -0,0 +1,24 @@ +# +# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1392 +# ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: +# ;; WARNING: recursion requested but not available + +# ;; QUESTION SECTION: +# ;a.dname.zlk.cn. IN NS +# +# ;; ANSWER SECTION: +# dname.zlk.cn. 21600 IN DNAME dname.zlk.org. +# a.dname.zlk.cn. 21600 IN CNAME a.dname.zlk.org. +# dname.zlk.org. 21600 IN DNAME dname.zlk.com. +# a.dname.zlk.org. 21600 IN CNAME a.dname.zlk.com. + +0570 8500 +00 01 00 04 00 00 00 00 01 61 05 64 + 6e 61 6d 65 03 7a 6c 6b 02 63 6e 00 00 02 00 01 + c0 0e 00 27 00 01 00 00 54 60 00 0f 05 64 6e 61 + 6d 65 03 7a 6c 6b 03 6f 72 67 00 c0 0c 00 05 00 + 01 00 00 54 60 00 04 01 61 c0 2c c0 2c 00 27 00 + 01 00 00 54 60 00 0f 05 64 6e 61 6d 65 03 7a 6c + 6b 03 63 6f 6d 00 c0 47 00 05 00 01 00 00 54 60 + 00 04 01 61 c0 57 + From 7de2a6f3f96d466ed24f7d8b24708a30ee679729 Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Fri, 25 Feb 2011 10:10:11 +0800 Subject: [PATCH 008/113] [trac595] commit the forgot change --- src/lib/cache/message_entry.cc | 92 +++++++++++-------- src/lib/cache/tests/message_entry_unittest.cc | 67 +++++++++++--- 2 files changed, 111 insertions(+), 48 deletions(-) diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc index d4de11f45a..e296ed2cbe 100644 --- a/src/lib/cache/message_entry.cc +++ b/src/lib/cache/message_entry.cc @@ -111,6 +111,42 @@ MessageEntry::genMessage(const time_t& time_now, } } +// Get the deepest owner name of DNAME record for the given query name. +static Name +getDeepestDNAMEOwner(const Message& message, const Name& query_name) { + Name dname = query_name; + RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); + while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { + if ((*rrset_iter)->getType() == RRType::DNAME()) { + const Name& rrname = (*rrset_iter)->getName(); + if (NameComparisonResult::SUBDOMAIN == + dname.compare(rrname).getRelation()) { + dname = rrname; + } + } + ++rrset_iter; + } + + return dname; +} + +// Check whether answer section in given message has non-authoritative rrsets. +static bool +answerHasNonAuthRecord(const Message& message, const Name& query_name) { + RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); + while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { + // Here, only check CNAME is enough. If there is + // cname record whose ower name is same with query name, answer + // section may has non-authoritative rrsets. + if ((*rrset_iter)->getType() == RRType::CNAME() && + (*rrset_iter)->getName() == query_name) { + return true; + } + ++rrset_iter; + } + return false; +} + RRsetTrustLevel MessageEntry::getRRsetTrustLevel(const Message& message, const isc::dns::RRsetPtr& rrset, @@ -120,48 +156,30 @@ MessageEntry::getRRsetTrustLevel(const Message& message, switch(section) { case Message::SECTION_ANSWER: { if (aa) { - RRsetIterator rrset_iter = message.beginSection(section); - - // Make sure we are inspecting the right RRset - while((*rrset_iter)->getName() != rrset->getName() && - (*rrset_iter)->getType() != rrset->getType() && - rrset_iter != message.endSection(section)) { - ++rrset_iter; - } - assert(rrset_iter != message.endSection(section)); - // According RFC2181 section 5.4.1, only the record // describing that ailas is necessarily authoritative. - // If there is one or more CNAME records in answer section. - // CNAME records is assumed as the first rrset. - if ((*rrset_iter)->getType() == RRType::CNAME()) { - // TODO: real equals for RRsets? - if ((*rrset_iter).get() == rrset.get()) { - return (RRSET_TRUST_ANSWER_AA); - } else { - return (RRSET_TRUST_ANSWER_NONAA); - } - } - - // Here, if the first rrset is DNAME, then assume the - // second rrset is synchronized CNAME record, except - // these two records, any other records in answer section - // should be treated as non-authoritative. - // TODO, this part logic should be revisited later, - // since it's not mentioned by RFC2181. - if ((*rrset_iter)->getType() == RRType::DNAME()) { - // TODO: real equals for RRsets? - if ((*rrset_iter).get() == rrset.get() || - ((++rrset_iter) != message.endSection(section) && - (*rrset_iter).get() == rrset.get())) { - return (RRSET_TRUST_ANSWER_AA); - } else { - return (RRSET_TRUST_ANSWER_NONAA); - } + // If there are CNAME(Not synchronized from DNAME) + // records in answer section, only the CNAME record + // whose owner name is same with qname is assumed as + // authoritative, all the left records are not authoritative. + // + // If there are DNAME records in answer section, + // Only the start DNAME and the synchronized CNAME record + // from it are authoritative, any other records in answer + // section are non-authoritative. + QuestionIterator quest_iter = message.beginQuestion(); + const Name& query_name = (*quest_iter)->getName(); + const RRType& type = rrset->getType(); + const Name& name = rrset->getName(); + if ((type == RRType::CNAME() && name == query_name) || + (type == RRType::DNAME() && + name == getDeepestDNAMEOwner(message, query_name))) { + return RRSET_TRUST_ANSWER_AA; + } else if (answerHasNonAuthRecord(message, query_name)) { + return RRSET_TRUST_ANSWER_NONAA; } return (RRSET_TRUST_ANSWER_AA); - } else { return (RRSET_TRUST_ANSWER_NONAA); } diff --git a/src/lib/cache/tests/message_entry_unittest.cc b/src/lib/cache/tests/message_entry_unittest.cc index 3b2711a2f9..a96c44129c 100644 --- a/src/lib/cache/tests/message_entry_unittest.cc +++ b/src/lib/cache/tests/message_entry_unittest.cc @@ -29,7 +29,7 @@ using namespace isc; using namespace isc::dns; using namespace std; -static uint32_t MAX_UINT32 = numeric_limits::max(); +static uint32_t MAX_UINT32 = numeric_limits::max(); namespace { @@ -74,7 +74,6 @@ public: message_parse(Message::PARSE), message_render(Message::RENDER) { - rrset_cache_.reset(new RRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_)); } @@ -108,7 +107,6 @@ TEST_F(MessageEntryTest, testParseRRset) { TEST_F(MessageEntryTest, testGetRRsetTrustLevel_AA) { messageFromFile(message_parse, "message_fromWire3"); DerivedMessageEntry message_entry(message_parse, rrset_cache_); - RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER); RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse, @@ -164,7 +162,54 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME) { level = message_entry.getRRsetTrustLevelForTest(message_parse, *rrset_iter, Message::SECTION_ANSWER); + EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA); +} + +TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME_and_DNAME) { + messageFromFile(message_parse, "message_fromWire7"); + DerivedMessageEntry message_entry(message_parse, rrset_cache_); + RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER); + RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse, + *rrset_iter, + Message::SECTION_ANSWER); EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA); + // All the left rrset are non-authoritative + ++rrset_iter; + while (rrset_iter != message_parse.endSection(Message::SECTION_ANSWER)) { + level = message_entry.getRRsetTrustLevelForTest(message_parse, + *rrset_iter, + Message::SECTION_ANSWER); + ++rrset_iter; + EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA); + } +} + +TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME_and_CNAME) { + messageFromFile(message_parse, "message_fromWire8"); + DerivedMessageEntry message_entry(message_parse, rrset_cache_); + RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER); + RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse, + *rrset_iter, + Message::SECTION_ANSWER); + // Test the deepest DNAME + EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA); + ++rrset_iter; + // Test the synchronized CNAME + level = message_entry.getRRsetTrustLevelForTest(message_parse, + *rrset_iter, + Message::SECTION_ANSWER); + ++rrset_iter; + EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA); + + ++rrset_iter; + // All the left rrset are non-authoritative + while (rrset_iter != message_parse.endSection(Message::SECTION_ANSWER)) { + level = message_entry.getRRsetTrustLevelForTest(message_parse, + *rrset_iter, + Message::SECTION_ANSWER); + ++rrset_iter; + EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA); + } } TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME) { @@ -186,7 +231,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME) { level = message_entry.getRRsetTrustLevelForTest(message_parse, *rrset_iter, Message::SECTION_ANSWER); - EXPECT_EQ(level, RRSET_TRUST_ANSWER_AA); + EXPECT_EQ(level, RRSET_TRUST_ANSWER_NONAA); } // We only test the expire_time of the message entry. @@ -204,8 +249,8 @@ TEST_F(MessageEntryTest, testGetRRsetEntries) { messageFromFile(message_parse, "message_fromWire3"); DerivedMessageEntry message_entry(message_parse, rrset_cache_); vector vec; - - // the time is bigger than the smallest expire time of + + // the time is bigger than the smallest expire time of // the rrset in message. time_t expire_time = time(NULL) + 10802; EXPECT_FALSE(message_entry.getRRsetEntriesForTest(vec, expire_time)); @@ -215,17 +260,17 @@ TEST_F(MessageEntryTest, testGenMessage) { messageFromFile(message_parse, "message_fromWire3"); DerivedMessageEntry message_entry(message_parse, rrset_cache_); time_t expire_time = message_entry.getExpireTime(); - + Message msg(Message::RENDER); EXPECT_FALSE(message_entry.genMessage(expire_time + 2, msg)); message_entry.genMessage(time(NULL), msg); // Check whether the generated message is same with cached one. - + EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_AA)); EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_TC)); - EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_ANSWER)); - EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_AUTHORITY)); - EXPECT_EQ(5, sectionRRsetCount(msg, Message::SECTION_ADDITIONAL)); + EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_ANSWER)); + EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_AUTHORITY)); + EXPECT_EQ(5, sectionRRsetCount(msg, Message::SECTION_ADDITIONAL)); // Check the rrset in answer section. EXPECT_EQ(1, msg.getRRCount(Message::SECTION_ANSWER)); From d3b169adf97f9c5a4990400e54562d644016fdaf Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Fri, 25 Feb 2011 14:02:16 +0800 Subject: [PATCH 009/113] [trac595] 1. Put file-scoped functions in unnamed namespace instead of by using static descriptor. 2. add brackets to the returned value. 3. Avoid using the domain names which may conflict with the current or future domain names. --- src/lib/cache/message_entry.cc | 81 ++++++++++--------- .../cache/tests/testdata/message_fromWire7 | 33 ++++---- .../cache/tests/testdata/message_fromWire8 | 39 +++++---- 3 files changed, 79 insertions(+), 74 deletions(-) diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc index e296ed2cbe..5f42ed5c8b 100644 --- a/src/lib/cache/message_entry.cc +++ b/src/lib/cache/message_entry.cc @@ -23,6 +23,47 @@ using namespace isc::dns; using namespace std; +// Put file scope functions in unnamed namespace. +namespace { + +// Get the deepest owner name of DNAME record for the given query name. +Name +getDeepestDNAMEOwner(const Message& message, const Name& query_name) { + Name dname = query_name; + RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); + while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { + if ((*rrset_iter)->getType() == RRType::DNAME()) { + const Name& rrname = (*rrset_iter)->getName(); + if (NameComparisonResult::SUBDOMAIN == + dname.compare(rrname).getRelation()) { + dname = rrname; + } + } + ++rrset_iter; + } + + return (dname); +} + +// Check whether answer section in given message has non-authoritative rrsets. +bool +answerHasNonAuthRecord(const Message& message, const Name& query_name) { + RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); + while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { + // Here, only check CNAME is enough. If there is + // cname record whose ower name is same with query name, answer + // section may has non-authoritative rrsets. + if ((*rrset_iter)->getType() == RRType::CNAME() && + (*rrset_iter)->getName() == query_name) { + return (true); + } + ++rrset_iter; + } + return (false); +} + +} // End of unnamed namespace + namespace isc { namespace cache { @@ -111,42 +152,6 @@ MessageEntry::genMessage(const time_t& time_now, } } -// Get the deepest owner name of DNAME record for the given query name. -static Name -getDeepestDNAMEOwner(const Message& message, const Name& query_name) { - Name dname = query_name; - RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); - while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { - if ((*rrset_iter)->getType() == RRType::DNAME()) { - const Name& rrname = (*rrset_iter)->getName(); - if (NameComparisonResult::SUBDOMAIN == - dname.compare(rrname).getRelation()) { - dname = rrname; - } - } - ++rrset_iter; - } - - return dname; -} - -// Check whether answer section in given message has non-authoritative rrsets. -static bool -answerHasNonAuthRecord(const Message& message, const Name& query_name) { - RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); - while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { - // Here, only check CNAME is enough. If there is - // cname record whose ower name is same with query name, answer - // section may has non-authoritative rrsets. - if ((*rrset_iter)->getType() == RRType::CNAME() && - (*rrset_iter)->getName() == query_name) { - return true; - } - ++rrset_iter; - } - return false; -} - RRsetTrustLevel MessageEntry::getRRsetTrustLevel(const Message& message, const isc::dns::RRsetPtr& rrset, @@ -174,9 +179,9 @@ MessageEntry::getRRsetTrustLevel(const Message& message, if ((type == RRType::CNAME() && name == query_name) || (type == RRType::DNAME() && name == getDeepestDNAMEOwner(message, query_name))) { - return RRSET_TRUST_ANSWER_AA; + return (RRSET_TRUST_ANSWER_AA); } else if (answerHasNonAuthRecord(message, query_name)) { - return RRSET_TRUST_ANSWER_NONAA; + return (RRSET_TRUST_ANSWER_NONAA); } return (RRSET_TRUST_ANSWER_AA); diff --git a/src/lib/cache/tests/testdata/message_fromWire7 b/src/lib/cache/tests/testdata/message_fromWire7 index c4f04a799f..7b10b5ddef 100644 --- a/src/lib/cache/tests/testdata/message_fromWire7 +++ b/src/lib/cache/tests/testdata/message_fromWire7 @@ -1,26 +1,27 @@ -# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1060 +# +# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1155 # ;; flags: qr aa rd; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 0 # ;; WARNING: recursion requested but not available # # ;; QUESTION SECTION: -# ;test.zlk.cn. IN A +# ;test.example.com. IN A # # ;; ANSWER SECTION: -# test.zlk.cn. 21600 IN CNAME cname.a.dname.test.zlk.cn. -# a.dname.test.zlk.cn. 21600 IN DNAME dname.zlk.org. -# cname.a.dname.test.zlk.cn. 21600 IN CNAME cname.dname.zlk.org. -# dname.zlk.org. 21600 IN DNAME dname.zlk.com. -# cname.dname.zlk.org. 21600 IN CNAME cname.dname.zlk.com. +# test.example.com. 21600 IN CNAME cname.a.dname.example.com. +# dname.example.com. 21600 IN DNAME dname.example.org. +# cname.a.dname.example.com. 21600 IN CNAME cname.a.dname.example.org. +# dname.example.org. 21600 IN DNAME dname.example.org. +# cname.a.dname.example.org. 21600 IN CNAME cname.a.dname.example.org. 0424 8500 00 01 00 05 00 00 00 00 04 74 65 73 - 74 03 7a 6c 6b 02 63 6e 00 00 01 00 01 c0 0c 00 - 05 00 01 00 00 54 60 00 10 05 63 6e 61 6d 65 01 - 61 05 64 6e 61 6d 65 c0 0c c0 2f 00 27 00 01 00 - 00 54 60 00 0f 05 64 6e 61 6d 65 03 7a 6c 6b 03 - 6f 72 67 00 c0 29 00 05 00 01 00 00 54 60 00 08 - 05 63 6e 61 6d 65 c0 45 c0 45 00 27 00 01 00 00 - 54 60 00 0f 05 64 6e 61 6d 65 03 7a 6c 6b 03 63 - 6f 6d 00 c0 60 00 05 00 01 00 00 54 60 00 08 05 - 63 6e 61 6d 65 c0 74 + 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 01 + 00 01 c0 0c 00 05 00 01 00 00 54 60 00 10 05 63 + 6e 61 6d 65 01 61 05 64 6e 61 6d 65 c0 11 c0 36 + 00 27 00 01 00 00 54 60 00 13 05 64 6e 61 6d 65 + 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 c0 2e 00 + 05 00 01 00 00 54 60 00 0a 05 63 6e 61 6d 65 01 + 61 c0 4a c0 4a 00 27 00 01 00 00 54 60 00 13 05 + 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 6f 72 + 67 00 c0 69 00 05 00 01 00 00 54 60 00 02 c0 69 diff --git a/src/lib/cache/tests/testdata/message_fromWire8 b/src/lib/cache/tests/testdata/message_fromWire8 index 4a0a9c3910..bc9e144080 100644 --- a/src/lib/cache/tests/testdata/message_fromWire8 +++ b/src/lib/cache/tests/testdata/message_fromWire8 @@ -1,24 +1,23 @@ -# -# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1392 -# ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: +# A response includes multiple DNAME and synchronized CNAME records +# ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 900 +# ;; flags: qr aa rd; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 0 # ;; WARNING: recursion requested but not available - +# # ;; QUESTION SECTION: -# ;a.dname.zlk.cn. IN NS +# ;a.dname.example.com. IN NS # # ;; ANSWER SECTION: -# dname.zlk.cn. 21600 IN DNAME dname.zlk.org. -# a.dname.zlk.cn. 21600 IN CNAME a.dname.zlk.org. -# dname.zlk.org. 21600 IN DNAME dname.zlk.com. -# a.dname.zlk.org. 21600 IN CNAME a.dname.zlk.com. - -0570 8500 -00 01 00 04 00 00 00 00 01 61 05 64 - 6e 61 6d 65 03 7a 6c 6b 02 63 6e 00 00 02 00 01 - c0 0e 00 27 00 01 00 00 54 60 00 0f 05 64 6e 61 - 6d 65 03 7a 6c 6b 03 6f 72 67 00 c0 0c 00 05 00 - 01 00 00 54 60 00 04 01 61 c0 2c c0 2c 00 27 00 - 01 00 00 54 60 00 0f 05 64 6e 61 6d 65 03 7a 6c - 6b 03 63 6f 6d 00 c0 47 00 05 00 01 00 00 54 60 - 00 04 01 61 c0 57 - +# dname.example.com. 21600 IN DNAME dname.example.org. +# a.dname.example.com. 21600 IN CNAME a.dname.example.org. +# dname.example.org. 21600 IN DNAME dname.example.org. +# a.dname.example.org. 21600 IN CNAME a.dname.example.org. +0384 8500 + 00 01 00 04 00 00 00 00 01 61 05 64 + 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 63 6f 6d + 00 00 02 00 01 c0 0e 00 27 00 01 00 00 54 60 00 + 13 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 + 6f 72 67 00 c0 0c 00 05 00 01 00 00 54 60 00 04 + 01 61 c0 31 c0 31 00 27 00 01 00 00 54 60 00 13 + 05 64 6e 61 6d 65 07 65 78 61 6d 70 6c 65 03 6f + 72 67 00 c0 50 00 05 00 01 00 00 54 60 00 02 c0 + 50 From b13c2fd090114e2c0cfe164bae10cde145f509b4 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Fri, 25 Feb 2011 20:32:59 +0000 Subject: [PATCH 010/113] [trac499] Checkpoint of work to end of Friday 25 Feb 2011 --- src/lib/asiolink/dummy_io_cb.h | 8 + src/lib/asiolink/io_asio_socket.h | 72 +++- src/lib/asiolink/io_endpoint.cc | 1 + src/lib/asiolink/io_fetch.cc | 118 +++++- src/lib/asiolink/io_fetch.h | 126 ++---- src/lib/asiolink/tcp_endpoint.h | 61 ++- src/lib/asiolink/tcp_socket.h | 139 ++++-- src/lib/asiolink/tests/Makefile.am | 2 + src/lib/asiolink/tests/io_fetch_unittest.cc | 398 ++++++++++++++---- .../asiolink/tests/tcp_endpoint_unittest.cc | 55 +++ src/lib/asiolink/tests/tcp_socket_unittest.cc | 349 +++++++++++++++ src/lib/asiolink/tests/udp_socket_unittest.cc | 20 +- src/lib/asiolink/udp_endpoint.h | 11 + src/lib/asiolink/udp_socket.h | 72 ++-- 14 files changed, 1103 insertions(+), 329 deletions(-) create mode 100644 src/lib/asiolink/tests/tcp_endpoint_unittest.cc create mode 100644 src/lib/asiolink/tests/tcp_socket_unittest.cc diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h index bde656c348..0006b95cfc 100644 --- a/src/lib/asiolink/dummy_io_cb.h +++ b/src/lib/asiolink/dummy_io_cb.h @@ -36,6 +36,14 @@ namespace asiolink { class DummyIOCallback { public: + /// \brief Asynchronous I/O callback method + /// + /// \param error Unused + void operator()(asio::error_code) + { + // TODO: log an error if this method ever gets called. + } + /// \brief Asynchronous I/O callback method /// /// \param error Unused diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index d485610bc2..2e165f4015 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -50,6 +50,16 @@ public: IOError(file, line, what) {} }; +/// \brief Buffer Overflow +/// +/// Thrown if an attempt is made to receive into an area beyond the end of +/// the receive data buffer. +class BufferOverflow : public IOError { +public: + BufferOverflow(const char* file, size_t line, const char* what) : + IOError(file, line, what) {} +}; + /// Forward declaration of an IOEndpoint class IOEndpoint; @@ -129,32 +139,47 @@ public: /// \return IPPROTO_TCP for TCP sockets virtual int getProtocol() const = 0; - /// \brief Open AsioSocket + /// \brief Is Open() synchronous? /// - /// Opens the socket for asynchronous I/O. On a UDP socket, this is merely - /// an "open()" on the underlying socket (so completes immediately), but on - /// a TCP socket it also connects to the remote end (which is done as an + /// On a UDP socket, an "open" operation is merely a call to "open()" on + /// the underlying socket (so completes immediately), but on a TCP socket it + /// also includings connecting to the remote end (which is done as an /// asynchronous operation). /// /// For TCP, signalling of the completion of the operation is done by /// by calling the callback function in the normal way. This could be done /// for UDP (by posting en event on the event queue); however, that will - /// incur additional overhead in the most common case. Instead, the return - /// value indicates whether the operation was asynchronous or not. If yes, - /// (i.e. TCP) the callback has been posted to the event queue: if no (UDP), - /// no callback has been posted (in which case it is up to the caller as to - /// whether they want to manually post the callback themself.) + /// incur additional overhead in the most common case. So we give the + /// caller the choice for calling this open() method synchronously or + /// asynchronously. + /// + /// Owing to the way that the stackless coroutines are implemented, we need + /// to know _before_ executing the operation whether or not the open is + /// asynchronous. So this method simply provides that information. + /// + /// (The reason there is a need to know is because the call to open() passes + /// in the state of the coroutine at the time the call is made. On an + /// asynchronous I/O, we need to set the state to point to the statement + /// after the call to open() before we pass the corotuine to the open() + /// call. Unfortunately, the macros that do this also yield control - which + /// we don't want to do if the open is synchronous. Hence we need to know + /// before we make the call to open() whether that call will complete + /// asynchronously.) + virtual bool isOpenSynchronous() const = 0; + + /// \brief Open AsioSocket + /// + /// Opens the socket for asynchronous I/O. The open will complete + /// synchronously on UCP or asynchronously on TCP (in which case a callback + /// will be queued): what will happen can be found by calling the method + /// isOpenSynchronous(). /// /// \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 operation has /// completed, but only if the operation was asynchronous. - /// - /// \return true if an asynchronous operation was started and the caller - /// should yield and wait for completion, false if the operation was - /// completed synchronously and no callback was queued. - virtual bool open(const IOEndpoint* endpoint, C& callback) = 0; + virtual void open(const IOEndpoint* endpoint, C& callback) = 0; /// \brief Send Asynchronously /// @@ -167,7 +192,7 @@ public: /// \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; + const IOEndpoint* endpoint, C& callback) = 0; /// \brief Receive Asynchronously /// @@ -178,11 +203,11 @@ 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. + /// \param offset Offset into buffer where data is to be put /// \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; + virtual void asyncReceive(void* data, size_t length, size_t offset, + IOEndpoint* endpoint, C& callback) = 0; /// \brief Checks if the data received is complete. /// @@ -204,7 +229,7 @@ public: /// \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; + size_t& cumulative) = 0; /// \brief Cancel I/O On AsioSocket virtual void cancel() = 0; @@ -251,6 +276,13 @@ public: virtual int getProtocol() const { return (protocol_); } + /// \brief Is socket opening synchronous? + /// + /// \return true - it is for this class. + bool isOpenSynchronous() const { + return true; + } + /// \brief Open AsioSocket /// /// A call that is a no-op on UDP sockets, this opens a connection to the @@ -280,7 +312,7 @@ public: /// /// \param data Unused /// \param length Unused - /// \param cumulative Unused + /// \param offset Unused /// \param endpoint Unused /// \param callback Unused virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc index bf79f61868..97e9c9139c 100644 --- a/src/lib/asiolink/io_endpoint.cc +++ b/src/lib/asiolink/io_endpoint.cc @@ -22,6 +22,7 @@ #include #include +#include #include #include diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 7fce6074ed..d890a52f49 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -19,6 +19,9 @@ #include #include +#include +#include +#include #include #include @@ -28,10 +31,18 @@ #include #include +#include + #include #include +#include #include #include +#include +#include +#include +#include +#include using namespace asio; using namespace isc::dns; @@ -44,13 +55,87 @@ namespace asiolink { isc::log::Logger logger("asio"); +/// \brief IOFetch Data +/// +/// The data for IOFetch is held in a separate struct pointed to by a +/// shared_ptr object. This is because the IOFetch object will be copied +/// often (it is used as a coroutine and passed as callback to many +/// async_*() functions) and we want keep the same data). Organising the +/// data in this way keeps copying to a minimum. +struct IOFetchData { + + // The first two members are shared pointers to a base class because what is + // actually instantiated depends on whether the fetch is over UDP or TCP, + // which is not known until construction of the IOFetch. Use of a shared + //pointer here is merely to ensure deletion when the data object is deleted. + boost::shared_ptr > 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 + 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 + IOFetch::Origin origin; ///< Origin of last asynchronous I/O + + /// \brief Constructor + /// + /// Just fills in the data members of the IOFetchData structure + /// + /// \param protocol Either IOFetch::TCP or IOFetch::UDP + /// \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 + /// \param port Port to use for the query + /// \param buff Output buffer into which the response (in wire format) + /// is written (if a response is received). + /// \param cb Callback object containing the callback to be called + /// when we terminate. The caller is responsible for managing this + /// object and deleting it if necessary. + /// \param wait Timeout for the fetch (in ms). + /// + /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554) + IOFetchData(IOFetch::Protocol protocol, IOService& service, + const isc::dns::Question& query, const IOAddress& address, + uint16_t port, isc::dns::OutputBufferPtr& buff, IOFetch::Callback* cb, + int wait) + : + socket((protocol == IOFetch::UDP) ? + static_cast*>( + new UDPSocket(service)) : + static_cast*>( + new TCPSocket(service)) + ), + remote((protocol == IOFetch::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::MIN_LENGTH]), + callback(cb), + cumulative(0), + stopped(false), + timer(service.get_io_service()), + timeout(wait), + origin(IOFetch::NONE) + {} +}; + + /// IOFetch Constructor - just initialize the private data IOFetch::IOFetch(Protocol protocol, IOService& service, const isc::dns::Question& question, const IOAddress& address, uint16_t port, - isc::dns::OutputBufferPtr& buff, Callback* cb, int wait) + OutputBufferPtr& buff, Callback* cb, int wait) : - data_(new IOFetch::IOFetchData(protocol, service, question, address, + data_(new IOFetchData(protocol, service, question, address, port, buff, cb, wait)) { } @@ -59,7 +144,9 @@ IOFetch::IOFetch(Protocol protocol, IOService& service, /// pattern; see internal/coroutine.h for details. void -IOFetch::operator()(error_code ec, size_t length) { +IOFetch::operator()(asio::error_code ec, size_t length) { + std::cerr << "IOFetch::operator() [" << this << "], origin = " << + data_->origin << ", coroutine = " << get_value() << "\n"; if (data_->stopped) { return; } else if (ec) { @@ -91,7 +178,6 @@ IOFetch::operator()(error_code ec, size_t length) { data_->remote->getAddress().toText()); } - // If we timeout, we stop, which will shutdown everything and // cancel all other attempts to run inside the coroutine if (data_->timeout != -1) { @@ -103,17 +189,26 @@ 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. - if (data_->socket->open(data_->remote.get(), *this)) { - data_->origin = OPEN; - CORO_YIELD; + + data_->origin = OPEN; + if (data_->socket->isOpenSynchronous()) { + std::cerr << "IOFetch: Opening socket synchronously\n"; + data_->socket->open(data_->remote.get(), *this); + } else { + std::cerr << "IOFetch: Opening socket asynchronously and yeilding\n"; + CORO_YIELD data_->socket->open(data_->remote.get(), *this); + std::cerr << "IOFetch: Resuming after Opening socket asynchronously\n"; } // Begin an asynchronous send, and then yield. When the send completes // send completes, we will resume immediately after this point. + // Note: A TCP message may not be sent in one piece (depends on the + // implementation in TCP socket). Therefore there may be data_->origin = SEND; + std::cerr << "IOFetch: asynchronous send\n"; CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(), data_->msgbuf->getLength(), data_->remote.get(), *this); - + std::cerr << "IOFetch: resuming after asynchronous send\n"; // 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 @@ -123,9 +218,11 @@ IOFetch::operator()(error_code ec, size_t length) { // we check if the operation is complete and if not, loop to read again. data_->origin = RECEIVE; do { + std::cerr << "IOFetch: asynchronous receive\n"; CORO_YIELD data_->socket->asyncReceive(data_->data.get(), - static_cast(MAX_LENGTH), data_->cumulative, + static_cast(MIN_LENGTH), data_->cumulative, data_->remote.get(), *this); + std::cerr << "IOFetch: resuming after asynchronous receive\n"; } while (!data_->socket->receiveComplete(data_->data.get(), length, data_->cumulative)); @@ -141,6 +238,7 @@ IOFetch::operator()(error_code ec, size_t length) { // Finished with this socket, so close it. data_->origin = CLOSE; + std::cerr << "IOFetch: close\n"; data_->socket->close(); /// We are done @@ -230,7 +328,7 @@ IOFetch::stop(Result result) { // Log an error - called on I/O failure -void IOFetch::logIOFailure(asio::error_code& ec) { +void IOFetch::logIOFailure(asio::error_code ec) { // Get information that will be in all messages static const char* PROTOCOL[2] = {"TCP", "UDP"}; diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index 369e057156..479c54c8f4 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -17,31 +17,24 @@ #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 - - namespace asiolink { +// Forward declarations +class IOAddress; +class IOFetchData; +class IOService; /// \brief Upstream Fetch Processing /// @@ -76,9 +69,9 @@ public: /// even if the contents of the packet indicate that some error occurred. enum Result { SUCCESS = 0, ///< Success, fetch completed - TIME_OUT, ///< Failure, fetch timed out - STOPPED, ///< Control code, fetch has been stopped - NOTSET ///< For testing, indicates value not set + TIME_OUT = 1, ///< Failure, fetch timed out + STOPPED = 2, ///< Control code, fetch has been stopped + NOTSET = 3 ///< For testing, indicates value not set }; // The next enum is a "trick" to allow constants to be defined in a class @@ -86,7 +79,7 @@ public: /// \brief Integer Constants enum { - MAX_LENGTH = 4096 ///< Maximum size of receive buffer + MIN_LENGTH = 4096 ///< Minimum size of receive buffer }; /// \brief I/O Fetch Callback @@ -112,91 +105,14 @@ public: virtual ~Callback() {} - /// \brief Callback method called when the fetch completes /// \brief Origin of Asynchronous I/O Call - /// - - // The next enum is a "trick" to allow constants to be defined in a class - // declaration. - + /// \brief Callback method /// - /// \brief result Result of the fetch + /// This is the method called when the fecth completes. + /// + /// \param result Result of the fetch virtual void operator()(Result result) = 0; }; - /// \brief IOFetch Data - /// - /// The data for IOFetch is held in a separate struct pointed to by a - /// shared_ptr object. This is because the IOFetch object will be copied - /// often (it is used as a coroutine and passed as callback to many - /// async_*() functions) and we want keep the same data). Organising the - /// data in this way keeps copying to a minimum. - struct IOFetchData { - - // The next two members are shared pointers to a base class because what - // is actually instantiated depends on whether the fetch is over UDP or - // TCP, which is not known until construction of the IOFetch. Use of - // a shared pointer here is merely to ensure deletion when the data - // object is deleted. - boost::shared_ptr > 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 - 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 - Origin origin; ///< Origin of last asynchronous I/O - - /// \brief Constructor - /// - /// Just fills in the data members of the IOFetchData structure - /// - /// \param proto Protocol: either IOFetch::TCP or IOFetch::UDP - /// \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 - /// \param port Port to use for the query - /// \param buff Output buffer into which the response (in wire format) - /// is written (if a response is received). - /// \param cb Callback object containing the callback to be called - /// when we terminate. The caller is responsible for managing this - /// object and deleting it if necessary. - /// \param wait Timeout for the fetch (in ms). - /// - /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554) - IOFetchData(Protocol proto, IOService& service, - const isc::dns::Question& query, const IOAddress& address, - uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb, - int wait) - : - socket((proto == UDP) ? - static_cast*>( - new UDPSocket(service)) : - static_cast*>( - new TCPSocket(service)) - ), - remote((proto == 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), - origin(NONE) - {} - }; - /// \brief Constructor. /// /// Creates the object that will handle the upstream fetch. @@ -229,8 +145,16 @@ public: /// /// \param ec Error code, the result of the last asynchronous I/O operation. /// \param length Amount of data received on the last asynchronous read - void operator()(asio::error_code ec = asio::error_code(), - size_t length = 0); + void operator()(asio::error_code ec, size_t length); + + void operator()(asio::error_code ec) { + operator()(ec, 0); + } + + void operator()() { + asio::error_code ec; + operator()(ec); + } /// \brief Terminate query /// @@ -246,7 +170,7 @@ private: /// Records an I/O failure to the log file /// /// \param ec ASIO error code - void logIOFailure(asio::error_code& ec); + void logIOFailure(asio::error_code ec); boost::shared_ptr data_; ///< Private data diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h index 8f6270f3b3..158ca4a97e 100644 --- a/src/lib/asiolink/tcp_endpoint.h +++ b/src/lib/asiolink/tcp_endpoint.h @@ -24,32 +24,33 @@ namespace asiolink { /// \brief The \c TCPEndpoint class is a concrete derived class of -/// \c IOEndpoint that represents an endpoint of a TCP connection. +/// \c IOEndpoint that represents an endpoint of a TCP packet. /// -/// 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 IOEndpoint. -/// This design may be changed when we generalize the wrapper interface. -/// -/// Note: this implementation is optimized for the case where this object -/// is created from an ASIO endpoint object in a receiving code path -/// by avoiding to make a copy of the base endpoint. For TCP it may not be -/// a big deal, but when we receive UDP packets at a high rate, the copy -/// overhead might be significant. +/// Other notes about \c TCPEndpoint applies to this class, too. class TCPEndpoint : public IOEndpoint { public: /// - /// \name Constructors and Destructor + /// \name Constructors and Destructor. /// //@{ + + /// \brief Default Constructor + /// + /// Creates an internal endpoint. This is expected to be set by some + /// external call. + TCPEndpoint() : + asio_endpoint_placeholder_(new asio::ip::tcp::endpoint()), + asio_endpoint_(*asio_endpoint_placeholder_) + {} + /// \brief Constructor from a pair of address and port. /// /// \param address The IP address of the endpoint. /// \param port The TCP port number of the endpoint. TCPEndpoint(const IOAddress& address, const unsigned short port) : asio_endpoint_placeholder_( - new asio::ip::tcp::endpoint( - asio::ip::address::from_string(address.toText()), port)), + new asio::ip::tcp::endpoint(asio::ip::address::from_string(address.toText()), + port)), asio_endpoint_(*asio_endpoint_placeholder_) {} @@ -59,39 +60,53 @@ public: /// corresponding ASIO class, \c tcp::endpoint. /// /// \param asio_endpoint The ASIO representation of the TCP endpoint. - TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) : + TCPEndpoint(asio::ip::tcp::endpoint& asio_endpoint) : asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint) {} + /// \brief Constructor from an ASIO TCP endpoint. + /// + /// This constructor is designed to be an efficient wrapper for the + /// corresponding ASIO class, \c tcp::endpoint. + /// + /// \param asio_endpoint The ASIO representation of the TCP endpoint. + TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) : + asio_endpoint_placeholder_(new asio::ip::tcp::endpoint(asio_endpoint)), + asio_endpoint_(*asio_endpoint_placeholder_) + {} + /// \brief The destructor. - ~TCPEndpoint() { delete asio_endpoint_placeholder_; } + virtual ~TCPEndpoint() { delete asio_endpoint_placeholder_; } //@} - IOAddress getAddress() const { + virtual IOAddress getAddress() const { return (asio_endpoint_.address()); } - uint16_t getPort() const { + virtual uint16_t getPort() const { return (asio_endpoint_.port()); } - short getProtocol() const { + virtual short getProtocol() const { return (asio_endpoint_.protocol().protocol()); } - short getFamily() const { + virtual short getFamily() const { return (asio_endpoint_.protocol().family()); } // This is not part of the exosed IOEndpoint API but allows // direct access to the ASIO implementation of the endpoint - const asio::ip::tcp::endpoint& getASIOEndpoint() const { + inline const asio::ip::tcp::endpoint& getASIOEndpoint() const { + return (asio_endpoint_); + } + inline asio::ip::tcp::endpoint& getASIOEndpoint() { return (asio_endpoint_); } private: - const asio::ip::tcp::endpoint* asio_endpoint_placeholder_; - const asio::ip::tcp::endpoint& asio_endpoint_; + asio::ip::tcp::endpoint* asio_endpoint_placeholder_; + asio::ip::tcp::endpoint& asio_endpoint_; }; } // namespace asiolink diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index 5a85aaa633..a7cc8e97da 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -27,8 +27,13 @@ #include #include +#include +#include + #include +#include + #include #include #include @@ -36,6 +41,15 @@ namespace asiolink { +/// \brief Buffer Too Large +/// +/// Thrown on an attempt to send a buffer > 64k +class BufferTooLarge : public IOError { +public: + BufferTooLarge(const char* file, size_t line, const char* what) : + IOError(file, line, what) {} +}; + /// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket /// that represents a TCP socket. /// @@ -67,27 +81,37 @@ public: /// \brief Destructor virtual ~TCPSocket(); - virtual int getNative() const { return (socket_.native()); } - virtual int getProtocol() const { return (IPPROTO_TCP); } + /// \brief Return file descriptor of underlying socket + virtual int getNative() const { + return (socket_.native()); + } + + /// \brief Return protocol of socket + virtual int getProtocol() const { + return (IPPROTO_TCP); + } + + /// \brief Is "open()" synchronous? + /// + /// Indicates that the opening of a TCP socket is asynchronous. + virtual bool isOpenSynchronous() const { + return (false); + } /// \brief Open Socket /// - /// 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. + /// Opens the UDP socket. This is an asynchronous operation, completion of + /// which will be signalled via a call to the callback function. /// /// \param endpoint Endpoint to which the socket will connect to. - /// \param callback Unused. - /// - /// \return false to indicate that the "operation" completed synchronously. - virtual bool open(const IOEndpoint* endpoint, C&); + /// \param callback Callback object. + virtual void open(const IOEndpoint* endpoint, C& callback); /// \brief Send Asynchronously /// - /// 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. + /// Calls the underlying socket's async_send() method to send a packet of + /// data asynchronously to the remote endpoint. The callback will be called + /// on completion. /// /// \param data Data to send /// \param length Length of data to send @@ -98,19 +122,17 @@ public: /// \brief Receive Asynchronously /// - /// 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. + /// Calls the underlying socket's async_receive() method to read a packet + /// of data from a remote endpoint. Arrival of the data is signalled via a + /// call to the callback function. /// /// \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 offset Offset into buffer where data is to be put /// \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); + virtual void asyncReceive(void* data, size_t length, size_t offset, + IOEndpoint* endpoint, C& callback); /// \brief Checks if the data received is complete. /// @@ -144,13 +166,24 @@ private: asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket asio::ip::tcp::socket& socket_; ///< Socket bool isopen_; ///< true when socket is open + + // TODO: Remove temporary buffer + // The current implementation copies the buffer passed to asyncSend() into + // a temporary buffer and precedes it with a two-byte count field. As + // ASIO should really be just about sendiong and receiving data, the TCP + // code should not do this. If the protocol using this requires a two-byte + // count, it should add it before calling this code. (This may be best + // achieved by altering isc::dns::buffer to have pairs of methods: + // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx() + // methods taking into account a two-byte count field.) + isc::dns::OutputBufferPtr send_buffer_; ///< Send buffer }; // Constructor - caller manages socket template TCPSocket::TCPSocket(asio::ip::tcp::socket& socket) : - socket_ptr_(NULL), socket_(socket), isopen_(true) + socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_() { } @@ -171,16 +204,16 @@ TCPSocket::~TCPSocket() delete socket_ptr_; } -// Open the socket. Throws an error on failure -// TODO: Make the open more resilient +// Open the socket. -template bool -TCPSocket::open(const IOEndpoint* endpoint, C&) { +template void +TCPSocket::open(const IOEndpoint* endpoint, C& callback) { // 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. + std::cerr << "TCPSocket::open(): open_ flags is " << isopen_ << "\n"; if (!isopen_) { if (endpoint->getFamily() == AF_INET) { socket_.open(asio::ip::tcp::v4()); @@ -190,35 +223,57 @@ TCPSocket::open(const IOEndpoint* endpoint, C&) { } isopen_ = true; - // TODO: Complete TCPSocket::open() + // Set options on the socket: + // Reuse address - allow the socket to bind to a port even if the port + // is in the TIMED_WAIT state. + socket_.set_option(asio::socket_base::reuse_address(true)); } - return (false); + + // Upconvert to a TCPEndpoint. We need to do this because although + // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not + // contain a method for getting at the underlying endpoint type - that is in + /// the derived class and the two classes differ on return type. + assert(endpoint->getProtocol() == IPPROTO_TCP); + const TCPEndpoint* tcp_endpoint = + static_cast(endpoint); + + // Connect to the remote endpoint. On success, the handler will be + // called (with one argument - the length argument will default to + // zero). + socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback); } // 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) +TCPSocket::asyncSend(const void* data, size_t length, const IOEndpoint*, + 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. + // Need to copy the data into a temporary buffer and precede it with + // a two-byte count field. + // TODO: arrange for the buffer passed to be preceded by the count + try { + // Ensure it fits into 16 bits + uint16_t count = boost::numeric_cast(length); - 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"; + // Copy data into a buffer preceded by the count field. + send_buffer_.reset(new isc::dns::OutputBuffer(length + 2)); + send_buffer_->writeUint16(count); + send_buffer_->writeData(data, length); - // TODO: Complete TCPSocket::asyncSend() + // ... and send it + std::cerr << "TCPSocket::asyncSend(): sending " << count << " data bytes\n"; + + socket_.async_send(asio::buffer(send_buffer_->getData(), + send_buffer_->getLength()), callback); + } catch (boost::numeric::bad_numeric_cast& e) { + isc_throw(BufferTooLarge, + "attempt to send buffer larger than 64kB"); + } } else { isc_throw(SocketNotOpen, diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index ff4a745ffe..ded145c17c 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -25,6 +25,8 @@ run_unittests_SOURCES += io_socket_unittest.cc run_unittests_SOURCES += io_service_unittest.cc run_unittests_SOURCES += interval_timer_unittest.cc run_unittests_SOURCES += recursive_query_unittest.cc +run_unittests_SOURCES += tcp_endpoint_unittest.cc +run_unittests_SOURCES += tcp_socket_unittest.cc run_unittests_SOURCES += udp_endpoint_unittest.cc run_unittests_SOURCES += udp_socket_unittest.cc diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index d21f03f1ae..a265d6e5ed 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -16,6 +16,7 @@ #include #include #include +#include #include @@ -29,12 +30,14 @@ #include #include +#include +#include #include #include using namespace asio; using namespace isc::dns; -using asio::ip::udp; +using namespace asio::ip; namespace asiolink { @@ -51,13 +54,16 @@ public: 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 + OutputBufferPtr result_buff_; ///< Buffer to hold result of fetch + OutputBufferPtr msgbuf_; ///< Buffer corresponding to known question IOFetch udp_fetch_; ///< For UDP query test - //IOFetch tcp_fetch_; ///< For TCP query test + IOFetch tcp_fetch_; ///< For TCP query test + IOFetch::Protocol protocol_; ///< Protocol being tested - // 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 + // The next member is the buffer in which the "server" (implemented by the + // response handler methods in this class) receives the question sent by the + // fetch object. + uint8_t server_buff_[512]; ///< Server buffer /// \brief Constructor IOFetchTest() : @@ -65,106 +71,293 @@ public: expected_(IOFetch::NOTSET), run_(false), question_(Name("example.net"), RRClass::IN(), RRType::A()), - buff_(new OutputBuffer(512)), + result_buff_(new OutputBuffer(512)), + msgbuf_(new OutputBuffer(512)), udp_fetch_(IOFetch::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) { - + TEST_PORT, result_buff_, this, 100), + tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST), + TEST_PORT, result_buff_, this, 1000), + protocol_(IOFetch::TCP) // for initialization - will be changed + { // 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); + MessageRenderer renderer(*msgbuf_); msg.toWire(renderer); + } + + /// \brief Read uint16_t from network-byte-order buffer + /// + /// Adapted from isc::dns::InputBuffer::readUint16(). + /// + /// \param data Pointer to at least two bytes of data which are in network + /// byte order. + /// + /// \return uint16_t value in host byte order. + uint16_t readUint16(const void* data) { + const uint8_t* cp = static_cast(data); + + uint16_t value = ((unsigned int)(cp[0])) << 8; + value |= ((unsigned int)(cp[1])); + + return (value); + } + + /// \brief Write uint16_t to network-byte-order buffer + /// + /// Adapted from isc::dns::OutputBuffer::writeUint16(). + /// + /// \param value The 16-bit integer to be written into the buffer. + /// \param data Pointer to buffer at least two bytes long + void writeUint16(uint16_t value, uint8_t* data) { + data[0] = static_cast((value & 0xff00U) >> 8); + data[1] = static_cast(value & 0x00ffU); + } + + /// \brief UDP Response handler (the "remote UDP DNS server") + /// + /// When IOFetch is sending data, this response handler emulates the remote + /// DNS server. It checks that the data sent by the IOFetch object is what + /// was expected to have been sent, then sends back a known buffer of data. + /// + /// \param remote Endpoint to which to send the answer + /// \param socket Socket to use to send the answer + /// \param ec ASIO error code, completion code of asynchronous I/O issued + /// by the "server" to receive data. + /// \param length Amount of data received. + void udpReceiveHandler(udp::endpoint* remote, udp::socket* socket, + error_code ec = error_code(), size_t length = 0) { // 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.) + // data comparison check. (It is set to 0 in the buffer containing + // the expected data.) 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); + // Check that length of the received data and the expected data are + // identical, then check that the data is identical as well. + EXPECT_EQ(msgbuf_->getLength(), length); + EXPECT_TRUE(memcmp(msgbuf_->getData(), server_buff_, length) == 0); - // ... and return a message back. + // Return a message back to the IOFetch object. socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA), *remote); } + + /// \brief Completion Handler for accepting TCP data + /// + /// Called when the remote system connects to the "server". It issues + /// an asynchronous read on the socket to read data. + /// + /// \param socket Socket on which data will be received + /// \param ec Boost error code, value should be zero. + void tcpAcceptHandler(tcp::socket* socket, error_code ec = error_code()) + { + std::cerr << "TCP Accept Handler\n"; + EXPECT_EQ(0, ec.value()); // Expect no error + + // Initiate a read on the socket + socket->async_receive(asio::buffer(server_buff_, sizeof(server_buff_)), + boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2)); + } + + /// \brief Completion handler for receiving TCP data + /// + /// When IOFetch is sending data, this response handler emulates the remote + /// DNS server. It checks that the data sent by the IOFetch object is what + /// was expected to have been sent, then sends back a known buffer of data. + /// + /// \param socket Socket to use to send the answer + /// \param ec ASIO error code, completion code of asynchronous I/O issued + /// by the "server" to receive data. + /// \param length Amount of data received. + void tcpReceiveHandler(tcp::socket* socket, error_code ec = error_code(), + size_t length = 0) + { + std::cerr << "TCP Receive Handler\n"; + // TODO - need to loop until all the data is received. + + // Interpret the received data. The first two bytes, when converted + // to host byte order, are the count of the length of the message. + EXPECT_GE(2, length); + uint16_t dns_length = readUint16(server_buff_); + EXPECT_EQ(length, dns_length + 2); + + // Check that length of the DNS message received is that expected. + EXPECT_EQ(msgbuf_->getLength(), dns_length); + + // Compare buffers, zeroing the QID in the received buffer to match + // that set in our expected question. Note that due to the length + // field the QID in the received buffer is in the thrid and fourth + // bytes. + server_buff_[2] = server_buff_[3] = 0; + EXPECT_TRUE(memcmp(msgbuf_->getData(), server_buff_ + 2, dns_length) == 0); + + // ... and return a message back. This has to be preceded by a two-byte + // count field. It's simpler to do this as two writes - it shouldn't + // make any difference to the IOFetch object. + uint8_t count[2]; + writeUint16(sizeof(TEST_DATA), count); + socket->async_send(asio::buffer(count, 2), + boost::bind(&IOFetchTest::tcpSendHandler, this, + sizeof(count), _1, _2)); + socket->async_send(asio::buffer(TEST_DATA, sizeof(TEST_DATA)), + boost::bind(&IOFetchTest::tcpSendHandler, this, + sizeof(count), _1, _2)); + } + + /// \brief Completion Handler for Sending TCP data + /// + /// Called when the asynchronous send of data back to the IOFetch object + /// by the TCP "server" in this class has completed. (This send has to + /// be asynchronous because control needs to return to the caller in order + /// for the IOService "run()" method to be called to run the handlers.) + /// + /// \param expected Number of bytes that were expected to have been sent. + /// \param ec Boost error code, value should be zero. + /// \param length Number of bytes sent. + void tcpSendHandler(size_t expected = 0, error_code ec = error_code(), + size_t length = 0) + { + std::cerr << "TCP Send Handler\n"; + EXPECT_EQ(0, ec.value()); // Expect no error + EXPECT_EQ(expected, length); // And that amount sent is as expected + } + + /// \brief Fetch completion callback + /// + /// This is the callback's operator() method which is called when the fetch + /// is complete. It checks that the data received is the wire format of the + /// data sent back by the server. + /// + /// \param result Result indicated by the callback + void operator()(IOFetch::Result result) { + std::cerr << "Fetch completion\n"; + EXPECT_EQ(expected_, result); // Check correct result returned + EXPECT_FALSE(run_); // Check it is run only once + run_ = true; // Note success + + // If the expected result for SUCCESS, then this should have been called + // when one of the "servers" in this class has sent back the TEST_DATA. + // Check the data is as expected/ + if (expected_ == IOFetch::SUCCESS) { + size_t offset = 0; // Offset into start of buffer of data + if (protocol_ == IOFetch::UDP) { + + // Check the length of data received against the amount expected. + EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength()); + + } else { + + // Check the length of data received against the amount expected + EXPECT_EQ(sizeof(TEST_DATA) + 2, result_buff_->getLength()); + + // Check the count field. This should be equal to the total + // length of the packet less 2 (the count field is equal to + // the total length of the message less the count field itself - + // RFC 1035, section 4.2.2). + uint16_t count = readUint16(result_buff_->getData()); + EXPECT_EQ(result_buff_->getLength(), count + 2); + + // Update offset and count for the content check. + offset += 2; + } + const void* start = static_cast( + static_cast(result_buff_->getData()) + offset); + EXPECT_TRUE(memcmp(TEST_DATA, start, sizeof(TEST_DATA)) == 0); + } + + // ... and cause the run loop to exit. + service_.stop(); + } + + // The next set of methods are the tests themselves. A number of the TCP + // and UDP tests are very similar. + + /// \brief Check for stop() + /// + /// 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.) + /// + /// \param protocol Test protocol + /// \param fetch Fetch object being tested + void stopTest(IOFetch::Protocol protocol, IOFetch& fetch) { + protocol_ = protocol; + expected_ = IOFetch::STOPPED; + + // Post the query + service_.get_io_service().post(fetch); + + // Post query_.stop() (yes, the boost::bind thing is just + // query_.stop()). + service_.get_io_service().post( + boost::bind(&IOFetch::stop, fetch, IOFetch::STOPPED)); + + // Run both of them. run() returns when everything in the I/O service + // queue has completed. + service_.run(); + EXPECT_TRUE(run_); + } + + /// \brief Premature stop test + /// + /// 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). + /// + /// \param protocol Test protocol + /// \param fetch Fetch object being tested + void prematureStopTest(IOFetch::Protocol protocol, IOFetch& fetch) { + protocol_ = protocol; + expected_ = IOFetch::STOPPED; + + // Stop before it is started + fetch.stop(); + service_.get_io_service().post(fetch); + + service_.run(); + EXPECT_TRUE(run_); + } + + /// \brief Timeout test + /// + /// Test that fetch times out when no answer arrives. + /// + /// \param protocol Test protocol + /// \param fetch Fetch object being tested + void timeoutTest(IOFetch::Protocol protocol, IOFetch& fetch) { + protocol_ = protocol; + expected_ = IOFetch::TIME_OUT; + + service_.get_io_service().post(fetch); + service_.run(); + EXPECT_TRUE(run_); + } }; -/// 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. +/// UDP Stop test - see IOFetchTest::stopTest() header. 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_); + stopTest(IOFetch::UDP, udp_fetch_); } -// 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). +/// UDP premature stop test - see IOFetchTest::prematureStopTest() header. 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_); + prematureStopTest(IOFetch::UDP, udp_fetch_); } -// Test that it will timeout when no answer arrives. +/// UDP premature stop test - see IOFetchTest::timeoutTest() header. TEST_F(IOFetchTest, UdpTimeout) { - expected_ = IOFetch::TIME_OUT; - - service_.get_io_service().post(udp_fetch_); - service_.run(); - EXPECT_TRUE(run_); + timeoutTest(IOFetch::UDP, udp_fetch_); } -// 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) { +// UDP SendReceive test. Set up a UDP server then ports a UDP fetch object. +// This will send question_ to the server and receive the answer back from it. +TEST_F(IOFetchTest, UdpSendReceive) { + protocol_ = IOFetch::UDP; expected_ = IOFetch::SUCCESS; udp::socket socket(service_.get_io_service(), udp::v4()); @@ -174,15 +367,56 @@ TEST_F(IOFetchTest, UdpReceive) { udp::endpoint remote; socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)), remote, - boost::bind(&IOFetchTest::respond, this, &remote, &socket, _1, _2)); + boost::bind(&IOFetchTest::udpReceiveHandler, 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)); + EXPECT_TRUE(run_);; +} + +// Do the same tests for TCP transport + +TEST_F(IOFetchTest, TcpStop) { + stopTest(IOFetch::TCP, tcp_fetch_); +} + +TEST_F(IOFetchTest, TcpPrematureStop) { + prematureStopTest(IOFetch::TCP, tcp_fetch_); +} + +TEST_F(IOFetchTest, TcpTimeout) { + timeoutTest(IOFetch::TCP, tcp_fetch_); +} + +TEST_F(IOFetchTest, TcpSendReceive) { + protocol_ = IOFetch::TCP; + expected_ = IOFetch::SUCCESS; + + std::cerr << "Creating socket\n"; + // Socket into which the connection will be accepted + tcp::socket socket(service_.get_io_service()); + + std::cerr << "Creating acceptor\n"; + // Acceptor object - called when the connection is made, the handler will + // initiate a read on the socket. + tcp::acceptor acceptor(service_.get_io_service(), + tcp::endpoint(tcp::v4(), TEST_PORT)); + std::cerr << "Issuing async accept call\n"; + acceptor.async_accept(socket, + boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); + + // Post the TCP fetch object to send the query and receive the response. + std::cerr << "Posting TCP fetch\n"; + service_.get_io_service().post(tcp_fetch_); + + // ... and execute all the callbacks. This exits when the fetch completes. + service_.run(); + EXPECT_TRUE(run_); // Make sure the callback did execute + + socket.close(); } } // namespace asiolink diff --git a/src/lib/asiolink/tests/tcp_endpoint_unittest.cc b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc new file mode 100644 index 0000000000..3787e1c152 --- /dev/null +++ b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc @@ -0,0 +1,55 @@ +// 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 + +using namespace asiolink; +using namespace std; + +// This test checks that the endpoint can manage its own internal +// asio::ip::tcp::endpoint object. + +TEST(TCPEndpointTest, v4Address) { + const string test_address("192.0.2.1"); + const unsigned short test_port = 5301; + + IOAddress address(test_address); + TCPEndpoint endpoint(address, test_port); + + EXPECT_TRUE(address == endpoint.getAddress()); + EXPECT_EQ(test_port, endpoint.getPort()); + EXPECT_EQ(IPPROTO_TCP, endpoint.getProtocol()); + EXPECT_EQ(AF_INET, endpoint.getFamily()); +} + +TEST(TCPEndpointTest, v6Address) { + const string test_address("2001:db8::1235"); + const unsigned short test_port = 5302; + + IOAddress address(test_address); + TCPEndpoint endpoint(address, test_port); + + EXPECT_TRUE(address == endpoint.getAddress()); + EXPECT_EQ(test_port, endpoint.getPort()); + EXPECT_EQ(IPPROTO_TCP, endpoint.getProtocol()); + EXPECT_EQ(AF_INET6, endpoint.getFamily()); +} diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc new file mode 100644 index 0000000000..d37f23677d --- /dev/null +++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc @@ -0,0 +1,349 @@ +// 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. + +/// \brief Test of TCPSocket +/// +/// Tests the fuctionality of a TCPSocket by working through an open-send- +/// receive-close sequence and checking that the asynchronous notifications +/// work. + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include + +using namespace asio; +using namespace asio::ip; +using namespace asiolink; +using namespace std; + +namespace { + +const char SERVER_ADDRESS[] = "127.0.0.1"; +const unsigned short SERVER_PORT = 5303; + +// TODO: Shouldn't we send something that is real message? +const char OUTBOUND_DATA[] = "Data sent from client to server"; +const char INBOUND_DATA[] = "Returned data from server to client"; +} + +/// +/// An instance of this object is passed to the asynchronous I/O functions +/// 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 TCPCallback { +public: + /// \brief Operations the server is doing + enum Operation { + ACCEPT = 0, ///< accept() was issued + OPEN = 1, /// Client connected to server + READ = 2, ///< Asynchronous read completed + WRITE = 3, ///< Asynchronous write completed + NONE = 4 ///< "Not set" state + }; + + /// \brief Minimim size of buffers + enum { + MIN_SIZE = 4096 + }; + + struct PrivateData { + PrivateData() : + error_code_(), length_(0), name_(""), queued_(NONE), called_(NONE) + {} + + asio::error_code error_code_; ///< Completion error code + size_t length_; ///< Number of bytes transferred + std::string name_; ///< Which of the objects this is + Operation queued_; ///< Queued operation + Operation called_; ///< Which callback called + }; + + /// \brief Constructor + /// + /// Constructs the object. It also creates the data member pointed to by + /// a shared pointer. When used as a callback object, this is copied as it + /// is passed into the asynchronous function. This means that there are two + /// objects and inspecting the one we passed in does not tell us anything. + /// + /// Therefore we use a boost::shared_ptr. When the object is copied, the + /// shared pointer is copied, which leaves both objects pointing to the same + /// data. + /// + /// \param which Which of the two callback objects this is + TCPCallback(std::string which) : ptr_(new PrivateData()) + { + setName(which); + } + + /// \brief Destructor + /// + /// No code needed, destroying the shared pointer destroys the private data. + virtual ~TCPCallback() + {} + + /// \brief Client Callback Function + /// + /// Called when an asynchronous connect is completed by the client, this + /// stores the origin of the operation in the client_called_ data member. + /// + /// \param ec I/O completion error code passed to callback function. + /// \param length Number of bytes transferred + void operator()(asio::error_code ec = asio::error_code(), + size_t length = 0) + { + setCode(ec.value()); + setCalled(getQueued()); + setLength(length); + } + + /// \brief Get I/O completion error code + int getCode() { + return (ptr_->error_code_.value()); + } + + /// \brief Set I/O completion code + /// + /// \param code New value of completion code + void setCode(int code) { + ptr_->error_code_ = asio::error_code(code, asio::error_code().category()); + } + + /// \brief Get number of bytes transferred in I/O + size_t getLength() { + return (ptr_->length_); + } + + /// \brief Set number of bytes transferred in I/O + /// + /// \param length New value of length parameter + void setLength(size_t length) { + ptr_->length_ = length; + } + + /// \brief Get flag to say what was queued + Operation getQueued() { + return (ptr_->queued_); + } + + /// \brief Set flag to say what is being queued + /// + /// \param called New value of queued parameter + void setQueued(Operation queued) { + ptr_->queued_ = queued; + } + + /// \brief Get flag to say when callback was called + Operation getCalled() { + return (ptr_->called_); + } + + /// \brief Set flag to say when callback was called + /// + /// \param called New value of called parameter + void setCalled(Operation called) { + ptr_->called_ = called; + } + + /// \brief Return instance of callback name + std::string getName() { + return (ptr_->name_); + } + + /// \brief Set callback name + /// + /// \param name New value of the callback name + void setName(const std::string& name) { + ptr_->name_ = name; + } + +private: + boost::shared_ptr ptr_; ///< Pointer to private data +}; + +// TODO: Need to add a test to check the cancel() method + +// Tests the operation of a TCPSocket by opening it, sending an asynchronous +// message to a server, receiving an asynchronous message from the server and +// closing. +TEST(TCPSocket, SequenceTest) { + + // Common objects. + IOService service; // Service object for async control + + // Server + IOAddress server_address(SERVER_ADDRESS); + // Address of target server + TCPCallback server_cb("Server"); // Server callback + TCPEndpoint server_endpoint(server_address, SERVER_PORT); + // Endpoint describing server + TCPEndpoint server_remote_endpoint; // Address where server received message from + tcp::socket server_socket(service.get_io_service()); + // Socket used for server + char server_data[TCPCallback::MIN_SIZE]; + // Data received by server + ASSERT_GT(sizeof(server_data), sizeof(OUTBOUND_DATA)); + // Make sure it's large enough + + // The client - the TCPSocket being tested + TCPSocket client(service);// Socket under test + TCPCallback client_cb("Client"); // Async I/O callback function + TCPEndpoint client_remote_endpoint; // Where client receives message from + char client_data[TCPCallback::MIN_SIZE]; + // Data received by client + ASSERT_GT(sizeof(client_data), sizeof(OUTBOUND_DATA)); + // Make sure it's large enough + //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. + + std::cerr << "Setting up acceptor\n"; + // Set up the server to accept incoming connections. + server_cb.setQueued(TCPCallback::ACCEPT); + server_cb.setCalled(TCPCallback::NONE); + server_cb.setCode(42); // Some error + tcp::acceptor acceptor(service.get_io_service(), + tcp::endpoint(tcp::v4(), SERVER_PORT)); + acceptor.set_option(tcp::acceptor::reuse_address(true)); + acceptor.async_accept(server_socket, server_cb); + + std::cerr << "Setting up client\n"; + // Open the client socket - the operation should be asynchronous + client_cb.setQueued(TCPCallback::OPEN); + client_cb.setCalled(TCPCallback::NONE); + client_cb.setCode(43); // Some error + EXPECT_FALSE(client.isOpenSynchronous()); + client.open(&server_endpoint, client_cb); + + // Run the open and the accept callback and check that they ran. + service.run_one(); + service.run_one(); + + EXPECT_EQ(TCPCallback::ACCEPT, server_cb.getCalled()); + EXPECT_EQ(0, server_cb.getCode()); + EXPECT_EQ(TCPCallback::OPEN, client_cb.getCalled()); + EXPECT_EQ(0, client_cb.getCode()); + + // Write something to the server using the client and read it on ther server. + server_cb.setCalled(TCPCallback::NONE); + server_cb.setQueued(TCPCallback::READ); + server_cb.setCode(142); // Arbitrary number + server_cb.setLength(0); + server_socket.async_receive(buffer(server_data, sizeof(server_data)), server_cb); + + client_cb.setCalled(TCPCallback::NONE); + client_cb.setQueued(TCPCallback::WRITE); + client_cb.setCode(143); // Arbitrary number + client_cb.setLength(0); + client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb); + + // Run the write and read callback and check they ran + service.run_one(); + service.run_one(); + + // Check lengths. As the client wrote the buffer, currently two bytes + // will be prepended by the client containing the length. + EXPECT_EQ(TCPCallback::READ, server_cb.getCalled()); + EXPECT_EQ(0, server_cb.getCode()); + EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.getLength()); + + EXPECT_EQ(TCPCallback::WRITE, client_cb.getCalled()); + EXPECT_EQ(0, client_cb.getCode()); + EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.getLength()); + + // Check that the first two bytes of the buffer are in fact the remaining + // length of the buffer (code copied from isc::dns::buffer.h) + uint16_t count = ((unsigned int)(server_data[0])) << 8; + count |= ((unsigned int)(server_data[1])); + EXPECT_EQ(sizeof(OUTBOUND_DATA), count); + + // ... and check data received by server is what we expect + EXPECT_TRUE(equal(&server_data[2], &server_data[server_cb.getLength() - 1], + OUTBOUND_DATA)); + + // TODO: Complete this server test + // TODO: Add in loop for server to read data - read 2 bytes, then as much as needed + + /* + // Now return data from the server to the client. Issue the read on the + // client. + client_cb.setCalled(TCPCallback::NONE); + client_cb.setQueued(TCPCallback::READ); + client_cb.setCode(143); // Arbitrary number + client_cb.setLength(0); + client.asyncReceive(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb); + + client_cb.setLength(12345); // Arbitrary number + client_cb.setCalled(false); + client_cb.setCode(32); // Arbitrary number + client.asyncReceive(data, sizeof(data), client_cumulative, + &client_remote_endpoint, client_cb); + + // Issue the write on the server side to the source of the data it received. + server_cb.setLength(22345); // Arbitrary number + server_cb.setCalled(false); + server_cb.setCode(232); // Arbitrary number + server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)), + server_remote_endpoint.getASIOEndpoint(), server_cb); + + // Expect two callbacks to run + service.get_io_service().poll(); + //service.run_one(); + + EXPECT_TRUE(client_cb.getCalled()); + EXPECT_EQ(0, client_cb.getCode()); + EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength()); + + EXPECT_TRUE(server_cb.getCalled()); + EXPECT_EQ(0, server_cb.getCode()); + EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength()); + + EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA)); + + // Check that the address/port received by the client corresponds to the + // address and port the server is listening on. + EXPECT_TRUE(server_address == client_remote_endpoint.getAddress()); + EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort()); + + // Finally, check that the receive received a complete buffer's worth of data. + EXPECT_TRUE(client.receiveComplete(&data[0], client_cb.getLength(), + client_cumulative)); + EXPECT_EQ(client_cb.getLength(), client_cumulative); + + // Close client and server. + EXPECT_NO_THROW(client.close()); + EXPECT_NO_THROW(server.close()); + * */ +} diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc index 7332d29ffe..7b81a6205a 100644 --- a/src/lib/asiolink/tests/udp_socket_unittest.cc +++ b/src/lib/asiolink/tests/udp_socket_unittest.cc @@ -12,21 +12,6 @@ // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -// 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. - - /// \brief Test of UDPSocket /// /// Tests the fuctionality of a UDPSocket by working through an open-send- @@ -208,11 +193,12 @@ TEST(UDPSocket, SequenceTest) { 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::MIN_SIZE]; ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA)); // Open the client socket - the operation should be synchronous - EXPECT_FALSE(client.open(&server_endpoint, client_cb)); + EXPECT_TRUE(client.isOpenSynchronous()); + client.open(&server_endpoint, client_cb); // Issue read on the server. Completion callback should not have run. server_cb.setCalled(false); diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h index 0958af6e4d..99dc27ffee 100644 --- a/src/lib/asiolink/udp_endpoint.h +++ b/src/lib/asiolink/udp_endpoint.h @@ -64,6 +64,17 @@ public: asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint) {} + /// \brief Constructor from an ASIO UDP endpoint. + /// + /// This constructor is designed to be an efficient wrapper for the + /// corresponding ASIO class, \c udp::endpoint. + /// + /// \param asio_endpoint The ASIO representation of the TCP endpoint. + UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) : + asio_endpoint_placeholder_(new asio::ip::udp::endpoint(asio_endpoint)), + asio_endpoint_(*asio_endpoint_placeholder_) + {} + /// \brief The destructor. virtual ~UDPEndpoint() { delete asio_endpoint_placeholder_; } //@} diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index 56a9bb0972..0df6fba175 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -48,7 +48,7 @@ private: public: enum { - MAX_SIZE = 4096 // Send and receive size + MIN_SIZE = 4096 // Minimum send and receive size }; /// \brief Constructor from an ASIO UDP socket. @@ -79,24 +79,26 @@ public: return (IPPROTO_UDP); } + /// \brief Is "open()" synchronous? + /// + /// Indicates that the opening of a UDP socket is synchronous. + virtual bool isOpenSynchronous() const { + return true; + } + /// \brief Open Socket /// - /// Opens the UDP 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 UDP, so the method returns - /// "false" to indicate that the operation completed synchronously. + /// Opens the UDP socket. This is a synchronous operation. /// /// \param endpoint Endpoint to which the socket will connect to. /// \param callback Unused. - /// - /// \return false to indicate that the "operation" completed synchronously. - virtual bool open(const IOEndpoint* endpoint, C&); + virtual void open(const IOEndpoint* endpoint, C&); /// \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. + /// Calls the underlying socket's async_send_to() method to send a packet of + /// data asynchronously to the remote endpoint. The callback will be called + /// on completion. /// /// \param data Data to send /// \param length Length of data to send @@ -107,19 +109,17 @@ public: /// \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. + /// Calls the underlying socket's async_receive_from() method to read a + /// packet of data from a remote endpoint. Arrival of the data is + /// signalled via a call to the callback function. /// /// \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 offset Offset into buffer where data is to be put /// \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); + virtual void asyncReceive(void* data, size_t length, size_t offset, + IOEndpoint* endpoint, C& callback); /// \brief Checks if the data received is complete. /// @@ -133,7 +133,7 @@ public: /// 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. + /// needed. Always true for a UDP socket. virtual bool receiveComplete(void*, size_t length, size_t& cumulative) { cumulative = length; return (true); @@ -180,10 +180,9 @@ UDPSocket::~UDPSocket() delete socket_ptr_; } -// Open the socket. Throws an error on failure -// TODO: Make the open more resilient +// Open the socket. -template bool +template void UDPSocket::open(const IOEndpoint* endpoint, C&) { // Ignore opens on already-open socket. Don't throw a failure because @@ -203,21 +202,18 @@ UDPSocket::open(const IOEndpoint* endpoint, C&) { asio::ip::udp::socket::send_buffer_size snd_size; socket_.get_option(snd_size); - if (snd_size.value() < MAX_SIZE) { - snd_size = MAX_SIZE; + if (snd_size.value() < MIN_SIZE) { + snd_size = MIN_SIZE; socket_.set_option(snd_size); } asio::ip::udp::socket::receive_buffer_size rcv_size; socket_.get_option(rcv_size); - if (rcv_size.value() < MAX_SIZE) { - rcv_size = MAX_SIZE; + if (rcv_size.value() < MIN_SIZE) { + rcv_size = MIN_SIZE; socket_.set_option(rcv_size); } } - - // Nothing was done asynchronously, so tell the caller that. - return (false); } // Send a message. Should never do this if the socket is not open, so throw @@ -225,7 +221,7 @@ UDPSocket::open(const IOEndpoint* endpoint, C&) { template void UDPSocket::asyncSend(const void* data, size_t length, - const IOEndpoint* endpoint, C& callback) + const IOEndpoint* endpoint, C& callback) { if (isopen_) { @@ -252,8 +248,8 @@ UDPSocket::asyncSend(const void* data, size_t length, // the need for the socket to be open. template void -UDPSocket::asyncReceive(void* data, size_t length, size_t, - IOEndpoint* endpoint, C& callback) +UDPSocket::asyncReceive(void* data, size_t length, size_t offset, + IOEndpoint* endpoint, C& callback) { if (isopen_) { @@ -261,7 +257,15 @@ UDPSocket::asyncReceive(void* data, size_t length, size_t, assert(endpoint->getProtocol() == IPPROTO_UDP); UDPEndpoint* udp_endpoint = static_cast(endpoint); - socket_.async_receive_from(asio::buffer(data, length), + // Ensure we can write into the buffer + if (offset >= length) { + isc_throw(BufferOverflow, "attempt to read into area beyond end of " + "UDP receive buffer"); + } + void* buffer_start = static_cast(static_cast(data) + offset); + + // Issue the read + socket_.async_receive_from(asio::buffer(buffer_start, length - offset), udp_endpoint->getASIOEndpoint(), callback); } else { isc_throw(SocketNotOpen, From 86355ec5ded2f1988dc466f8844360c78e8454a7 Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Mon, 28 Feb 2011 14:42:28 +0800 Subject: [PATCH 011/113] [trac595] Refactor the code according Jelte's review. --- src/lib/cache/message_entry.cc | 48 ++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc index 5f42ed5c8b..fb59916fb3 100644 --- a/src/lib/cache/message_entry.cc +++ b/src/lib/cache/message_entry.cc @@ -26,9 +26,13 @@ using namespace std; // Put file scope functions in unnamed namespace. namespace { -// Get the deepest owner name of DNAME record for the given query name. +// Get the shortest existing ancestor which is the owner name of +// one DNAME record for the given query name. +// Note: there maybe multiple DNAME records(DNAME chain) in answer section, +// in most case they are in order, but the code can't depends on it, it has to +// find the starter by iterating the DNAME chain. Name -getDeepestDNAMEOwner(const Message& message, const Name& query_name) { +getDNAMEChainStarter(const Message& message, const Name& query_name) { Name dname = query_name; RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { @@ -45,23 +49,6 @@ getDeepestDNAMEOwner(const Message& message, const Name& query_name) { return (dname); } -// Check whether answer section in given message has non-authoritative rrsets. -bool -answerHasNonAuthRecord(const Message& message, const Name& query_name) { - RRsetIterator rrset_iter = message.beginSection(Message::SECTION_ANSWER); - while(rrset_iter != message.endSection(Message::SECTION_ANSWER)) { - // Here, only check CNAME is enough. If there is - // cname record whose ower name is same with query name, answer - // section may has non-authoritative rrsets. - if ((*rrset_iter)->getType() == RRType::CNAME() && - (*rrset_iter)->getName() == query_name) { - return (true); - } - ++rrset_iter; - } - return (false); -} - } // End of unnamed namespace namespace isc { @@ -173,17 +160,32 @@ MessageEntry::getRRsetTrustLevel(const Message& message, // from it are authoritative, any other records in answer // section are non-authoritative. QuestionIterator quest_iter = message.beginQuestion(); + // Make sure question section is not empty. + assert( quest_iter != message.endQuestion()); + const Name& query_name = (*quest_iter)->getName(); const RRType& type = rrset->getType(); const Name& name = rrset->getName(); if ((type == RRType::CNAME() && name == query_name) || (type == RRType::DNAME() && - name == getDeepestDNAMEOwner(message, query_name))) { + name == getDNAMEChainStarter(message, query_name))) { return (RRSET_TRUST_ANSWER_AA); - } else if (answerHasNonAuthRecord(message, query_name)) { - return (RRSET_TRUST_ANSWER_NONAA); + } else { + // If there is one CNAME record whose ower name is same with + // query name in answer section, the left records in answer + // section are non-authoritative, except the starter of DNAME + // chain(only checking CNAME is enough, because if the CNAME + // record is synchronized from one DNAME record, that DNAME + // record must be the starter of DNAME chain). + RRsetIterator iter = message.beginSection(Message::SECTION_ANSWER); + while(iter != message.endSection(Message::SECTION_ANSWER)) { + if ((*iter)->getType() == RRType::CNAME() && + (*iter)->getName() == query_name) { + return (RRSET_TRUST_ANSWER_NONAA); + } + ++iter; + } } - return (RRSET_TRUST_ANSWER_AA); } else { return (RRSET_TRUST_ANSWER_NONAA); From 8c7144c6aef7eb26215f045ee95551ed7a6fe307 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Mon, 28 Feb 2011 14:39:28 +0100 Subject: [PATCH 012/113] [trac523] Change test ports to 53210 This is in the private range, so it should not conflict with anything (we still might be unlucky if two tests run at the same time, but the chance is low). --- src/bin/auth/benchmarks/query_bench.cc | 2 +- src/bin/auth/tests/auth_srv_unittest.cc | 2 +- src/lib/asiolink/tests/io_service_unittest.cc | 2 +- src/lib/asiolink/tests/ioendpoint_unittest.cc | 6 +++--- src/lib/testutils/portconfig.h | 12 ++++++------ src/lib/testutils/srv_test.cc | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc index 7f643f3a6d..5e69134900 100644 --- a/src/bin/auth/benchmarks/query_bench.cc +++ b/src/bin/auth/benchmarks/query_bench.cc @@ -77,7 +77,7 @@ protected: dummy_socket(IOSocket::getDummyUDPSocket()), dummy_endpoint(IOEndpointPtr(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), - 5300))) + 53210))) {} public: unsigned int run() { diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc index 54dc0c739a..379342e046 100644 --- a/src/bin/auth/tests/auth_srv_unittest.cc +++ b/src/bin/auth/tests/auth_srv_unittest.cc @@ -644,7 +644,7 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) { // Modify the message. delete io_message; endpoint = IOEndpoint::create(IPPROTO_UDP, - IOAddress(DEFAULT_REMOTE_ADDRESS), 5300); + IOAddress(DEFAULT_REMOTE_ADDRESS), 53210); io_message = new IOMessage(request_renderer.getData(), request_renderer.getLength(), getDummyUnknownSocket(), *endpoint); diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc index 49aa67ec38..7c23e7a39b 100644 --- a/src/lib/asiolink/tests/io_service_unittest.cc +++ b/src/lib/asiolink/tests/io_service_unittest.cc @@ -27,7 +27,7 @@ const char* const TEST_IPV4_ADDR = "127.0.0.1"; TEST(IOServiceTest, badPort) { IOService io_service; EXPECT_THROW(DNSService(io_service, *"65536", true, false, NULL, NULL, NULL), IOError); - EXPECT_THROW(DNSService(io_service, *"5300.0", true, false, NULL, NULL, NULL), IOError); + EXPECT_THROW(DNSService(io_service, *"53210.0", true, false, NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *"-1", true, false, NULL, NULL, NULL), IOError); EXPECT_THROW(DNSService(io_service, *"domain", true, false, NULL, NULL, NULL), IOError); } diff --git a/src/lib/asiolink/tests/ioendpoint_unittest.cc b/src/lib/asiolink/tests/ioendpoint_unittest.cc index a5b5cd2069..eda21ce73d 100644 --- a/src/lib/asiolink/tests/ioendpoint_unittest.cc +++ b/src/lib/asiolink/tests/ioendpoint_unittest.cc @@ -21,9 +21,9 @@ using namespace asiolink; TEST(IOEndpointTest, createUDPv4) { const IOEndpoint* ep; - ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5300); + ep = IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 53210); EXPECT_EQ("192.0.2.1", ep->getAddress().toText()); - EXPECT_EQ(5300, ep->getPort()); + EXPECT_EQ(53210, ep->getPort()); EXPECT_EQ(AF_INET, ep->getFamily()); EXPECT_EQ(AF_INET, ep->getAddress().getFamily()); EXPECT_EQ(IPPROTO_UDP, ep->getProtocol()); @@ -61,7 +61,7 @@ TEST(IOEndpointTest, createTCPv6) { TEST(IOEndpointTest, createIPProto) { EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"), - 5300)->getAddress().toText(), + 53210)->getAddress().toText(), IOError); } diff --git a/src/lib/testutils/portconfig.h b/src/lib/testutils/portconfig.h index c8cbe8c337..8af178b7d7 100644 --- a/src/lib/testutils/portconfig.h +++ b/src/lib/testutils/portconfig.h @@ -51,8 +51,8 @@ listenAddresses(Server& server) { // Try putting there some addresses AddressList addresses; - addresses.push_back(AddressPair("127.0.0.1", 5321)); - addresses.push_back(AddressPair("::1", 5321)); + addresses.push_back(AddressPair("127.0.0.1", 53210)); + addresses.push_back(AddressPair("::1", 53210)); server.setListenAddresses(addresses); EXPECT_EQ(2, server.getListenAddresses().size()); EXPECT_EQ("::1", server.getListenAddresses()[1].first); @@ -88,7 +88,7 @@ listenAddressConfig(Server& server) { "\"listen_on\": [" " {" " \"address\": \"127.0.0.1\"," - " \"port\": 5321" + " \"port\": 53210" " }" "]" "}")); @@ -96,7 +96,7 @@ listenAddressConfig(Server& server) { EXPECT_EQ(result->toWire(), isc::config::createAnswer()->toWire()); ASSERT_EQ(1, server.getListenAddresses().size()); EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first); - EXPECT_EQ(5321, server.getListenAddresses()[0].second); + EXPECT_EQ(53210, server.getListenAddresses()[0].second); // As this is example address, the machine should not have it on // any interface @@ -107,7 +107,7 @@ listenAddressConfig(Server& server) { "\"listen_on\": [" " {" " \"address\": \"192.0.2.0\"," - " \"port\": 5321" + " \"port\": 53210" " }" "]" "}"); @@ -115,7 +115,7 @@ listenAddressConfig(Server& server) { EXPECT_FALSE(result->equals(*isc::config::createAnswer())); ASSERT_EQ(1, server.getListenAddresses().size()); EXPECT_EQ("127.0.0.1", server.getListenAddresses()[0].first); - EXPECT_EQ(5321, server.getListenAddresses()[0].second); + EXPECT_EQ(53210, server.getListenAddresses()[0].second); } diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc index c0d6e0fef7..4fec4ca7dc 100644 --- a/src/lib/testutils/srv_test.cc +++ b/src/lib/testutils/srv_test.cc @@ -60,7 +60,7 @@ SrvTestBase::createDataFromFile(const char* const datafile, delete endpoint; endpoint = IOEndpoint::create(protocol, - IOAddress(DEFAULT_REMOTE_ADDRESS), 5300); + IOAddress(DEFAULT_REMOTE_ADDRESS), 53210); UnitTestUtil::readWireData(datafile, data); io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() : &IOSocket::getDummyTCPSocket(); @@ -76,7 +76,7 @@ SrvTestBase::createRequestPacket(Message& message, delete io_message; endpoint = IOEndpoint::create(protocol, - IOAddress(DEFAULT_REMOTE_ADDRESS), 5300); + IOAddress(DEFAULT_REMOTE_ADDRESS), 53210); io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() : &IOSocket::getDummyTCPSocket(); io_message = new IOMessage(request_renderer.getData(), From 682436e844799b2194eccc2ffdfb3fa3da527cea Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 28 Feb 2011 14:27:01 +0000 Subject: [PATCH 013/113] [trac499] Completed TCPSocket and unit tests --- src/lib/asiolink/Makefile.am | 1 + src/lib/asiolink/asiolink_utilities.h | 61 +++ src/lib/asiolink/io_asio_socket.h | 81 ++-- src/lib/asiolink/io_fetch.cc | 3 +- src/lib/asiolink/tcp_socket.h | 109 +++-- src/lib/asiolink/tests/Makefile.am | 1 + .../tests/asiolink_utilities_unittest.cc | 74 ++++ src/lib/asiolink/tests/tcp_socket_unittest.cc | 400 ++++++++++++------ src/lib/asiolink/tests/udp_socket_unittest.cc | 4 +- src/lib/asiolink/udp_socket.h | 63 ++- 10 files changed, 539 insertions(+), 258 deletions(-) create mode 100644 src/lib/asiolink/asiolink_utilities.h create mode 100644 src/lib/asiolink/tests/asiolink_utilities_unittest.cc diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index 71d31f9a6c..b6133bbffd 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -13,6 +13,7 @@ CLEANFILES = *.gcno *.gcda # which would make the build fail with -Werror (our default setting). lib_LTLIBRARIES = libasiolink.la libasiolink_la_SOURCES = asiolink.h +libasiolink_la_SOURCES += asiolink_utilities.h libasiolink_la_SOURCES += asiodef.cc asiodef.h libasiolink_la_SOURCES += dns_answer.h libasiolink_la_SOURCES += dns_lookup.h diff --git a/src/lib/asiolink/asiolink_utilities.h b/src/lib/asiolink/asiolink_utilities.h new file mode 100644 index 0000000000..f7f82be695 --- /dev/null +++ b/src/lib/asiolink/asiolink_utilities.h @@ -0,0 +1,61 @@ +// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +#ifndef __ASIOLINK_UTILITIES_H +#define __ASIOLINK_UTILITIES_H + +#include + +namespace asiolink { + +/// \brief Read Unsigned 16-Bit Integer from Buffer +/// +/// This is essentially a copy of the isc::dns::InputBuffer::readUint16. It +/// should really be moved into a separate library. +/// +/// \param buffer Data buffer at least two bytes long of which the first two +/// bytes are assumed to represent a 16-bit integer in network-byte +/// order. +/// +/// \return Value of 16-bit integer +inline uint16_t +readUint16(const void* buffer) { + const uint8_t* byte_buffer = static_cast(buffer); + + uint16_t result = (static_cast(byte_buffer[0])) << 8; + result |= static_cast(byte_buffer[1]); + + return (result); +} + +/// \brief Write Unisgned 16-Bit Integer to Buffer +/// +/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16. It +/// should really be moved into a separate library. +/// +/// \param value 16-bit value to convert +/// \param buffer Data buffer at least two bytes long into which the 16-bit +/// value is written in network-byte order. + +inline void +writeUint16(uint16_t value, void* buffer) { + uint8_t* byte_buffer = static_cast(buffer); + + byte_buffer[0] = static_cast((value & 0xff00U) >> 8); + byte_buffer[1] = static_cast(value & 0x00ffU); +} + +} // namespace asiolink + +#endif // __ASIOLINK_UTILITIES_H diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index 2e165f4015..fbf749ef33 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -41,7 +41,7 @@ public: IOError(file, line, what) {} }; -/// \brief Socket not open +/// \brief Error setting socket options /// /// Thrown if attempt to change socket options fails. class SocketSetError : public IOError { @@ -50,7 +50,7 @@ public: IOError(file, line, what) {} }; -/// \brief Buffer Overflow +/// \brief Buffer overflow /// /// Thrown if an attempt is made to receive into an area beyond the end of /// the receive data buffer. @@ -108,24 +108,23 @@ public: /// \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 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. + /// 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 what would happen if + /// 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. + /// file descriptor for UNIX-like systems. virtual int getNative() const = 0; /// \brief Return the transport protocol of the socket. @@ -135,16 +134,15 @@ public: /// /// This method never throws an exception. /// - /// \return IPPROTO_UDP for UDP sockets - /// \return IPPROTO_TCP for TCP sockets + /// \return \c IPPROTO_UDP for UDP sockets, \c IPPROTO_TCP for TCP sockets virtual int getProtocol() const = 0; /// \brief Is Open() synchronous? /// - /// On a UDP socket, an "open" operation is merely a call to "open()" on - /// the underlying socket (so completes immediately), but on a TCP socket it - /// also includings connecting to the remote end (which is done as an - /// asynchronous operation). + /// On a TCP socket, an "open" operation is a call to the socket's "open()" + /// method followed by a connection to the remote system: it is an + /// asynchronous operation. On a UDP socket, it is just a call to "open()" + /// and completes synchronously. /// /// For TCP, signalling of the completion of the operation is done by /// by calling the callback function in the normal way. This could be done @@ -154,31 +152,31 @@ public: /// asynchronously. /// /// Owing to the way that the stackless coroutines are implemented, we need - /// to know _before_ executing the operation whether or not the open is - /// asynchronous. So this method simply provides that information. + /// to know _before_ executing the "open" function whether or not it is + /// asynchronous. So this method is called to provide that information. /// /// (The reason there is a need to know is because the call to open() passes /// in the state of the coroutine at the time the call is made. On an /// asynchronous I/O, we need to set the state to point to the statement - /// after the call to open() before we pass the corotuine to the open() - /// call. Unfortunately, the macros that do this also yield control - which - /// we don't want to do if the open is synchronous. Hence we need to know - /// before we make the call to open() whether that call will complete - /// asynchronously.) + /// after the call to open() _before_ we pass the corouine to the open() + /// call. Unfortunately, the macros that set the state of the coroutine + /// also yield control - which we don't want to do if the open is + /// synchronous. Hence we need to know before we make the call to open() + /// whether that call will complete asynchronously.) virtual bool isOpenSynchronous() const = 0; /// \brief Open AsioSocket /// /// Opens the socket for asynchronous I/O. The open will complete /// synchronously on UCP or asynchronously on TCP (in which case a callback - /// will be queued): what will happen can be found by calling the method - /// isOpenSynchronous(). + /// will be queued). /// /// \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. + /// 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 operation has - /// completed, but only if the operation was asynchronous. + /// completed, but only if the operation was asynchronous. (It is + /// ignored on a UDP socket.) virtual void open(const IOEndpoint* endpoint, C& callback) = 0; /// \brief Send Asynchronously @@ -196,7 +194,7 @@ public: /// \brief Receive Asynchronously /// - /// This correstponds to async_receive_from() for UDP sockets and + /// This corresponds 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. @@ -214,22 +212,17 @@ public: /// 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. + /// amount received so far and checks if the message is complete. /// /// 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). + /// effectively a no-op). /// /// \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. + /// \param length Total amount of data in the buffer. /// /// \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; + /// needed. + virtual bool receiveComplete(const void* data, size_t length) = 0; /// \brief Cancel I/O On AsioSocket virtual void cancel() = 0; diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index d890a52f49..212dd26461 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -222,8 +222,9 @@ IOFetch::operator()(asio::error_code ec, size_t length) { CORO_YIELD data_->socket->asyncReceive(data_->data.get(), static_cast(MIN_LENGTH), data_->cumulative, data_->remote.get(), *this); + data_->cumulative += length; std::cerr << "IOFetch: resuming after asynchronous receive\n"; - } while (!data_->socket->receiveComplete(data_->data.get(), length, + } while (!data_->socket->receiveComplete(data_->data.get(), data_->cumulative)); // The message is not rendered yet, so we can't print it easily diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index a7cc8e97da..d049f497b1 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -24,7 +24,6 @@ #include #include // for some IPC/network system calls -#include #include #include @@ -34,6 +33,7 @@ #include +#include #include #include #include @@ -65,15 +65,15 @@ public: /// \brief Constructor from an ASIO TCP 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. + /// \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); /// \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. + /// socket. In this case, the open() and close() methods are used. /// /// \param service I/O Service object used to manage the socket. TCPSocket(IOService& service); @@ -100,10 +100,10 @@ public: /// \brief Open Socket /// - /// Opens the UDP socket. This is an asynchronous operation, completion of + /// Opens the TCP socket. This is an asynchronous operation, completion of /// which will be signalled via a call to the callback function. /// - /// \param endpoint Endpoint to which the socket will connect to. + /// \param endpoint Endpoint to which the socket will connect. /// \param callback Callback object. virtual void open(const IOEndpoint* endpoint, C& callback); @@ -115,7 +115,8 @@ public: /// /// \param data Data to send /// \param length Length of data to send - /// \param endpoint Target of the send + /// \param endpoint Target of the send. (Unused for a TCP socket because + /// that was determined when the connection was opened.) /// \param callback Callback object. virtual void asyncSend(const void* data, size_t length, const IOEndpoint* endpoint, C& callback); @@ -136,21 +137,15 @@ public: /// \brief Checks if the data received is complete. /// - /// 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). + /// Checks if all the data has been received by checking that the amount + /// of data received is equal to the number in the first two bytes of the + /// message plus two (for the count field itself). /// - /// \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. + /// \param data Data buffer containing data to date (ignored) + /// \param length Amount of data in the buffer. /// - /// \return true if the receive is complete, false if another receive is - /// needed. - virtual bool receiveComplete(void*, size_t length, size_t& cumulative) { - cumulative = length; - return (true); - } + /// \return true if the receive is complete, false if not. + virtual bool receiveComplete(const void* data, size_t length); /// \brief Cancel I/O On Socket virtual void cancel(); @@ -176,6 +171,10 @@ private: // achieved by altering isc::dns::buffer to have pairs of methods: // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx() // methods taking into account a two-byte count field.) + // + // The option of sending the data in two operations, the count followed by + // the data was discounted as that would lead to two callbacks which would + // cause problems with the stackless coroutine code. isc::dns::OutputBufferPtr send_buffer_; ///< Send buffer }; @@ -212,8 +211,6 @@ TCPSocket::open(const IOEndpoint* endpoint, C& callback) { // 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. - - std::cerr << "TCPSocket::open(): open_ flags is " << isopen_ << "\n"; if (!isopen_) { if (endpoint->getFamily() == AF_INET) { socket_.open(asio::ip::tcp::v4()); @@ -266,8 +263,6 @@ TCPSocket::asyncSend(const void* data, size_t length, const IOEndpoint*, send_buffer_->writeData(data, length); // ... and send it - std::cerr << "TCPSocket::asyncSend(): sending " << count << " data bytes\n"; - socket_.async_send(asio::buffer(send_buffer_->getData(), send_buffer_->getLength()), callback); } catch (boost::numeric::bad_numeric_cast& e) { @@ -281,34 +276,70 @@ TCPSocket::asyncSend(const void* data, size_t length, const IOEndpoint*, } } -// 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. - +// Receive a message. Note that the "offset" argument is used as an index +// into the buffer in order to decide where to put the data. It is up to the +// caller to initialize the data to zero template void -TCPSocket::asyncReceive(void* data, size_t length, size_t, +TCPSocket::asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback) { if (isopen_) { - - // Upconvert the endpoint again. + // Upconvert to a TCPEndpoint. We need to do this because although + // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it + // does not contain a method for getting at the underlying endpoint + // type - that is 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::asyncReceive(): receiving from " << - tcp_endpoint->getAddress().toText() << - ", port " << tcp_endpoint->getPort() << "\n"; + TCPEndpoint* tcp_endpoint = static_cast(endpoint); + + // Write the endpoint details from the comminications link. Ideally + // we should make IOEndpoint assignable, but this runs in to all sorts + // of problems concerning the management of the underlying Boost + // endpoint (e.g. if it is not self-managed, is the copied one + // self-managed?) The most pragmatic solution is to let Boost take care + // of everything and copy details of the underlying endpoint. + tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint(); - // TODO: Complete TCPSocket::asyncReceive() + // Ensure we can write into the buffer and if so, set the pointer to + // where the data will be written. + if (offset >= length) { + isc_throw(BufferOverflow, "attempt to read into area beyond end of " + "TCP receive buffer"); + } + void* buffer_start = static_cast(static_cast(data) + offset); + // ... and kick off the read. + socket_.async_receive(asio::buffer(buffer_start, length - offset), callback); + } else { isc_throw(SocketNotOpen, "attempt to receive from a TCP socket that is not open"); } } +// Is the receive complete? + +template bool +TCPSocket::receiveComplete(const void* data, size_t length) { + + bool complete = false; + + // If we have read at least two bytes, we can work out how much we should be + // reading. + if (length >= 2) { + + // Convert first two bytes to a count and check that the following data + // is that length. + // TODO: Should we check to see if we have received too much data? + uint16_t expected = readUint16(data); + complete = ((expected + 2) == length); + } + + return (complete); +} + // Cancel I/O on the socket. No-op if the socket is not open. + template void TCPSocket::cancel() { if (isopen_) { diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index ded145c17c..13f63e676c 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -18,6 +18,7 @@ TESTS += run_unittests 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 += asiolink_utilities_unittest.cc run_unittests_SOURCES += io_address_unittest.cc run_unittests_SOURCES += io_endpoint_unittest.cc run_unittests_SOURCES += io_fetch_unittest.cc diff --git a/src/lib/asiolink/tests/asiolink_utilities_unittest.cc b/src/lib/asiolink/tests/asiolink_utilities_unittest.cc new file mode 100644 index 0000000000..51f565f87f --- /dev/null +++ b/src/lib/asiolink/tests/asiolink_utilities_unittest.cc @@ -0,0 +1,74 @@ +// 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. + +/// \brief Test of asiolink utilties +/// +/// Tests the fuctionality of the asiolink utilities code by comparing them +/// with the equivalent methods in isc::dns::[Input/Output]Buffer. + +#include + +#include + +#include +#include + +using namespace asiolink; +using namespace isc::dns; + +TEST(asioutil, readUint16) { + + // Reference buffer + uint8_t data[2]; + isc::dns::InputBuffer buffer(data, sizeof(data)); + + // Avoid possible compiler warnings by only setting uint8_t variables to + // uint8_t values. + uint8_t i8 = 0; + uint8_t j8 = 0; + for (int i = 0; i < (2 << 8); ++i, ++i8) { + for (int j = 0; j < (2 << 8); ++j, ++j8) { + data[0] = i8; + data[1] = j8; + buffer.setPosition(0); + EXPECT_EQ(buffer.readUint16(), readUint16(data)); + } + } +} + + +TEST(asioutil, writeUint16) { + + // Reference buffer + isc::dns::OutputBuffer buffer(2); + uint8_t test[2]; + + // Avoid possible compiler warnings by only setting uint16_t variables to + // uint16_t values. + uint16_t i16 = 0; + for (uint32_t i = 0; i < (2 << 16); ++i, ++i16) { + + // Write the reference data + buffer.clear(); + buffer.writeUint16(i16); + + // ... and the test data + writeUint16(i16, test); + + // ... and compare + const uint8_t* ref = static_cast(buffer.getData()); + EXPECT_EQ(ref[0], test[0]); + EXPECT_EQ(ref[1], test[1]); + } +} diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc index d37f23677d..8ecadaf5ee 100644 --- a/src/lib/asiolink/tests/tcp_socket_unittest.cc +++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc @@ -37,6 +37,7 @@ #include +#include #include #include #include @@ -56,11 +57,9 @@ const char OUTBOUND_DATA[] = "Data sent from client to server"; const char INBOUND_DATA[] = "Returned data from server to client"; } -/// /// An instance of this object is passed to the asynchronous I/O functions -/// and the operator() method is called when when an asynchronous I/O -/// completes. The arguments to the completion callback are stored for later -/// retrieval. +/// 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 TCPCallback { public: /// \brief Operations the server is doing @@ -74,17 +73,20 @@ public: /// \brief Minimim size of buffers enum { - MIN_SIZE = 4096 + MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count }; struct PrivateData { PrivateData() : - error_code_(), length_(0), name_(""), queued_(NONE), called_(NONE) + error_code_(), length_(0), cumulative_(0), name_(""), + queued_(NONE), called_(NONE) {} asio::error_code error_code_; ///< Completion error code - size_t length_; ///< Number of bytes transferred + size_t length_; ///< Bytes transfreed in this I/O + size_t cumulative_; ///< Cumulative bytes transferred std::string name_; ///< Which of the objects this is + uint8_t data_[MIN_SIZE]; ///< Receive buffer Operation queued_; ///< Queued operation Operation called_; ///< Which callback called }; @@ -103,7 +105,7 @@ public: /// \param which Which of the two callback objects this is TCPCallback(std::string which) : ptr_(new PrivateData()) { - setName(which); + ptr_->name_ = which; } /// \brief Destructor @@ -114,7 +116,7 @@ public: /// \brief Client Callback Function /// - /// Called when an asynchronous connect is completed by the client, this + /// Called when an asynchronous operation is completed by the client, this /// stores the origin of the operation in the client_called_ data member. /// /// \param ec I/O completion error code passed to callback function. @@ -123,8 +125,8 @@ public: size_t length = 0) { setCode(ec.value()); - setCalled(getQueued()); - setLength(length); + ptr_->called_ = ptr_->queued_; + ptr_->length_ = length; } /// \brief Get I/O completion error code @@ -140,57 +142,138 @@ public: } /// \brief Get number of bytes transferred in I/O - size_t getLength() { + size_t& length() { return (ptr_->length_); } - /// \brief Set number of bytes transferred in I/O + /// \brief Get cumulative number of bytes transferred in I/O + size_t& cumulative() { + return (ptr_->cumulative_); + } + + /// \brief Access Data Member /// - /// \param length New value of length parameter - void setLength(size_t length) { - ptr_->length_ = length; + /// \param Reference to the data member + uint8_t* data() { + return (ptr_->data_); } /// \brief Get flag to say what was queued - Operation getQueued() { + Operation& queued() { return (ptr_->queued_); } - /// \brief Set flag to say what is being queued - /// - /// \param called New value of queued parameter - void setQueued(Operation queued) { - ptr_->queued_ = queued; - } - /// \brief Get flag to say when callback was called - Operation getCalled() { + Operation& called() { return (ptr_->called_); } - /// \brief Set flag to say when callback was called - /// - /// \param called New value of called parameter - void setCalled(Operation called) { - ptr_->called_ = called; - } - /// \brief Return instance of callback name - std::string getName() { + std::string& name() { return (ptr_->name_); } - /// \brief Set callback name - /// - /// \param name New value of the callback name - void setName(const std::string& name) { - ptr_->name_ = name; - } - private: boost::shared_ptr ptr_; ///< Pointer to private data }; + +// Read Server Data +// +// Called in the part of the test that has the client send a message to the +// server, this loops until all the data has been read (synchronously) by the +// server. +// +// "All the data read" means that the server has received a message that is +// preceded by a two-byte count field and that the total amount of data received +// from the remote end is equal to the value in the count field plus two bytes +// for the count field itself. +// +// \param socket Socket on which the server is reading data +// \param server_cb Structure in which server data is held. +void +serverRead(tcp::socket& socket, TCPCallback& server_cb) { + + // Until we read something, the read is not complete. + bool complete = false; + + // As we may need to read multiple times, keep a count of the cumulative + // amount of data read and do successive reads into the appropriate part + // of the buffer. + // + // Note that there are no checks for buffer overflow - this is a test + // program and we have sized the buffer to be large enough for the test. + server_cb.cumulative() = 0; + + while (! complete) { + + // Read block of data and update cumulative amount of data received. + server_cb.length() = socket.receive( + asio::buffer(server_cb.data() + server_cb.cumulative(), + TCPCallback::MIN_SIZE - server_cb.cumulative())); + server_cb.cumulative() += server_cb.length(); + + // If we have read at least two bytes, we can work out how much we + // should be reading. + if (server_cb.cumulative() >= 2) { + uint16_t expected = readUint16(server_cb.data()); + if ((expected + 2) == server_cb.cumulative()) { + + // Amount of data read from socket equals the size of the + // message (as indicated in the first two bytes of the message) + // plus the size of the count field. Therefore we have received + // all the data. + complete = true; + } + } + } +} + +// Client read complete? +// +// This function is called when it appears that a client callback has been +// executed as the result of a read. It checks to see if all the data has been +// read and, if not, queues another asynchronous read. +// +// "All the data read" means that the client has received a message that is +// preceded by a two-byte count field and that the total amount of data received +// from the remote end is equal to the value in the count field plus two bytes +// for the count field itself. +// +// \param client TCPSocket object representing the client (i.e. the object +// under test). +// \param client_cb TCPCallback object holding information about the client. +// \param client_remote_endpoint Needed for the call to the client's asyncRead() +// method (but otherwise unused). +// +// \return true if the read is complete, false if not. +bool +clientReadComplete(TCPSocket& client, TCPCallback& client_cb, + TCPEndpoint& client_remote_endpoint) +{ + // Assume that all the data has not been read. + bool complete = false; + + // Check that the callback has in fact completed. + EXPECT_EQ(TCPCallback::READ, client_cb.called()); + EXPECT_EQ(0, client_cb.getCode()); + + // Update length of data received. + client_cb.cumulative() += client_cb.length(); + + // If the data is not complete, queue another read. + if (!client.receiveComplete(client_cb.data(), client_cb.cumulative())) { + client_cb.called() = TCPCallback::NONE; + client_cb.queued() = TCPCallback::READ; + client_cb.length() = 0; + client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE , + client_cb.cumulative(), &client_remote_endpoint, + client_cb); + } + + return (complete); +} + // TODO: Need to add a test to check the cancel() method // Tests the operation of a TCPSocket by opening it, sending an asynchronous @@ -201,7 +284,12 @@ TEST(TCPSocket, SequenceTest) { // Common objects. IOService service; // Service object for async control - // Server + // The client - the TCPSocket being tested + TCPSocket client(service);// Socket under test + TCPCallback client_cb("Client"); // Async I/O callback function + TCPEndpoint client_remote_endpoint; // Where client receives message from + + // The server - with which the client communicates. IOAddress server_address(SERVER_ADDRESS); // Address of target server TCPCallback server_cb("Server"); // Server callback @@ -210,39 +298,23 @@ TEST(TCPSocket, SequenceTest) { TCPEndpoint server_remote_endpoint; // Address where server received message from tcp::socket server_socket(service.get_io_service()); // Socket used for server - char server_data[TCPCallback::MIN_SIZE]; - // Data received by server - ASSERT_GT(sizeof(server_data), sizeof(OUTBOUND_DATA)); - // Make sure it's large enough - // The client - the TCPSocket being tested - TCPSocket client(service);// Socket under test - TCPCallback client_cb("Client"); // Async I/O callback function - TCPEndpoint client_remote_endpoint; // Where client receives message from - char client_data[TCPCallback::MIN_SIZE]; - // Data received by client - ASSERT_GT(sizeof(client_data), sizeof(OUTBOUND_DATA)); - // Make sure it's large enough - //size_t client_cumulative = 0; // Cumulative data received + // Step 1. Create the connection between the client and the server. Set + // up the server to accept incoming connections and have the client open + // a channel to it. - // 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. - - std::cerr << "Setting up acceptor\n"; - // Set up the server to accept incoming connections. - server_cb.setQueued(TCPCallback::ACCEPT); - server_cb.setCalled(TCPCallback::NONE); + // Set up server - open socket and queue an accept. + server_cb.queued() = TCPCallback::ACCEPT; + server_cb.called() = TCPCallback::NONE; server_cb.setCode(42); // Some error tcp::acceptor acceptor(service.get_io_service(), tcp::endpoint(tcp::v4(), SERVER_PORT)); acceptor.set_option(tcp::acceptor::reuse_address(true)); acceptor.async_accept(server_socket, server_cb); - std::cerr << "Setting up client\n"; - // Open the client socket - the operation should be asynchronous - client_cb.setQueued(TCPCallback::OPEN); - client_cb.setCalled(TCPCallback::NONE); + // Set up client - connect to the server. + client_cb.queued() = TCPCallback::OPEN; + client_cb.called() = TCPCallback::NONE; client_cb.setCode(43); // Some error EXPECT_FALSE(client.isOpenSynchronous()); client.open(&server_endpoint, client_cb); @@ -250,100 +322,150 @@ TEST(TCPSocket, SequenceTest) { // Run the open and the accept callback and check that they ran. service.run_one(); service.run_one(); - - EXPECT_EQ(TCPCallback::ACCEPT, server_cb.getCalled()); + + EXPECT_EQ(TCPCallback::ACCEPT, server_cb.called()); EXPECT_EQ(0, server_cb.getCode()); - EXPECT_EQ(TCPCallback::OPEN, client_cb.getCalled()); + + EXPECT_EQ(TCPCallback::OPEN, client_cb.called()); EXPECT_EQ(0, client_cb.getCode()); - // Write something to the server using the client and read it on ther server. - server_cb.setCalled(TCPCallback::NONE); - server_cb.setQueued(TCPCallback::READ); - server_cb.setCode(142); // Arbitrary number - server_cb.setLength(0); - server_socket.async_receive(buffer(server_data, sizeof(server_data)), server_cb); + // Step 2. Get the client to write to the server asynchronously. The + // server will loop reading the data synchronously. - client_cb.setCalled(TCPCallback::NONE); - client_cb.setQueued(TCPCallback::WRITE); + // Write asynchronously to the server. + client_cb.called() = TCPCallback::NONE; + client_cb.queued() = TCPCallback::WRITE; client_cb.setCode(143); // Arbitrary number - client_cb.setLength(0); + client_cb.length() = 0; client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb); - // Run the write and read callback and check they ran - service.run_one(); + // Synchronously read the data from the server.; + serverRead(server_socket, server_cb); + + // Wait for the client callback to complete. service.run_one(); - // Check lengths. As the client wrote the buffer, currently two bytes - // will be prepended by the client containing the length. - EXPECT_EQ(TCPCallback::READ, server_cb.getCalled()); - EXPECT_EQ(0, server_cb.getCode()); - EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.getLength()); - - EXPECT_EQ(TCPCallback::WRITE, client_cb.getCalled()); + // Check the client state + EXPECT_EQ(TCPCallback::WRITE, client_cb.called()); EXPECT_EQ(0, client_cb.getCode()); - EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.getLength()); + EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length()); - // Check that the first two bytes of the buffer are in fact the remaining - // length of the buffer (code copied from isc::dns::buffer.h) - uint16_t count = ((unsigned int)(server_data[0])) << 8; - count |= ((unsigned int)(server_data[1])); - EXPECT_EQ(sizeof(OUTBOUND_DATA), count); + // ... and check what the server received. + EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative()); + EXPECT_TRUE(equal(OUTBOUND_DATA, + (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)), + (server_cb.data() + 2))); - // ... and check data received by server is what we expect - EXPECT_TRUE(equal(&server_data[2], &server_data[server_cb.getLength() - 1], - OUTBOUND_DATA)); + // Step 3. Get the server to write all the data asynchronously and have the + // client loop (asynchronously) reading the data. Note that we copy the + // data into the server's internal buffer in order to precede it with a two- + // byte count field. - // TODO: Complete this server test - // TODO: Add in loop for server to read data - read 2 bytes, then as much as needed - - /* - // Now return data from the server to the client. Issue the read on the - // client. - client_cb.setCalled(TCPCallback::NONE); - client_cb.setQueued(TCPCallback::READ); - client_cb.setCode(143); // Arbitrary number - client_cb.setLength(0); - client.asyncReceive(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb); + // Have the server write asynchronously to the client. + server_cb.called() = TCPCallback::NONE; + server_cb.queued() = TCPCallback::WRITE; + server_cb.length() = 0; + server_cb.cumulative() = 0; - client_cb.setLength(12345); // Arbitrary number - client_cb.setCalled(false); - client_cb.setCode(32); // Arbitrary number - client.asyncReceive(data, sizeof(data), client_cumulative, - &client_remote_endpoint, client_cb); + writeUint16(sizeof(INBOUND_DATA), server_cb.data()); + copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1), + (server_cb.data() + 2)); + server_socket.async_send(asio::buffer(server_cb.data(), + (sizeof(INBOUND_DATA) + 2)), + server_cb); - // Issue the write on the server side to the source of the data it received. - server_cb.setLength(22345); // Arbitrary number - server_cb.setCalled(false); - server_cb.setCode(232); // Arbitrary number - server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)), - server_remote_endpoint.getASIOEndpoint(), server_cb); + // Have the client read asynchronously. + client_cb.called() = TCPCallback::NONE; + client_cb.queued() = TCPCallback::READ; + client_cb.length() = 0; + client_cb.cumulative() = 0; + client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE , + client_cb.cumulative(), &client_remote_endpoint, + client_cb); - // Expect two callbacks to run - service.get_io_service().poll(); - //service.run_one(); + // Run the callbacks. Several options are possible depending on how ASIO + // is implemented and whether the message gets fragmented: + // + // 1) The send handler may complete immediately, regardess of whether the + // data has been read by the client. (This is the most likely.) + // 2) The send handler may only run after all the data has been read by + // the client. (This could happen if the client's TCP buffers were too + // small so the data was not transferred to the "remote" system until the + // remote buffer has been emptied one or more times.) + // 3) The client handler may be run a number of times to handle the message + // fragments and the server handler may run between calls of the client + // handler. + // + // So loop, running one handler at a time until we are certain that all the + // handlers have run. - EXPECT_TRUE(client_cb.getCalled()); + bool server_complete = false; + bool client_complete = false; + while (!server_complete || !client_complete) { + service.run_one(); + + // Has the server run? + if (!server_complete) { + if (server_cb.called() == server_cb.queued()) { + + // Yes. Check that the send completed successfully and that + // all the data that was expected to have been sent was in fact + // sent. + EXPECT_EQ(0, server_cb.getCode()); + EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length()); + server_complete = true; + continue; + } + } + + if (!client_complete) { + + // Client callback must have run. Check that it ran OK. + EXPECT_EQ(TCPCallback::READ, client_cb.called()); + EXPECT_EQ(0, client_cb.getCode()); + + // Update length of data received. + client_cb.cumulative() += client_cb.length(); + if (client_cb.cumulative() > 2) { + + // Have at least the message length field, check if we have the + // entire message. (If we don't have the length field, the data + // is not complete.) + client_complete = ((readUint16(client_cb.data()) + 2) == + client_cb.cumulative()); + } + + // If the data is not complete, queue another read. + if (! client_complete) { + client_cb.called() = TCPCallback::NONE; + client_cb.queued() = TCPCallback::READ; + client_cb.length() = 0; + client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE , + client_cb.cumulative(), &client_remote_endpoint, + client_cb); + } + } + } + + // Both the send and the receive have comnpleted. Check that the received + // is what was sent. + + // Check the client state + EXPECT_EQ(TCPCallback::READ, client_cb.called()); EXPECT_EQ(0, client_cb.getCode()); - EXPECT_EQ(sizeof(INBOUND_DATA), client_cb.getLength()); + EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative()); - EXPECT_TRUE(server_cb.getCalled()); + // ... and check what the server sent. + EXPECT_EQ(TCPCallback::WRITE, server_cb.called()); EXPECT_EQ(0, server_cb.getCode()); - EXPECT_EQ(sizeof(INBOUND_DATA), server_cb.getLength()); + EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length()); - EXPECT_TRUE(equal(&data[0], &data[server_cb.getLength() - 1], INBOUND_DATA)); - - // Check that the address/port received by the client corresponds to the - // address and port the server is listening on. - EXPECT_TRUE(server_address == client_remote_endpoint.getAddress()); - EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort()); - - // Finally, check that the receive received a complete buffer's worth of data. - EXPECT_TRUE(client.receiveComplete(&data[0], client_cb.getLength(), - client_cumulative)); - EXPECT_EQ(client_cb.getLength(), client_cumulative); + // ... and that what was sent is what was received. + EXPECT_TRUE(equal(INBOUND_DATA, + (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)), + (client_cb.data() + 2))); // Close client and server. EXPECT_NO_THROW(client.close()); - EXPECT_NO_THROW(server.close()); - * */ -} + EXPECT_NO_THROW(server_socket.close()); +} \ No newline at end of file diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc index 7b81a6205a..aa46498a97 100644 --- a/src/lib/asiolink/tests/udp_socket_unittest.cc +++ b/src/lib/asiolink/tests/udp_socket_unittest.cc @@ -263,8 +263,8 @@ TEST(UDPSocket, SequenceTest) { EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort()); // Finally, check that the receive received a complete buffer's worth of data. - EXPECT_TRUE(client.receiveComplete(&data[0], client_cb.getLength(), - client_cumulative)); + client_cumulative += client_cb.getLength(); + EXPECT_TRUE(client.receiveComplete(&data[0], client_cumulative)); EXPECT_EQ(client_cb.getLength(), client_cumulative); // Close client and server. diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index 0df6fba175..1a016d2d95 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -53,15 +53,15 @@ public: /// \brief Constructor from an ASIO UDP 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. + /// \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. It is assumed that open() and close() will not be used. + /// socket. In this case, the open() and close() methods are used. /// /// \param service I/O Service object used to manage the socket. UDPSocket(IOService& service); @@ -90,9 +90,11 @@ public: /// /// Opens the UDP socket. This is a synchronous operation. /// - /// \param endpoint Endpoint to which the socket will connect to. - /// \param callback Unused. - virtual void open(const IOEndpoint* endpoint, C&); + /// \param endpoint Endpoint to which the socket will send data. This is + /// used to determine the address family trhat should be used for the + /// underlying socket. + /// \param callback Unused as the operation is synchronous. + virtual void open(const IOEndpoint* endpoint, C& callback); /// \brief Send Asynchronously /// @@ -110,8 +112,8 @@ public: /// \brief Receive Asynchronously /// /// Calls the underlying socket's async_receive_from() method to read a - /// packet of data from a remote endpoint. Arrival of the data is - /// signalled via a call to the callback function. + /// packet of data from a remote endpoint. Arrival of the data is signalled + /// via a call to the callback function. /// /// \param data Buffer to receive incoming message /// \param length Length of the data buffer @@ -123,19 +125,15 @@ public: /// \brief Checks if the data received is complete. /// - /// 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). + /// For a UDP socket all the data is received in one I/O, so this is + /// effectively a no-op (although it does update the amount of data + /// received). /// - /// \param data Data buffer containing data to date. (This is ignored - /// for UDP 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. + /// \param data Data buffer containing data to date (ignored) + /// \param length Amount of data in the buffer. /// - /// \return true if the receive is complete, false if another receive is - /// needed. Always true for a UDP socket. - virtual bool receiveComplete(void*, size_t length, size_t& cumulative) { - cumulative = length; + /// \return Always true + virtual bool receiveComplete(const void*, size_t) { return (true); } @@ -185,10 +183,11 @@ UDPSocket::~UDPSocket() template void 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. - + // Ignore opens on already-open socket. (Don't throw a failure because + // of uncertainties as to what precedes whan when using asynchronous I/O.) + // It also allows us a treat a passed-in socket in exactly the same way as + // a self-managed socket (in that we can call the open() and close() methods + // of this class). if (!isopen_) { if (endpoint->getFamily() == AF_INET) { socket_.open(asio::ip::udp::v4()); @@ -198,8 +197,7 @@ UDPSocket::open(const IOEndpoint* endpoint, C&) { } isopen_ = true; - // Ensure it can send and receive 4K buffers. - + // Ensure it can send and receive at least 4K buffers. asio::ip::udp::socket::send_buffer_size snd_size; socket_.get_option(snd_size); if (snd_size.value() < MIN_SIZE) { @@ -227,13 +225,14 @@ UDPSocket::asyncSend(const void* data, size_t length, // 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 + // does not contain a method for getting at the underlying endpoint + // type - that is in the derived class and the two classes differ on // return type. - assert(endpoint->getProtocol() == IPPROTO_UDP); const UDPEndpoint* udp_endpoint = static_cast(endpoint); + + // ... and send the message. socket_.async_send_to(asio::buffer(data, length), udp_endpoint->getASIOEndpoint(), callback); } else { @@ -242,10 +241,8 @@ UDPSocket::asyncSend(const void* data, size_t length, } } -// 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. +// Receive a message. Should never do this if the socket is not open, so throw +// an exception if this is the case. template void UDPSocket::asyncReceive(void* data, size_t length, size_t offset, From f4b20fc54b74d03279245942900806a5e7b57d39 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 28 Feb 2011 18:00:54 +0000 Subject: [PATCH 014/113] [trac499] Finish TCPSocket and IOFetch and associated unit tests --- src/lib/asiolink/asiodef.cc | 16 +- src/lib/asiolink/asiodef.h | 2 +- src/lib/asiolink/asiodef.msg | 14 +- src/lib/asiolink/io_fetch.cc | 149 ++++++++---------- src/lib/asiolink/tcp_socket.h | 2 +- src/lib/asiolink/tests/io_fetch_unittest.cc | 78 +++++---- src/lib/asiolink/tests/tcp_socket_unittest.cc | 4 +- 7 files changed, 129 insertions(+), 136 deletions(-) diff --git a/src/lib/asiolink/asiodef.cc b/src/lib/asiolink/asiodef.cc index 5f7a02a0a4..94c71b5f46 100644 --- a/src/lib/asiolink/asiodef.cc +++ b/src/lib/asiolink/asiodef.cc @@ -1,4 +1,4 @@ -// File created from asiodef.msg on Thu Feb 24 11:52:42 2011 +// File created from asiodef.msg on Mon Feb 28 17:15:30 2011 #include #include @@ -20,14 +20,14 @@ extern const isc::log::MessageID ASIO_UNKRESULT = "UNKRESULT"; namespace { const char* values[] = { - "FETCHCOMP", "upstream fetch to %s has now completed", - "FETCHSTOP", "upstream fetch to %s has been stopped", - "OPENSOCK", "error %d opening %s socket to %s", - "RECVSOCK", "error %d reading data from %s via a %s socket", - "RECVTMO", "receive timeout while waiting for data from %s", - "SENDSOCK", "error %d sending data to %s via a %s socket", + "FETCHCOMP", "upstream fetch to %s(%d) has now completed", + "FETCHSTOP", "upstream fetch to %s(%d) has been stopped", + "OPENSOCK", "error %d opening %s socket to %s(%d)", + "RECVSOCK", "error %d reading %s data from %s(%d)", + "RECVTMO", "receive timeout while waiting for data from %s(%d)", + "SENDSOCK", "error %d sending data using %s to %s(%d)", "UNKORIGIN", "unknown origin for ASIO error code %d (protocol: %s, address %s)", - "UNKRESULT", "unknown result (%d) when IOFetch::stop() was executed for I/O to %s", + "UNKRESULT", "unknown result (%d) when IOFetch::stop() was executed for I/O to %s(%d)", NULL }; diff --git a/src/lib/asiolink/asiodef.h b/src/lib/asiolink/asiodef.h index c36026ec7d..ba77817974 100644 --- a/src/lib/asiolink/asiodef.h +++ b/src/lib/asiolink/asiodef.h @@ -1,4 +1,4 @@ -// File created from asiodef.msg on Thu Feb 24 11:52:42 2011 +// File created from asiodef.msg on Mon Feb 28 17:15:30 2011 #ifndef __ASIODEF_H #define __ASIODEF_H diff --git a/src/lib/asiolink/asiodef.msg b/src/lib/asiolink/asiodef.msg index 2e9de05bbb..2fcadd1f54 100644 --- a/src/lib/asiolink/asiodef.msg +++ b/src/lib/asiolink/asiodef.msg @@ -15,32 +15,32 @@ $PREFIX ASIO_ $NAMESPACE asiolink -FETCHCOMP upstream fetch to %s has now completed +FETCHCOMP upstream fetch to %s(%d) has now completed + A debug message, this records the the upstream fetch (a query made by the + resolver on behalf of its client) to the specified address has completed. -FETCHSTOP upstream fetch to %s has been stopped +FETCHSTOP upstream fetch to %s(%d) has been stopped + An external component has requested the halting of an upstream fetch. This + is an allowed operation, and the message should only appear if debug is + enabled. -OPENSOCK error %d opening %s socket to %s +OPENSOCK error %d opening %s socket to %s(%d) + The asynchronous I/O code encountered an error when trying to open a socket + of the specified protocol in order to send a message to the target address. + The the number of the system error that cause the problem is given in the + message. -RECVSOCK error %d reading data from %s via a %s socket +RECVSOCK error %d reading %s data from %s(%d) + The asynchronous I/O code encountered an error when trying read data from + the specified address on the given protocol. The the number of the system + error that cause the problem is given in the message. -SENDSOCK error %d sending data to %s via a %s socket +SENDSOCK error %d sending data using %s to %s(%d) + The asynchronous I/O code encountered an error when trying send data to + the specified address on the given protocol. The the number of the system + error that cause the problem is given in the message. -RECVTMO receive timeout while waiting for data from %s +RECVTMO receive timeout while waiting for data from %s(%d) + An upstream fetch from the specified address timed out. This may happen for + any number of reasons and is most probably a problem at the remote server + or a problem on the network. The message will only appear if debug is @@ -50,7 +50,7 @@ UNKORIGIN unknown origin for ASIO error code %d (protocol: %s, address %s) + This message should not appear and indicates an internal error if it does. + Please enter a bug report. -UNKRESULT unknown result (%d) when IOFetch::stop() was executed for I/O to %s +UNKRESULT unknown result (%d) when IOFetch::stop() was executed for I/O to %s(%d) + The termination method of the resolver's upstream fetch class was called with + an unknown result code (which is given in the message). This message should + not appear and may indicate an internal error. Please enter a bug report. diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 212dd26461..faff645f1a 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -57,17 +57,17 @@ isc::log::Logger logger("asio"); /// \brief IOFetch Data /// -/// The data for IOFetch is held in a separate struct pointed to by a -/// shared_ptr object. This is because the IOFetch object will be copied -/// often (it is used as a coroutine and passed as callback to many -/// async_*() functions) and we want keep the same data). Organising the -/// data in this way keeps copying to a minimum. +/// 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 in this way keeps copying to +/// a minimum. struct IOFetchData { // The first two members are shared pointers to a base class because what is // actually instantiated depends on whether the fetch is over UDP or TCP, // which is not known until construction of the IOFetch. Use of a shared - //pointer here is merely to ensure deletion when the data object is deleted. + // 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 remote; ///< Where the fetch was sent @@ -80,23 +80,29 @@ struct IOFetchData { bool stopped; ///< Have we stopped running? asio::deadline_timer timer; ///< Timer to measure timeouts int timeout; ///< Timeout in ms - IOFetch::Origin origin; ///< Origin of last asynchronous I/O + + // In case we need to log an error, the origin of the last asynchronous + // I/O is recorded. To save time and simplify the code, this is recorded + // as the ID of the error message that would be generated if the I/O failed. + // This means that we must make sure that all possible "origins" take the + // same arguments in their message in the same order. + isc::log::MessageID origin; ///< Origin of last asynchronous I/O /// \brief Constructor /// /// Just fills in the data members of the IOFetchData structure /// - /// \param protocol Either IOFetch::TCP or IOFetch::UDP + /// \param protocol Either IOFetch::TCP or IOFetch::UDP. /// \param service I/O Service object to handle the asynchronous - /// operations. + /// operations. /// \param query DNS question to send to the upstream server. /// \param address IP address of upstream server /// \param port Port to use for the query /// \param buff Output buffer into which the response (in wire format) - /// is written (if a response is received). + /// is written (if a response is received). /// \param cb Callback object containing the callback to be called - /// when we terminate. The caller is responsible for managing this - /// object and deleting it if necessary. + /// when we terminate. The caller is responsible for managing this + /// object and deleting it if necessary. /// \param wait Timeout for the fetch (in ms). /// /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554) @@ -124,11 +130,10 @@ struct IOFetchData { stopped(false), timer(service.get_io_service()), timeout(wait), - origin(IOFetch::NONE) + origin(ASIO_UNKORIGIN) {} }; - /// IOFetch Constructor - just initialize the private data IOFetch::IOFetch(Protocol protocol, IOService& service, @@ -145,8 +150,7 @@ IOFetch::IOFetch(Protocol protocol, IOService& service, void IOFetch::operator()(asio::error_code ec, size_t length) { - std::cerr << "IOFetch::operator() [" << this << "], origin = " << - data_->origin << ", coroutine = " << get_value() << "\n"; + if (data_->stopped) { return; } else if (ec) { @@ -161,7 +165,7 @@ IOFetch::operator()(asio::error_code ec, size_t length) { /// declarations. { Message msg(Message::RENDER); - + // TODO: replace with boost::random or some other suitable PRNG msg.setQid(0); msg.setOpcode(Opcode::QUERY()); @@ -178,8 +182,8 @@ IOFetch::operator()(asio::error_code ec, size_t length) { data_->remote->getAddress().toText()); } - // If we timeout, we stop, which will shutdown everything and - // cancel all other attempts to run inside the coroutine + // If we timeout, we stop, which will can cancel outstanding I/Os and + // shutdown everything. if (data_->timeout != -1) { data_->timer.expires_from_now(boost::posix_time::milliseconds( data_->timeout)); @@ -188,27 +192,20 @@ IOFetch::operator()(asio::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. - - data_->origin = OPEN; + // is synchronous (i.e. UDP operation) we bypass the yield. + data_->origin = ASIO_OPENSOCK; if (data_->socket->isOpenSynchronous()) { - std::cerr << "IOFetch: Opening socket synchronously\n"; data_->socket->open(data_->remote.get(), *this); } else { - std::cerr << "IOFetch: Opening socket asynchronously and yeilding\n"; CORO_YIELD data_->socket->open(data_->remote.get(), *this); - std::cerr << "IOFetch: Resuming after Opening socket asynchronously\n"; } - // Begin an asynchronous send, and then yield. When the send completes - // send completes, we will resume immediately after this point. - // Note: A TCP message may not be sent in one piece (depends on the - // implementation in TCP socket). Therefore there may be - data_->origin = SEND; - std::cerr << "IOFetch: asynchronous send\n"; + // Begin an asynchronous send, and then yield. When the send completes, + // we will resume immediately after this point. + data_->origin = ASIO_SENDSOCK; CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(), data_->msgbuf->getLength(), data_->remote.get(), *this); - std::cerr << "IOFetch: resuming after asynchronous send\n"; + // 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 @@ -216,30 +213,25 @@ IOFetch::operator()(asio::error_code ec, size_t length) { // 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. - data_->origin = RECEIVE; + data_->origin = ASIO_RECVSOCK; do { - std::cerr << "IOFetch: asynchronous receive\n"; CORO_YIELD data_->socket->asyncReceive(data_->data.get(), static_cast(MIN_LENGTH), data_->cumulative, data_->remote.get(), *this); data_->cumulative += length; - std::cerr << "IOFetch: resuming after asynchronous receive\n"; } while (!data_->socket->receiveComplete(data_->data.get(), data_->cumulative)); - // 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 - /// OutputBuffer object were made to meet the requirements of - /// a MutableBufferSequence, then it could be written to directly - /// by async_receive_from() and this additional copy step would - /// be unnecessary.) + /// OutputBuffer object were made to meet the requirements of a + /// MutableBufferSequence, then it could be written to directly by + /// async_receive_from() and this additional copy step would be + /// unnecessary.) data_->buffer->writeData(data_->data.get(), length); - // Finished with this socket, so close it. - data_->origin = CLOSE; - std::cerr << "IOFetch: close\n"; + // Finished with this socket, so close it. This will not generate an + // I/O error, but reset the origin to unknown in case we change this. + data_->origin = ASIO_UNKORIGIN; data_->socket->close(); /// We are done @@ -251,9 +243,8 @@ IOFetch::operator()(asio::error_code ec, size_t length) { // query finishes or when the timer times out. Either way, it sets the // "stopped_" flag and cancels anything that is in progress. // -// As the function may be entered multiple times as things wind down, the -// stopped_ flag checks if stop() has already been called. If it has, -// subsequent calls are no-ops. +// As the function may be entered multiple times as things wind down, it checks +// if the stopped_ flag is already set. If it is, the call is a no-op. void IOFetch::stop(Result result) { @@ -276,24 +267,24 @@ IOFetch::stop(Result result) { // // Although Logger::debug checks the debug flag internally, doing it // below before calling Logger::debug avoids the overhead of a string - // conversion in the common paths and in the common case when debug is - // not enabled. + // conversion in the common case when debug is not enabled. // // TODO: Update testing of stopped_ if threads are used. data_->stopped = true; - switch (result) { case TIME_OUT: if (logger.isDebugEnabled(1)) { - logger.debug(1, ASIO_RECVTMO, - data_->remote->getAddress().toText().c_str()); + logger.debug(20, ASIO_RECVTMO, + data_->remote->getAddress().toText().c_str(), + static_cast(data_->remote->getPort())); } break; case SUCCESS: if (logger.isDebugEnabled(50)) { - logger.debug(50, ASIO_FETCHCOMP, - data_->remote->getAddress().toText().c_str()); + logger.debug(30, ASIO_FETCHCOMP, + data_->remote->getAddress().toText().c_str(), + static_cast(data_->remote->getPort())); } break; @@ -301,13 +292,15 @@ IOFetch::stop(Result result) { // Fetch has been stopped for some other reason. This is // allowed but as it is unusual it is logged, but with a lower // debug level than a timeout (which is totally normal). - logger.debug(10, ASIO_FETCHSTOP, - data_->remote->getAddress().toText().c_str()); + logger.debug(1, ASIO_FETCHSTOP, + data_->remote->getAddress().toText().c_str(), + static_cast(data_->remote->getPort())); break; default: logger.error(ASIO_UNKRESULT, static_cast(result), - data_->remote->getAddress().toText().c_str()); + data_->remote->getAddress().toText().c_str(), + static_cast(data_->remote->getPort())); } // Stop requested, cancel and I/O's on the socket and shut it down, @@ -321,9 +314,6 @@ IOFetch::stop(Result result) { if (data_->callback) { (*(data_->callback))(result); } - - // Mark that stop() has now been called. - } } @@ -331,32 +321,19 @@ IOFetch::stop(Result result) { void IOFetch::logIOFailure(asio::error_code ec) { - // Get information that will be in all messages + // Should only get here with a known error code. + assert((data_->origin == ASIO_OPENSOCK) || + (data_->origin == ASIO_SENDSOCK) || + (data_->origin == ASIO_RECVSOCK) || + (data_->origin == ASIO_UNKORIGIN)); + static const char* PROTOCOL[2] = {"TCP", "UDP"}; - const char* prot = (data_->remote->getProtocol() == IPPROTO_TCP) ? - PROTOCOL[0] : PROTOCOL[1]; - - int errcode = ec.value(); - - std::string str_address = data_->remote->getAddress().toText(); - const char* address = str_address.c_str(); - - switch (data_->origin) { - case OPEN: - logger.error(ASIO_OPENSOCK, errcode, prot, address); - break; - - case SEND: - logger.error(ASIO_SENDSOCK, errcode, prot, address); - break; - - case RECEIVE: - logger.error(ASIO_RECVSOCK, errcode, prot, address); - break; - - default: - logger.error(ASIO_UNKORIGIN, errcode, prot, address); - } + logger.error(data_->origin, + ec.value(), + ((data_->remote->getProtocol() == IPPROTO_TCP) ? + PROTOCOL[0] : PROTOCOL[1]), + data_->remote->getAddress().toText().c_str(), + static_cast(data_->remote->getPort())); } } // namespace asiolink diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index d049f497b1..1146fa3548 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -62,7 +62,7 @@ private: TCPSocket& operator=(const TCPSocket&); public: - + /// \brief Constructor from an ASIO TCP socket. /// /// \param socket The ASIO representation of the TCP socket. It is assumed diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index a265d6e5ed..99acee923c 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -12,13 +12,14 @@ // 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 @@ -30,6 +31,7 @@ #include #include +#include #include #include #include @@ -38,6 +40,7 @@ using namespace asio; using namespace isc::dns; using namespace asio::ip; +using namespace std; namespace asiolink { @@ -59,6 +62,7 @@ public: IOFetch udp_fetch_; ///< For UDP query test IOFetch tcp_fetch_; ///< For TCP query test IOFetch::Protocol protocol_; ///< Protocol being tested + size_t cumulative_; ///< Cumulative data received by "server". // The next member is the buffer in which the "server" (implemented by the // response handler methods in this class) receives the question sent by the @@ -77,7 +81,8 @@ public: TEST_PORT, result_buff_, this, 100), tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST), TEST_PORT, result_buff_, this, 1000), - protocol_(IOFetch::TCP) // for initialization - will be changed + protocol_(IOFetch::TCP), // for initialization - will be changed + cumulative_(0) { // Construct the data buffer for question we expect to receive. Message msg(Message::RENDER); @@ -140,7 +145,8 @@ public: // Check that length of the received data and the expected data are // identical, then check that the data is identical as well. EXPECT_EQ(msgbuf_->getLength(), length); - EXPECT_TRUE(memcmp(msgbuf_->getData(), server_buff_, length) == 0); + EXPECT_TRUE(equal(server_buff_, (server_buff_ + length - 1), + static_cast(msgbuf_->getData()))); // Return a message back to the IOFetch object. socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA), *remote); @@ -155,10 +161,11 @@ public: /// \param ec Boost error code, value should be zero. void tcpAcceptHandler(tcp::socket* socket, error_code ec = error_code()) { - std::cerr << "TCP Accept Handler\n"; - EXPECT_EQ(0, ec.value()); // Expect no error + // Expect that the accept completed without a problem. + EXPECT_EQ(0, ec.value()); - // Initiate a read on the socket + // Initiate a read on the socket. + cumulative_ = 0; socket->async_receive(asio::buffer(server_buff_, sizeof(server_buff_)), boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2)); } @@ -166,8 +173,9 @@ public: /// \brief Completion handler for receiving TCP data /// /// When IOFetch is sending data, this response handler emulates the remote - /// DNS server. It checks that the data sent by the IOFetch object is what - /// was expected to have been sent, then sends back a known buffer of data. + /// DNS server. It that all the data sent by the IOFetch object has been + /// received, issuing another read if not. If the data is complete, it is + /// compared to what is expected and a reply sent back to the IOFetch. /// /// \param socket Socket to use to send the answer /// \param ec ASIO error code, completion code of asynchronous I/O issued @@ -176,36 +184,48 @@ public: void tcpReceiveHandler(tcp::socket* socket, error_code ec = error_code(), size_t length = 0) { - std::cerr << "TCP Receive Handler\n"; - // TODO - need to loop until all the data is received. - - // Interpret the received data. The first two bytes, when converted - // to host byte order, are the count of the length of the message. - EXPECT_GE(2, length); - uint16_t dns_length = readUint16(server_buff_); - EXPECT_EQ(length, dns_length + 2); + // Expect that the receive completed without a problem. + EXPECT_EQ(0, ec.value()); - // Check that length of the DNS message received is that expected. - EXPECT_EQ(msgbuf_->getLength(), dns_length); + // If we haven't received all the data, issue another read. + cumulative_ += length; + bool complete = false; + if (cumulative_ > 2) { + uint16_t dns_length = readUint16(server_buff_); + complete = ((dns_length + 2) == cumulative_); + } - // Compare buffers, zeroing the QID in the received buffer to match + if (!complete) { + socket->async_receive(asio::buffer((server_buff_ + cumulative_), + (sizeof(server_buff_) - cumulative_)), + boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2)); + return; + } + + // Check that length of the DNS message received is that expected, then + // compare buffers, zeroing the QID in the received buffer to match // that set in our expected question. Note that due to the length - // field the QID in the received buffer is in the thrid and fourth + // field the QID in the received buffer is in the third and fourth // bytes. + EXPECT_EQ(msgbuf_->getLength() + 2, cumulative_); server_buff_[2] = server_buff_[3] = 0; - EXPECT_TRUE(memcmp(msgbuf_->getData(), server_buff_ + 2, dns_length) == 0); + EXPECT_TRUE(equal((server_buff_ + 2), (server_buff_ + cumulative_ - 2), + static_cast(msgbuf_->getData()))); // ... and return a message back. This has to be preceded by a two-byte // count field. It's simpler to do this as two writes - it shouldn't // make any difference to the IOFetch object. + // + // When specifying the callback handler, the expected size of the + // data written is passed as the first parameter. uint8_t count[2]; writeUint16(sizeof(TEST_DATA), count); socket->async_send(asio::buffer(count, 2), boost::bind(&IOFetchTest::tcpSendHandler, this, - sizeof(count), _1, _2)); + 2, _1, _2)); socket->async_send(asio::buffer(TEST_DATA, sizeof(TEST_DATA)), boost::bind(&IOFetchTest::tcpSendHandler, this, - sizeof(count), _1, _2)); + sizeof(TEST_DATA), _1, _2)); } /// \brief Completion Handler for Sending TCP data @@ -221,7 +241,6 @@ public: void tcpSendHandler(size_t expected = 0, error_code ec = error_code(), size_t length = 0) { - std::cerr << "TCP Send Handler\n"; EXPECT_EQ(0, ec.value()); // Expect no error EXPECT_EQ(expected, length); // And that amount sent is as expected } @@ -234,7 +253,7 @@ public: /// /// \param result Result indicated by the callback void operator()(IOFetch::Result result) { - std::cerr << "Fetch completion\n"; + EXPECT_EQ(expected_, result); // Check correct result returned EXPECT_FALSE(run_); // Check it is run only once run_ = true; // Note success @@ -360,6 +379,7 @@ TEST_F(IOFetchTest, UdpSendReceive) { protocol_ = IOFetch::UDP; expected_ = IOFetch::SUCCESS; + // Set up the server. 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)); @@ -395,21 +415,17 @@ TEST_F(IOFetchTest, TcpSendReceive) { protocol_ = IOFetch::TCP; expected_ = IOFetch::SUCCESS; - std::cerr << "Creating socket\n"; // Socket into which the connection will be accepted tcp::socket socket(service_.get_io_service()); - std::cerr << "Creating acceptor\n"; // Acceptor object - called when the connection is made, the handler will // initiate a read on the socket. tcp::acceptor acceptor(service_.get_io_service(), tcp::endpoint(tcp::v4(), TEST_PORT)); - std::cerr << "Issuing async accept call\n"; acceptor.async_accept(socket, boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); // Post the TCP fetch object to send the query and receive the response. - std::cerr << "Posting TCP fetch\n"; service_.get_io_service().post(tcp_fetch_); // ... and execute all the callbacks. This exits when the fetch completes. diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc index 8ecadaf5ee..ca24e1ae4e 100644 --- a/src/lib/asiolink/tests/tcp_socket_unittest.cc +++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc @@ -318,14 +318,14 @@ TEST(TCPSocket, SequenceTest) { client_cb.setCode(43); // Some error EXPECT_FALSE(client.isOpenSynchronous()); client.open(&server_endpoint, client_cb); - + // Run the open and the accept callback and check that they ran. service.run_one(); service.run_one(); EXPECT_EQ(TCPCallback::ACCEPT, server_cb.called()); EXPECT_EQ(0, server_cb.getCode()); - + EXPECT_EQ(TCPCallback::OPEN, client_cb.called()); EXPECT_EQ(0, client_cb.getCode()); From be701769ab3ba884c550d22455ba09af3b725a07 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 28 Feb 2011 18:08:27 +0000 Subject: [PATCH 015/113] [trac499] Remove last dummy log call from io_fetch.cc --- src/lib/asiolink/io_fetch.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index faff645f1a..2992243be9 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -177,9 +176,6 @@ IOFetch::operator()(asio::error_code ec, size_t length) { // As this is a new fetch, clear the amount of data received data_->cumulative = 0; - - dlog("Sending " + msg.toText() + " to " + - data_->remote->getAddress().toText()); } // If we timeout, we stop, which will can cancel outstanding I/Os and From 77027490d53b644e157fce2df59c5dbd496d1e1a Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 28 Feb 2011 19:24:01 +0000 Subject: [PATCH 016/113] [trac499] Add "normalization" of received data ... i.e. copying TCP data into the receive buffer omitting the leading two-byte message length. --- src/lib/asiolink/asiolink_utilities.h | 2 +- src/lib/asiolink/io_asio_socket.h | 26 ++++++++++ src/lib/asiolink/io_fetch.cc | 15 +++--- src/lib/asiolink/tcp_socket.h | 27 +++++++++- src/lib/asiolink/tests/io_fetch_unittest.cc | 29 ++--------- src/lib/asiolink/tests/tcp_socket_unittest.cc | 52 +++++++++++++++++++ src/lib/asiolink/tests/udp_socket_unittest.cc | 50 ++++++++++++++++++ src/lib/asiolink/udp_socket.h | 21 +++++++- 8 files changed, 188 insertions(+), 34 deletions(-) diff --git a/src/lib/asiolink/asiolink_utilities.h b/src/lib/asiolink/asiolink_utilities.h index f7f82be695..659e6a0908 100644 --- a/src/lib/asiolink/asiolink_utilities.h +++ b/src/lib/asiolink/asiolink_utilities.h @@ -41,7 +41,7 @@ readUint16(const void* buffer) { /// \brief Write Unisgned 16-Bit Integer to Buffer /// -/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16. It +/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16. It /// should really be moved into a separate library. /// /// \param value 16-bit value to convert diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index fbf749ef33..c8fea049bb 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -26,6 +26,8 @@ #include #include +#include + #include #include @@ -224,6 +226,21 @@ public: /// needed. virtual bool receiveComplete(const void* data, size_t length) = 0; + /// \brief Append Normalized Data + /// + /// When a UDP buffer is received, the entire buffer contains the data. + /// When a TCP buffer is received, the first two bytes of the buffer hold + /// a length count. This method removes those bytes from the buffer. + /// + /// \param inbuf Input buffer. This contains the data received over the + /// network connection. + /// \param length Amount of data in the input buffer. If TCP, this includes + /// the two-byte count field. + /// \param outbuf Pointer to output buffer to which the data will be + /// appended. + virtual void appendNormalizedData(const void* inbuf, size_t length, + isc::dns::OutputBufferPtr outbuf) = 0; + /// \brief Cancel I/O On AsioSocket virtual void cancel() = 0; @@ -321,6 +338,15 @@ public: virtual bool receiveComplete(void*, size_t, size_t&) { return (true); } + /// \brief Append Normalized Data + /// + /// \param inbuf Unused. + /// \param length Unused. + /// \param outbuf unused. + virtual void appendNormalizedData(const void*, size_t, + isc::dns::OutputBufferPtr) + { + } /// \brief Cancel I/O On AsioSocket /// diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 2992243be9..82e1907587 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -72,8 +72,8 @@ struct IOFetchData { 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 - boost::shared_array data; ///< Temporary array for data + isc::dns::OutputBufferPtr received; ///< Received data put here + boost::shared_array staging; ///< Temporary array for received data IOFetch::Callback* callback; ///< Called on I/O Completion size_t cumulative; ///< Cumulative received amount bool stopped; ///< Have we stopped running? @@ -122,8 +122,8 @@ struct IOFetchData { ), question(query), msgbuf(new isc::dns::OutputBuffer(512)), - buffer(buff), - data(new char[IOFetch::MIN_LENGTH]), + received(buff), + staging(new char[IOFetch::MIN_LENGTH]), callback(cb), cumulative(0), stopped(false), @@ -211,11 +211,11 @@ IOFetch::operator()(asio::error_code ec, size_t length) { // we check if the operation is complete and if not, loop to read again. data_->origin = ASIO_RECVSOCK; do { - CORO_YIELD data_->socket->asyncReceive(data_->data.get(), + CORO_YIELD data_->socket->asyncReceive(data_->staging.get(), static_cast(MIN_LENGTH), data_->cumulative, data_->remote.get(), *this); data_->cumulative += length; - } while (!data_->socket->receiveComplete(data_->data.get(), + } while (!data_->socket->receiveComplete(data_->staging.get(), data_->cumulative)); /// Copy the answer into the response buffer. (TODO: If the @@ -223,7 +223,8 @@ IOFetch::operator()(asio::error_code ec, size_t length) { /// MutableBufferSequence, then it could be written to directly by /// async_receive_from() and this additional copy step would be /// unnecessary.) - data_->buffer->writeData(data_->data.get(), length); + data_->socket->appendNormalizedData(data_->staging.get(), + data_->cumulative, data_->received); // Finished with this socket, so close it. This will not generate an // I/O error, but reset the origin to unknown in case we change this. diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index 1146fa3548..492671e112 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -119,7 +119,7 @@ public: /// that was determined when the connection was opened.) /// \param callback Callback object. virtual void asyncSend(const void* data, size_t length, - const IOEndpoint* endpoint, C& callback); + const IOEndpoint* endpoint, C& callback); /// \brief Receive Asynchronously /// @@ -147,6 +147,21 @@ public: /// \return true if the receive is complete, false if not. virtual bool receiveComplete(const void* data, size_t length); + /// \brief Append Normalized Data + /// + /// When a UDP buffer is received, the entire buffer contains the data. + /// When a TCP buffer is received, the first two bytes of the buffer hold + /// a length count. This method removes those bytes from the buffer. + /// + /// \param inbuf Input buffer. This contains the data received over the + /// network connection. + /// \param length Amount of data in the input buffer. If TCP, this includes + /// the two-byte count field. + /// \param outbuf Pointer to output buffer to which the data will be + /// appended + virtual void appendNormalizedData(const void* inbuf, size_t length, + isc::dns::OutputBufferPtr outbuf); + /// \brief Cancel I/O On Socket virtual void cancel(); @@ -338,6 +353,16 @@ TCPSocket::receiveComplete(const void* data, size_t length) { return (complete); } +// Copy buffer less leading two bytes to the target buffer. + +template void +TCPSocket::appendNormalizedData(const void* inbuf, size_t length, + isc::dns::OutputBufferPtr outbuf) +{ + const uint8_t* bytebuff = static_cast(inbuf); + outbuf->writeData(bytebuff + 2, length - 2); +} + // Cancel I/O on the socket. No-op if the socket is not open. template void diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index 99acee923c..d743df2ab0 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -47,7 +47,7 @@ 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"; +const char TEST_DATA[] = "Test response from server to client"; /// \brief Test fixture for the asiolink::IOFetch. class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback @@ -262,30 +262,11 @@ public: // when one of the "servers" in this class has sent back the TEST_DATA. // Check the data is as expected/ if (expected_ == IOFetch::SUCCESS) { - size_t offset = 0; // Offset into start of buffer of data - if (protocol_ == IOFetch::UDP) { + EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength()); - // Check the length of data received against the amount expected. - EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength()); - - } else { - - // Check the length of data received against the amount expected - EXPECT_EQ(sizeof(TEST_DATA) + 2, result_buff_->getLength()); - - // Check the count field. This should be equal to the total - // length of the packet less 2 (the count field is equal to - // the total length of the message less the count field itself - - // RFC 1035, section 4.2.2). - uint16_t count = readUint16(result_buff_->getData()); - EXPECT_EQ(result_buff_->getLength(), count + 2); - - // Update offset and count for the content check. - offset += 2; - } - const void* start = static_cast( - static_cast(result_buff_->getData()) + offset); - EXPECT_TRUE(memcmp(TEST_DATA, start, sizeof(TEST_DATA)) == 0); + const uint8_t* start = static_cast(result_buff_->getData()); + EXPECT_TRUE(equal(TEST_DATA, (TEST_DATA + sizeof(TEST_DATA) - 1), + start)); } // ... and cause the run loop to exit. diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc index ca24e1ae4e..e59eafc140 100644 --- a/src/lib/asiolink/tests/tcp_socket_unittest.cc +++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc @@ -274,6 +274,58 @@ clientReadComplete(TCPSocket& client, TCPCallback& client_cb, return (complete); } + +// Receive complete method should return true only if the count in the first +// two bytes is equal to the size of the rest if the buffer. + +TEST(TCPSocket, receiveComplete) { + IOService service; // Used to instantiate socket + TCPSocket test(service); // Socket under test + uint8_t buffer[32]; // Buffer to check + + // Expect that the value is true whatever number is written in the first + // two bytes of the buffer. + uint16_t count = 0; + for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { + writeUint16(count, buffer); + if (count == (sizeof(buffer) - 2)) { + EXPECT_TRUE(test.receiveComplete(buffer, sizeof(buffer))); + } else { + EXPECT_FALSE(test.receiveComplete(buffer, sizeof(buffer))); + } + } +} + +// Check that the normalized data copy only copies all but the first two bytes +// of the buffer (whatever the count). + +TEST(TCPSocket, appendNormalizedData) { + IOService service; // Used to instantiate socket + TCPSocket test(service); // Socket under test + uint8_t inbuff[32]; // Buffer to check + isc::dns::OutputBufferPtr outbuff(new isc::dns::OutputBuffer(sizeof(inbuff))); + // Where data is written + + // Initialize the input buffer with data. + for (uint8_t i = 0; i < sizeof(inbuff); ++i) { + inbuff[i] = i + 1; // An arbitrary number + } + + // Loop to ensure that entire buffer is copied on all count values, no + // matter what. + uint16_t count = 0; + for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { + writeUint16(count, inbuff); + outbuff->clear(); + test.appendNormalizedData(inbuff, sizeof(inbuff), outbuff); + + EXPECT_EQ((sizeof(inbuff) - 2), outbuff->getLength()); + + const uint8_t* outptr = static_cast(outbuff->getData()); + EXPECT_TRUE(equal(&inbuff[2], &inbuff[sizeof(inbuff) - 1], outptr)); + } +} + // TODO: Need to add a test to check the cancel() method // Tests the operation of a TCPSocket by opening it, sending an asynchronous diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc index aa46498a97..2ee1080e10 100644 --- a/src/lib/asiolink/tests/udp_socket_unittest.cc +++ b/src/lib/asiolink/tests/udp_socket_unittest.cc @@ -35,8 +35,11 @@ #include #include +#include + #include +#include #include #include #include @@ -162,6 +165,53 @@ private: boost::shared_ptr ptr_; ///< Pointer to private data }; +// Receive complete method should return true regardless of what is in the first +// two bytes of a buffer. + +TEST(UDPSocket, receiveComplete) { + IOService service; // Used to instantiate socket + UDPSocket test(service); // Socket under test + uint8_t buffer[32]; // Buffer to check + + // Expect that the value is true whatever number is written in the first + // two bytes of the buffer. + uint16_t count = 0; + for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { + writeUint16(count, buffer); + EXPECT_TRUE(test.receiveComplete(buffer, sizeof(buffer))); + } +} + +// Check that the normalized data copy copies the entire buffer regardless of +// the first two bytes. + +TEST(UDPSocket, appendNormalizedData) { + IOService service; // Used to instantiate socket + UDPSocket test(service); // Socket under test + uint8_t inbuff[32]; // Buffer to check + isc::dns::OutputBufferPtr outbuff(new isc::dns::OutputBuffer(sizeof(inbuff))); + // Where data is written + + // Initialize the input buffer with data. + for (uint8_t i = 0; i < sizeof(inbuff); ++i) { + inbuff[i] = i + 1; // An arbitrary number + } + + // Loop to ensure that entire buffer is copied on all count values, no + // matter what. + uint16_t count = 0; + for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { + writeUint16(count, inbuff); + outbuff->clear(); + test.appendNormalizedData(inbuff, sizeof(inbuff), outbuff); + + EXPECT_EQ(sizeof(inbuff), outbuff->getLength()); + + const uint8_t* outptr = static_cast(outbuff->getData()); + EXPECT_TRUE(equal(&inbuff[0], &inbuff[sizeof(inbuff) - 1], outptr)); + } +} + // TODO: Need to add a test to check the cancel() method // Tests the operation of a UDPSocket by opening it, sending an asynchronous diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index 1a016d2d95..8399f2c625 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -107,7 +107,7 @@ public: /// \param endpoint Target of the send /// \param callback Callback object. virtual void asyncSend(const void* data, size_t length, - const IOEndpoint* endpoint, C& callback); + const IOEndpoint* endpoint, C& callback); /// \brief Receive Asynchronously /// @@ -137,6 +137,24 @@ public: return (true); } + /// \brief Append Normalized Data + /// + /// When a UDP buffer is received, the entire buffer contains the data. + /// When a TCP buffer is received, the first two bytes of the buffer hold + /// a length count. This method removes those bytes from the buffer. + /// + /// \param inbuf Input buffer. This contains the data received over the + /// network connection. + /// \param length Amount of data in the input buffer. If TCP, this includes + /// the two-byte count field. + /// \param outbuf Pointer to output buffer to which the data will be + /// appended + virtual void appendNormalizedData(const void* inbuf, size_t length, + isc::dns::OutputBufferPtr outbuf) + { + outbuf->writeData(inbuf, length); + } + /// \brief Cancel I/O On Socket virtual void cancel(); @@ -271,6 +289,7 @@ UDPSocket::asyncReceive(void* data, size_t length, size_t offset, } // Cancel I/O on the socket. No-op if the socket is not open. + template void UDPSocket::cancel() { if (isopen_) { From 5017cbe7d18c752b80e8e8289b9ab0315f5915d0 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Tue, 1 Mar 2011 12:21:39 +0000 Subject: [PATCH 017/113] [trac499] Add ability to determing protocol used by IOFetch --- src/lib/asiolink/io_fetch.cc | 23 ++++++++++++++------ src/lib/asiolink/io_fetch.h | 24 ++++++++++----------- src/lib/asiolink/tests/io_fetch_unittest.cc | 11 +++++++--- src/lib/asiolink/tests/run_unittests.cc | 6 ++++-- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 82e1907587..717d3d92f4 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -52,7 +52,7 @@ namespace asiolink { /// Use the ASIO logger -isc::log::Logger logger("asio"); +isc::log::Logger logger("asiolink"); /// \brief IOFetch Data /// @@ -75,9 +75,10 @@ struct IOFetchData { isc::dns::OutputBufferPtr received; ///< Received data put here boost::shared_array staging; ///< Temporary array for received data IOFetch::Callback* callback; ///< Called on I/O Completion + asio::deadline_timer timer; ///< Timer to measure timeouts + IOFetch::Protocol protocol; ///< Protocol being used 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 // In case we need to log an error, the origin of the last asynchronous @@ -91,7 +92,7 @@ struct IOFetchData { /// /// Just fills in the data members of the IOFetchData structure /// - /// \param protocol Either IOFetch::TCP or IOFetch::UDP. + /// \param proto Either IOFetch::TCP or IOFetch::UDP. /// \param service I/O Service object to handle the asynchronous /// operations. /// \param query DNS question to send to the upstream server. @@ -105,18 +106,18 @@ struct IOFetchData { /// \param wait Timeout for the fetch (in ms). /// /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554) - IOFetchData(IOFetch::Protocol protocol, IOService& service, + IOFetchData(IOFetch::Protocol proto, IOService& service, const isc::dns::Question& query, const IOAddress& address, uint16_t port, isc::dns::OutputBufferPtr& buff, IOFetch::Callback* cb, int wait) : - socket((protocol == IOFetch::UDP) ? + socket((proto == IOFetch::UDP) ? static_cast*>( new UDPSocket(service)) : static_cast*>( new TCPSocket(service)) ), - remote((protocol == IOFetch::UDP) ? + remote((proto == IOFetch::UDP) ? static_cast(new UDPEndpoint(address, port)) : static_cast(new TCPEndpoint(address, port)) ), @@ -125,9 +126,10 @@ struct IOFetchData { received(buff), staging(new char[IOFetch::MIN_LENGTH]), callback(cb), + timer(service.get_io_service()), + protocol(proto), cumulative(0), stopped(false), - timer(service.get_io_service()), timeout(wait), origin(ASIO_UNKORIGIN) {} @@ -144,6 +146,13 @@ IOFetch::IOFetch(Protocol protocol, IOService& service, { } +// Return protocol in use. + +IOFetch::Protocol +IOFetch::getProtocol() const { + return (data_->protocol); +} + /// The function operator is implemented with the "stackless coroutine" /// pattern; see internal/coroutine.h for details. diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index 479c54c8f4..ce39f0ae02 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -17,15 +17,14 @@ #include - #include #include #include -#include - #include +#include + #include #include @@ -138,6 +137,11 @@ public: uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb, int wait = -1); + /// \brief Return Current Protocol + /// + /// \return Protocol associated with this IOFetch object. + Protocol getProtocol() const; + /// \brief Coroutine entry point /// /// The operator() method is the method in which the coroutine code enters @@ -145,16 +149,7 @@ public: /// /// \param ec Error code, the result of the last asynchronous I/O operation. /// \param length Amount of data received on the last asynchronous read - void operator()(asio::error_code ec, size_t length); - - void operator()(asio::error_code ec) { - operator()(ec, 0); - } - - void operator()() { - asio::error_code ec; - operator()(ec); - } + void operator()(asio::error_code ec = asio::error_code(), size_t length = 0); /// \brief Terminate query /// @@ -172,6 +167,9 @@ private: /// \param ec ASIO error code void logIOFailure(asio::error_code ec); + // Member variables. All data is in a structure pointed to by a shared + // pointer. The IOFetch object is copied a number of times during its + // life, and only requiring a pointer to be copied reduces overhead. boost::shared_ptr data_; ///< Private data }; diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index d743df2ab0..c1961818c1 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -338,18 +338,23 @@ public: } }; +// Check the protocol +TEST_F(IOFetchTest, Protocol) { + EXPECT_EQ(IOFetch::UDP, udp_fetch_.getProtocol()); + EXPECT_EQ(IOFetch::TCP, tcp_fetch_.getProtocol()); +} -/// UDP Stop test - see IOFetchTest::stopTest() header. +// UDP Stop test - see IOFetchTest::stopTest() header. TEST_F(IOFetchTest, UdpStop) { stopTest(IOFetch::UDP, udp_fetch_); } -/// UDP premature stop test - see IOFetchTest::prematureStopTest() header. +// UDP premature stop test - see IOFetchTest::prematureStopTest() header. TEST_F(IOFetchTest, UdpPrematureStop) { prematureStopTest(IOFetch::UDP, udp_fetch_); } -/// UDP premature stop test - see IOFetchTest::timeoutTest() header. +// UDP premature stop test - see IOFetchTest::timeoutTest() header. TEST_F(IOFetchTest, UdpTimeout) { timeoutTest(IOFetch::UDP, udp_fetch_); } diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc index b481784784..c285f9e8c8 100644 --- a/src/lib/asiolink/tests/run_unittests.cc +++ b/src/lib/asiolink/tests/run_unittests.cc @@ -14,13 +14,15 @@ #include +#include #include int main(int argc, char* argv[]) { - ::testing::InitGoogleTest(&argc, argv); - isc::UnitTestUtil::addDataPath(TEST_DATA_DIR); + ::testing::InitGoogleTest(&argc, argv); // Initialize Google test + isc::log::setRootLoggerName("unittest"); // Set a root logger name + isc::UnitTestUtil::addDataPath(TEST_DATA_DIR); // Add location of test data return (RUN_ALL_TESTS()); } From 9ae710aa95a75d63e2a37ca7d79297f8ef308e6c Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Tue, 1 Mar 2011 17:29:53 +0000 Subject: [PATCH 018/113] [trac499] Checkpoint comment --- src/lib/asiolink/io_fetch.h | 2 +- src/lib/asiolink/recursive_query.cc | 68 ++- src/lib/asiolink/recursive_query.h | 14 + src/lib/asiolink/tests/io_fetch_unittest.cc | 28 - .../tests/recursive_query_unittest_2.cc | 509 ++++++++++++++++++ 5 files changed, 583 insertions(+), 38 deletions(-) create mode 100644 src/lib/asiolink/tests/recursive_query_unittest_2.cc diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index ce39f0ae02..ab7e9add75 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -106,7 +106,7 @@ public: /// \brief Callback method /// - /// This is the method called when the fecth completes. + /// This is the method called when the fetch completes. /// /// \param result Result of the fetch virtual void operator()(Result result) = 0; diff --git a/src/lib/asiolink/recursive_query.cc b/src/lib/asiolink/recursive_query.cc index 0ea8f0e94f..09c15e615b 100644 --- a/src/lib/asiolink/recursive_query.cc +++ b/src/lib/asiolink/recursive_query.cc @@ -55,10 +55,22 @@ RecursiveQuery::RecursiveQuery(DNSService& dns_service, unsigned retries) : dns_service_(dns_service), upstream_(new AddressVector(upstream)), upstream_root_(new AddressVector(upstream_root)), + test_server_("", 0), query_timeout_(query_timeout), client_timeout_(client_timeout), lookup_timeout_(lookup_timeout), retries_(retries) {} +// Set the test server - only used for unit testing. + +void +RecursiveQuery::setTestServer(const std::string& address, uint16_t port) { + dlog("Setting test server to " + address + "(" + + boost::lexical_cast(port) + ")"); + test_server_.first = address; + test_server_.second = port; +} + + namespace { typedef std::pair addr_t; @@ -88,6 +100,10 @@ private: // root servers...just copied over to the zone_servers_ boost::shared_ptr upstream_root_; + // Test server - only used for testing. This takes precedence over all + // other servers if the port is non-zero. + std::pair test_server_; + // Buffer to store the result. OutputBufferPtr buffer_; @@ -95,6 +111,12 @@ private: //shared_ptr server_; isc::resolve::ResolverInterface::CallbackPtr resolvercallback_; + // Protocol used for the last query. This is set to IOFetch::UDP when a + // new upstream query is initiated, and changed to IOFetch::TCP if a + // packet is returned with the TC bit set. It is stored here to detect the + // case of a TCP packet being returned with the TC bit set. + IOFetch::Protocol protocol_; + // To prevent both unreasonably long cname chains and cname loops, // we simply keep a counter of the number of CNAMEs we have // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN @@ -155,15 +177,27 @@ private: } // (re)send the query to the server. - void send() { + // + // \param protocol Protocol to use for the fetch (default is UDP) + void send(IOFetch::Protocol protocol = IOFetch::UDP) { const int uc = upstream_->size(); const int zs = zone_servers_.size(); + protocol_ = protocol; // Store protocol being used for this buffer_->clear(); - if (uc > 0) { + if (test_server_.second != 0) { + dlog("Sending upstream query (" + question_.toText() + + ") to test server at " + test_server_.first); + IOFetch query(protocol, io_, question_, + test_server_.first, + test_server_.second, buffer_, this, + query_timeout_); + ++queries_out_; + io_.get_io_service().post(query); + } else if (uc > 0) { int serverIndex = rand() % uc; dlog("Sending upstream query (" + question_.toText() + ") to " + upstream_->at(serverIndex).first); - IOFetch query(IOFetch::UDP, io_, question_, + IOFetch query(protocol, io_, question_, upstream_->at(serverIndex).first, upstream_->at(serverIndex).second, buffer_, this, query_timeout_); @@ -173,7 +207,7 @@ private: int serverIndex = rand() % zs; dlog("Sending query to zone server (" + question_.toText() + ") to " + zone_servers_.at(serverIndex).first); - IOFetch query(IOFetch::UDP, io_, question_, + IOFetch query(protocol, io_, question_, zone_servers_.at(serverIndex).first, zone_servers_.at(serverIndex).second, buffer_, this, query_timeout_); @@ -291,6 +325,18 @@ private: return true; } break; + case isc::resolve::ResponseClassifier::TRUNCATED: + // Truncated packet. If the protocol we used for the last one is + // UDP, re-query using TCP. Otherwise regard it as an error. + if (protocol_ == IOFetch::UDP) { + dlog("Response truncated, re-querying over TCP"); + send(IOFetch::TCP); + break; + } + // Was a TCP query so we have received a packet over TCP with the TC + // bit set: drop through to common error processing. + // TODO: Can we use what we have received instead of discarding it? + case isc::resolve::ResponseClassifier::EMPTY: case isc::resolve::ResponseClassifier::EXTRADATA: case isc::resolve::ResponseClassifier::INVNAMCLASS: @@ -302,7 +348,7 @@ private: case isc::resolve::ResponseClassifier::NOTSINGLE: case isc::resolve::ResponseClassifier::OPCODE: case isc::resolve::ResponseClassifier::RCODE: - case isc::resolve::ResponseClassifier::TRUNCATED: + // Should we try a different server rather than SERVFAIL? isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL()); @@ -320,6 +366,7 @@ public: MessagePtr answer_message, boost::shared_ptr upstream, boost::shared_ptr upstream_root, + std::pair& test_server, OutputBufferPtr buffer, isc::resolve::ResolverInterface::CallbackPtr cb, int query_timeout, int client_timeout, int lookup_timeout, @@ -330,8 +377,10 @@ public: answer_message_(answer_message), upstream_(upstream), upstream_root_(upstream_root), + test_server_(test_server), buffer_(buffer), resolvercallback_(cb), + protocol_(IOFetch::UDP), cname_count_(0), query_timeout_(query_timeout), retries_(retries), @@ -441,7 +490,6 @@ public: // This function is used as callback from DNSQuery. virtual void operator()(IOFetch::Result result) { - // XXX is this the place for TCP retry? --queries_out_; if (!done_ && result != IOFetch::TIME_OUT) { // we got an answer @@ -496,7 +544,8 @@ RecursiveQuery::resolve(const QuestionPtr& question, dlog("Message not found in cache, starting recursive query"); // It will delete itself when it is done new RunningQuery(io, *question, answer_message, upstream_, - upstream_root_, buffer, callback, query_timeout_, + upstream_root_, test_server_, + buffer, callback, query_timeout_, client_timeout_, lookup_timeout_, retries_, cache_); } @@ -533,8 +582,9 @@ RecursiveQuery::resolve(const Question& question, dlog("Message not found in cache, starting recursive query"); // It will delete itself when it is done new RunningQuery(io, question, answer_message, upstream_, upstream_root_, - buffer, crs, query_timeout_, client_timeout_, - lookup_timeout_, retries_, cache_); + test_server_, + buffer, crs, query_timeout_, client_timeout_, + lookup_timeout_, retries_, cache_); } } diff --git a/src/lib/asiolink/recursive_query.h b/src/lib/asiolink/recursive_query.h index 6ef0069483..626ff4263e 100644 --- a/src/lib/asiolink/recursive_query.h +++ b/src/lib/asiolink/recursive_query.h @@ -98,12 +98,26 @@ public: isc::dns::MessagePtr answer_message, isc::dns::OutputBufferPtr buffer, DNSServer* server); + + /// \brief Set Test Server + /// + /// This method is *only* for unit testing the class. If set, it enables + /// recursive behaviour but, regardless of responses received, sends every + /// query to the test server. + /// + /// The test server is enabled by setting a non-zero port number. + /// + /// \param address IP address of the test server. + /// \param port Port number of the test server + void setTestServer(const std::string& address, uint16_t port); + private: DNSService& dns_service_; boost::shared_ptr > > upstream_; boost::shared_ptr > > upstream_root_; + std::pair test_server_; int query_timeout_; int client_timeout_; int lookup_timeout_; diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index c1961818c1..2869efd343 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -95,34 +95,6 @@ public: msg.toWire(renderer); } - /// \brief Read uint16_t from network-byte-order buffer - /// - /// Adapted from isc::dns::InputBuffer::readUint16(). - /// - /// \param data Pointer to at least two bytes of data which are in network - /// byte order. - /// - /// \return uint16_t value in host byte order. - uint16_t readUint16(const void* data) { - const uint8_t* cp = static_cast(data); - - uint16_t value = ((unsigned int)(cp[0])) << 8; - value |= ((unsigned int)(cp[1])); - - return (value); - } - - /// \brief Write uint16_t to network-byte-order buffer - /// - /// Adapted from isc::dns::OutputBuffer::writeUint16(). - /// - /// \param value The 16-bit integer to be written into the buffer. - /// \param data Pointer to buffer at least two bytes long - void writeUint16(uint16_t value, uint8_t* data) { - data[0] = static_cast((value & 0xff00U) >> 8); - data[1] = static_cast(value & 0x00ffU); - } - /// \brief UDP Response handler (the "remote UDP DNS server") /// /// When IOFetch is sending data, this response handler emulates the remote diff --git a/src/lib/asiolink/tests/recursive_query_unittest_2.cc b/src/lib/asiolink/tests/recursive_query_unittest_2.cc new file mode 100644 index 0000000000..eb05db7d09 --- /dev/null +++ b/src/lib/asiolink/tests/recursive_query_unittest_2.cc @@ -0,0 +1,509 @@ +// 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 +#include +#include +#include + +using namespace asio; +using namespace isc::dns; +using namespace asio::ip; +using namespace std; + +/// RecursiveQuery Test - 2 +/// +/// The second part of the RecursiveQuery unit tests, this attempts to get the +/// RecursiveQuery object to follow a set of referrals for "www.example.org" to +/// and to invoke TCP fallback on one of the queries. In particular, we +/// expect that the test will do the following in an attempt to resolve +/// www.example.org: +/// +/// - Send a question over UDP to the root servers - get referral to "org". +/// - Send question over UDP to "org" - get referral to "example.org" with TC bit set. +/// - Send question over TCP to "org" - get referral to "example.org". +/// - Send question over UDP to "example.org" - get response for www.example.org. +/// +/// The order of queries is partly to test that after there is a fallover to TCP, +/// queries revert to UDP. +/// +/// By using the "test_server_" element of RecursiveQuery, all queries are +/// directed to one or other of the "servers" in the RecursiveQueryTest2 class. + +namespace asiolink { + +const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1")); +const uint16_t TEST_PORT(5301); + + +/// \brief Test fixture for the asiolink::IOFetch. +class RecursiveQueryTest2 : public virtual ::testing::Test, public virtual IOFetch::Callback +{ +public: + + /// \brief Status of query + /// + /// Set before the query and then by each "server" when responding. + enum QueryStatus { + NONE = 0, ///< Default + UDP_ROOT = 1, ///< Query root server over UDP + UDP_ORG = 2, ///< Query ORG server over UDP + TCP_ORG = 3, ///< Query ORG server over TCP + UDP_EXAMPLE_ORG = 4, ///< Query EXAMPLE.ORG server over UDP + COMPLETE = 5 ///< Query is complete + }; + + IOService service_; ///< Service to run everything + Question question_; ///< What to ask + QueryStatus last_; ///< Last state + QueryStatus expected_; ///< Expected next state + OutputBufferPtr question_buffer_; ///< Question we expect to receive + + size_t tcp_cumulative_; ///< Cumulative TCP data received + tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives + size_t tcp_length_; ///< Expected length value + uint8_t tcp_receive_buffer_[512]; ///< Receive buffer for TCP I/O + OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O + tcp::socket tcp_socket_; ///< Socket used by TCP server + + /// Data for UDP + udp::endpoint udp_endpoint_; ///< Endpoint for UDP receives + size_t udp_length_; ///< Expected length value + uint8_t udp_receive_buffer_[512]; ///< Receive buffer for UDP I/O + OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O + udp::socket udp_socket_; ///< Socket used by UDP server + + + /// \brief Constructor + RecursiveQueryTest2() : + service_(), + question_(Name("www.example.org"), RRClass::IN(), RRType::A()), + last_(NONE), + expected_(NONE), + question_buffer_(), + tcp_cumulative_(0), + tcp_endpoint_(TEST_HOST, TEST_PORT), + tcp_length_(0), + tcp_receive_buffer_(), + tcp_send_buffer_(), + tcp_socket_(service_.get_io_service()), + udp_endpoint_(), + udp_length_(0), + udp_receive_buffer_(), + udp_send_buffer_(), + udp_socket_(service_.get_io_service(), udp::v4()) + { + + } + + + /// \brief Set Common Message Bits + /// + /// Sets up the common bits of a response message returned by the handlers. + /// + /// \param msg Message buffer in RENDER mode. + /// \param qid QIT to set the message to + void setCommonMessage(isc::dns::Message& msg, uint16_t qid = 0) { + msg.setQid(qid); + msg.setHeaderFlag(Message::HEADERFLAG_QR); + msg.setOpcode(Opcode::QUERY()); + msg.setHeaderFlag(Message::HEADERFLAG_AA); + msg.setRcode(Rcode::NOERROR()); + msg.addQuestion(question_); + } + + /// \brief Set Referral to "org" + /// + /// Sets up the passed-in message (expected to be in "RENDER" mode to + /// indicate a referral to fictitious .org nameservers. + /// + /// \param msg Message to update with referral information. + void setReferralOrg(isc::dns::Message& msg) { + + // Do a referral to org. We'll define all NS records as "in-zone" + // nameservers (and so supply glue) to avoid the possibility of + // the resolver doing another lookup. + RRSetPtr org_ns(new RRSet(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300))); + org_ns->addRdata(NS("ns1.org.")); + org_ns->addRdata(NS("ns2.org.")); + msg.addRRset(Message::SECTION_AUTHORITY, org_ns); + + RRsetPtr org_ns1(new RRSet(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + org_ns1->addRdata(A("192.0.2.1")); + msg.addRRset(Message::SECTION_ADDITIONAL, org_ns1); + + RRsetPtr org_ns1(new RRSet(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + org_ns2->addRdata(A("192.0.2.2")); + msg.addRRset(Message::SECTION_ADDITIONAL, org_ns2); + } + + /// \brief Set Referral to "example.org" + /// + /// Sets up the passed-in message (expected to be in "RENDER" mode to + /// indicate a referral to fictitious example.org nameservers. + /// + /// \param msg Message to update with referral information. + void setReferralExampleOrg(isc::dns::Message& msg) { + + // Do a referral to example.org. As before, we'll define all NS + // records as "in-zone" nameservers (and so supply glue) to avoid + // the possibility of the resolver doing another lookup. + RRSetPtr example_org_ns(new RRSet(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300))); + example_org_ns->addRdata(NS("ns1.example.org.")); + example_org_ns->addRdata(NS("ns2.example.org.")); + msg.addRRset(Message::SECTION_AUTHORITY, example_org_ns); + + RRsetPtr example_org_ns1(new RRSet(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + example_org_ns1->addRdata(A("192.0.2.11")); + msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1); + + RRsetPtr org_ns1(new RRSet(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + example_org_ns2->addRdata(A("192.0.2.12")); + msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns2); + } + + /// \brief Set Answer to "www.example.org" + /// + /// Sets up the passed-in message (expected to be in "RENDER" mode to + /// indicate an authoritative answer to www.example.org. + /// + /// \param msg Message to update with referral information. + void setAnswerWwwExampleOrg(isc::dns::Message& msg) { + + // Give a response for www.example.org. + RRsetPtr www_example_org_a(new RRSet(Name("www.example.org."), RRClass::IN(), RRType::NS(), RRTTL(300))); + www_example_org_a->addRdata(A("192.0.2.21")); + msg.addRRset(Message::SECTION_ANSWER, example_org_ns); + + // ... and add the Authority and Additional sections. (These are the + // same as in the referral to example.org from the .org nameserver.) + setReferralExampleOrg(msg); + } + + /// \brief UDP Receive Handler + /// + /// This is invoked when a message is received from the RecursiveQuery + /// Object. It formats an answer and sends it, with the UdpSendHandler + /// method being specified as the completion handler. + /// + /// \param remote Endpoint to which to send the answer + /// \param socket Socket to use to send the answer + /// \param ec ASIO error code, completion code of asynchronous I/O issued + /// by the "server" to receive data. + /// \param length Amount of data received. + void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) { + + // Expected state should be one greater than the last state. + EXPECT_EQ(static_cast(expected_), static_cast(last_) + 1); + last_ = expected_; + + // The QID in the incoming data is random so set it to 0 for the + // data comparison check. (It is set to 0 in the buffer containing + // the expected data.) + uint16_t qid = readUint16(udp_receive_buffer_); + udp_receive_buffer_[0] = udp_receive_buffer_[1] = 0; + + // Check that length of the received data and the expected data are + // identical, then check that the data is identical as well. + EXPECT_EQ(question_buff_->getLength(), length); + EXPECT_TRUE(equal(udp_receive_buffer_, (udp_receive_buffer_ + length - 1), + static_cast(question_buff_->getData()))); + + // The message returned depends on what state we are in. Set up + // common stuff first: bits not mentioned are set to 0. + Message msg(Message::RENDER); + setCommonMessage(msg, qid); + + // Set up state-dependent bits: + switch (expected_) { + case UDP_ROOT: + // Return a referral to org. We then expect to query the "org" + // nameservers over UDP next. + setReferralOrg(msg); + expected_ = UDP_ORG; + break; + + case UDP_ORG: + // Return a referral to example.org. We explicitly set the TC bit to + // force a repeat query to the .org nameservers over TCP. + setReferralExampleOrg(msg); + msg.setHeaderFlag(Message::HEADERFLAG_TC); + expected_ = TCP_ORG; + break; + + case UDP_EXAMPLE_ORG: + // Return the answer to the question. + setAnswerWwwExampleOrg(msg); + expected_ = COMPLETE; + break; + + default: + FAIL() << "UdpReceiveHandler called with unknown state"; + } + + // Convert to wire format + MessageRenderer renderer(*udp_send_buffer_); + msg.toWire(renderer); + + // Return a message back to the IOFetch object. + udp_socket_.send_to(asio::buffer(udp_send_buffer_.getData(), + udp_send_buffer_.getLength()), + boost::bind(&RecursiveQueryTest2::UdpSendHandler, + this, _1, _2)); + + // Set the expected length for the send handler. + udp_length_ = udp_send_buffer_.getLength(); + } + + /// \brief UDP Send Handler + /// + /// Called when a send operation of the UDP server (i.e. a response + /// being sent to the RecursiveQuery) has completed, this re-issues + /// a read call. + void udpSendHandler(error_code ec = error_code(), size_t length = 0) { + + // Check send was OK + EXPECT_EQ(0, ec.value()); + EXPECT_EQ(udp_length_, length); + + // Reissue the receive. + udp_socket_.async_receive_from( + asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)), + udp_endpoint_ + boost::bind(&recursiveQuery2::udpReceiveHandler, this, _1, _2)); + } + + /// \brief Completion Handler for Accepting TCP Data + /// + /// Called when the remote system connects to the "server". It issues + /// an asynchronous read on the socket to read data. + /// + /// \param socket Socket on which data will be received + /// \param ec Boost error code, value should be zero. + void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) + { + // Expect that the accept completed without a problem. + EXPECT_EQ(0, ec.value()); + + // Initiate a read on the socket, indicating that nothing has yet been + // received. + tcp_cumulative_ = 0; + tcp_socket_.async_receive( + asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)), + boost::bind(&recursiveQuery2::tcpReceiveHandler, this, _1, _2)); + } + + /// \brief Completion Handler for Receiving TCP Data + /// + /// Reads data from the RecursiveQuery object and loops, reissuing reads, + /// until all the message has been read. It then sends + /// + /// \param socket Socket to use to send the answer + /// \param ec ASIO error code, completion code of asynchronous I/O issued + /// by the "server" to receive data. + /// \param length Amount of data received. + void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) + { + // Expect that the receive completed without a problem. + EXPECT_EQ(0, ec.value()); + + // Have we received all the data? We know this by checking if the two- + // byte length count in the message is equal to the data received. + tcp_cumulative_ += length; + bool complete = false; + if (tcp_cumulative_ > 2) { + uint16_t dns_length = readUint16(tcp_receive_buffer_); + complete = ((dns_length + 2) == tcp_cumulative_); + } + + if (!complete) { + + // Not complete yet, issue another read. + tcp_socket_.async_receive( + asio::buffer(tcp_receive_buffer_ + tcp_cumulative_, + sizeof(tcp_receive_buffer_) - tcp_cumulative_), + boost::bind(&recursiveQuery2::tcpReceiveHandler, this, _1, _2)); + return; + } + + // Have received a TCP message. Expected state should be one greater + // than the last state. + EXPECT_EQ(static_cast(expected_), static_cast(last_) + 1); + last_ = expected_; + + // Check that length of the received data and the expected data are + // identical (taking into account the two-byte count), then check that + // the data is identical as well (after zeroing the QID). + EXPECT_EQ(question_buff_->getLength() + 2, tcp_cumulative_); + uint16_t qid = readUint16(&udp_receive_buffer_[2]); + tcp_receive_buffer_[2] = tcp_receive_buffer_[3] = 0; + EXPECT_TRUE(equal((tcp_receive_buffer_ + 2), + (tcp_receive_buffer_ + tcp_cumulative_), + static_cast(question_buff_->getData()))); + + + // Return a message back. This is a referral to example.org, which + // should result in another query over UDP. + Message msg(Message::RENDER); + setCommonMessage(msg, qid); + setReferralExampleOrg(msg); + + // Convert to wire format + MessageRenderer renderer(*tcp_send_buffer_); + msg.toWire(renderer); + + // Expected next state (when checked) is the UDP query to example.org. + expected_ = UDP_EXAMPLE_ORG; + + // We'll write the message in two parts, the count and the message + // itself. When specifying the send handler, the expected size of the + // data written is passed as the first parameter. + uint8_t count[2]; + writeUint16(tcp_send_buffer_->getLength(), count); + socket->async_send(asio::buffer(count, 2), + boost::bind(&IOFetchTest::tcpSendHandler, this, + 2, _1, _2)); + socket->async_send(asio::buffer(tcp_send_buffer_->getData(), + tcp_send_buffer_->getLength()), + boost::bind(&RecursiveQuery2::tcpSendHandler, this, + sizeof(TEST_DATA), _1, _2)); + } + + /// \brief Completion Handler for Sending TCP data + /// + /// Called when the asynchronous send of data back to the RecursiveQuery + /// by the TCP "server" in this class has completed. (This send has to + /// be asynchronous because control needs to return to the caller in order + /// for the IOService "run()" method to be called to run the handlers.) + /// + /// \param expected Number of bytes that were expected to have been sent. + /// \param ec Boost error code, value should be zero. + /// \param length Number of bytes sent. + void tcpSendHandler(size_t expected = 0, error_code ec = error_code(), + size_t length = 0) + { + EXPECT_EQ(0, ec.value()); // Expect no error + EXPECT_EQ(expected, length); // And that amount sent is as expected + } + + /// \brief Resolver Callback Completion + /// + /// This is the callback's operator() method which is called when the + /// resolution of the query is complete. It checks that the data received + /// is the wire format of the data sent back by the server. + /// + /// \param result Result indicated by the callback + void operator()(IOFetch::Result result) { + + EXPECT_EQ(COMPLETE, expected_); + /* + EXPECT_EQ(expected_, result); // Check correct result returned + EXPECT_FALSE(run_); // Check it is run only once + run_ = true; // Note success + + // If the expected result for SUCCESS, then this should have been called + // when one of the "servers" in this class has sent back the TEST_DATA. + // Check the data is as expected/ + if (expected_ == IOFetch::SUCCESS) { + EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength()); + + const uint8_t* start = static_cast(result_buff_->getData()); + EXPECT_TRUE(equal(TEST_DATA, (TEST_DATA + sizeof(TEST_DATA) - 1), + start)); + } + + // ... and cause the run loop to exit. + * */ + service_.stop(); + } + +// Sets up the UDP and TCP "servers", then tries a resolution. + +TEST_F(RecursiveQueryTest2, Resolve) { + + // Set the state of the + // Set up the UDP server and issue the first read. + udp_socket_.set_option(socket_base::reuse_address(true)); + udp_socket_.bind(udp::endpoint(TEST_HOST, TEST_PORT)); + udp_socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)), + remote, + boost::bind(&IOFetchTest::udpReceiveHandler, this, &remote, &socket, + _1, _2)); + service_.get_io_service().post(udp_fetch_); + service_.run(); + + socket.close(); + + EXPECT_TRUE(run_);; +} + +// Do the same tests for TCP transport + +TEST_F(IOFetchTest, TcpStop) { + stopTest(IOFetch::TCP, tcp_fetch_); +} + +TEST_F(IOFetchTest, TcpPrematureStop) { + prematureStopTest(IOFetch::TCP, tcp_fetch_); +} + +TEST_F(IOFetchTest, TcpTimeout) { + timeoutTest(IOFetch::TCP, tcp_fetch_); +} + +TEST_F(IOFetchTest, TcpSendReceive) { + protocol_ = IOFetch::TCP; + expected_ = IOFetch::SUCCESS; + + // Socket into which the connection will be accepted + tcp::socket socket(service_.get_io_service()); + + // Acceptor object - called when the connection is made, the handler will + // initiate a read on the socket. + tcp::acceptor acceptor(service_.get_io_service(), + tcp::endpoint(tcp::v4(), TEST_PORT)); + acceptor.async_accept(socket, + boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); + + // Post the TCP fetch object to send the query and receive the response. + service_.get_io_service().post(tcp_fetch_); + + // ... and execute all the callbacks. This exits when the fetch completes. + service_.run(); + EXPECT_TRUE(run_); // Make sure the callback did execute + + socket.close(); +} + +} // namespace asiolink From e55ec73b0bdcc2b7d6286c6a18886de194280947 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Tue, 1 Mar 2011 15:50:10 -0800 Subject: [PATCH 019/113] [trac606] (blindly) imported basic framework of BIND 9 system tests --- tests/system/cleanall.sh | 38 +++++++ tests/system/conf.sh.in | 77 +++++++++++++ tests/system/ifconfig.sh | 228 +++++++++++++++++++++++++++++++++++++++ tests/system/run.sh | 127 ++++++++++++++++++++++ tests/system/runall.sh | 46 ++++++++ 5 files changed, 516 insertions(+) create mode 100644 tests/system/cleanall.sh create mode 100644 tests/system/conf.sh.in create mode 100644 tests/system/ifconfig.sh create mode 100644 tests/system/run.sh create mode 100644 tests/system/runall.sh diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh new file mode 100644 index 0000000000..9b6aa6f85f --- /dev/null +++ b/tests/system/cleanall.sh @@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# $Id: cleanall.sh,v 1.11 2007/06/19 23:47:00 tbox Exp $ + +# +# Clean up after system tests. +# + +SYSTEMTESTTOP=. +. $SYSTEMTESTTOP/conf.sh + + +find . -type f \( \ + -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \ + -o -name '*.pid' -o -name '*.keyset' -o -name named.run \ + -o -name lwresd.run -o -name ans.run \) -print | xargs rm -f + +status=0 + +for d in $SUBDIRS +do + test ! -f $d/clean.sh || ( cd $d && sh clean.sh ) +done diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in new file mode 100644 index 0000000000..09993033c0 --- /dev/null +++ b/tests/system/conf.sh.in @@ -0,0 +1,77 @@ +#!/bin/sh +# +# Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000-2003 Internet Software Consortium. +# +# 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. + +# $Id: conf.sh.in,v 1.59 2011-01-13 04:59:24 tbox Exp $ + +# +# Common configuration data for system tests, to be sourced into +# other shell scripts. +# + +# Find the top of the BIND9 tree. +TOP=${SYSTEMTESTTOP:=.}/../../.. + +# Make it absolute so that it continues to work after we cd. +TOP=`cd $TOP && pwd` + +NAMED=$TOP/bin/named/named +# We must use "named -l" instead of "lwresd" because argv[0] is lost +# if the program is libtoolized. +LWRESD="$TOP/bin/named/named -l" +DIG=$TOP/bin/dig/dig +RNDC=$TOP/bin/rndc/rndc +NSUPDATE=$TOP/bin/nsupdate/nsupdate +DDNSCONFGEN=$TOP/bin/confgen/ddns-confgen +KEYGEN=$TOP/bin/dnssec/dnssec-keygen +KEYFRLAB=$TOP/bin/dnssec/dnssec-keyfromlabel +SIGNER=$TOP/bin/dnssec/dnssec-signzone +REVOKE=$TOP/bin/dnssec/dnssec-revoke +SETTIME=$TOP/bin/dnssec/dnssec-settime +DSFROMKEY=$TOP/bin/dnssec/dnssec-dsfromkey +CHECKZONE=$TOP/bin/check/named-checkzone +CHECKCONF=$TOP/bin/check/named-checkconf +PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -s 0 -p 1234" +PK11LIST="$TOP/bin/pkcs11/pkcs11-list -s 0 -p 1234" +PK11DEL="$TOP/bin/pkcs11/pkcs11-destroy -s 0 -p 1234" +JOURNALPRINT=$TOP/bin/tools/named-journalprint + +# The "stress" test is not run by default since it creates enough +# load on the machine to make it unusable to other users. +# v6synth +SUBDIRS="acl allow_query addzone autosign cacheclean checkconf checknames + dlv @DLZ_SYSTEM_TEST@ dlzexternal dns64 dnssec forward glue gost ixfr limits + lwresd masterfile masterformat metadata notify nsupdate pending pkcs11 + resolver rpz rrsetorder sortlist smartsign staticstub stub tkey + tsig tsiggss unknown upforwd views xfer xferquota zonechecks" + +# PERL will be an empty string if no perl interpreter was found. +PERL=@PERL@ +if test -n "$PERL" +then + if $PERL -e "use IO::Socket::INET6;" 2> /dev/null + then + TESTSOCK6="$PERL $TOP/bin/tests/system/testsock6.pl" + else + TESTSOCK6=false + fi +else + TESTSOCK6=false +fi + +export NAMED LWRESD DIG NSUPDATE KEYGEN KEYFRLAB SIGNER KEYSIGNER KEYSETTOOL \ + PERL SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ + JOURNALPRINT diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh new file mode 100644 index 0000000000..dd204cb92d --- /dev/null +++ b/tests/system/ifconfig.sh @@ -0,0 +1,228 @@ +#!/bin/sh +# +# Copyright (C) 2004, 2007-2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000-2003 Internet Software Consortium. +# +# 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. + +# $Id: ifconfig.sh,v 1.59 2010/06/11 23:46:49 tbox Exp $ + +# +# Set up interface aliases for bind9 system tests. +# +# IPv4: 10.53.0.{1..7} RFC 1918 +# IPv6: fd92:7065:b8e:ffff::{1..7} ULA +# + +config_guess="" +for f in ./config.guess ../../../config.guess +do + if test -f $f + then + config_guess=$f + fi +done + +if test "X$config_guess" = "X" +then + cat <&2 +$0: must be run from the top level source directory or the +bin/tests/system directory +EOF + exit 1 +fi + +# If running on hp-ux, don't even try to run config.guess. +# It will try to create a temporary file in the current directory, +# which fails when running as root with the current directory +# on a NFS mounted disk. + +case `uname -a` in + *HP-UX*) sys=hpux ;; + *) sys=`sh $config_guess` ;; +esac + +case "$2" in +[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base=$2;; +*) base="" +esac + +case "$3" in +[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base6=$2;; +*) base6="" +esac + +case "$1" in + + start|up) + for ns in 1 2 3 4 5 6 7 + do + if test -n "$base" + then + int=`expr $ns + $base - 1` + else + int=$ns + fi + if test -n "$base6" + then + int6=`expr $ns + $base6 - 1` + else + int6=$ns + fi + case "$sys" in + *-pc-solaris2.5.1) + ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up + ;; + *-sun-solaris2.[6-7]) + ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up + ;; + *-*-solaris2.[8-9]|*-*-solaris2.1[0-9]) + /sbin/ifconfig lo0:$int plumb + /sbin/ifconfig lo0:$int 10.53.0.$ns up + if test -n "$int6" + then + /sbin/ifconfig lo0:$int6 inet6 plumb + /sbin/ifconfig lo0:$int6 \ + inet6 fd92:7065:b8e:ffff::$ns up + fi + ;; + *-*-linux*) + ifconfig lo:$int 10.53.0.$ns up netmask 255.255.255.0 + ifconfig lo inet6 add fd92:7065:b8e:ffff::$ns/64 + ;; + *-unknown-freebsd*) + ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias + ;; + *-unknown-netbsd*) + ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0 + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias + ;; + *-unknown-openbsd*) + ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0 + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias + ;; + *-*-bsdi[3-5].*) + ifconfig lo0 add 10.53.0.$ns netmask 255.255.255.0 + ;; + *-dec-osf[4-5].*) + ifconfig lo0 alias 10.53.0.$ns + ;; + *-sgi-irix6.*) + ifconfig lo0 alias 10.53.0.$ns + ;; + *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*) + ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff + ;; + *-ibm-aix4.*|*-ibm-aix5.*) + ifconfig lo0 alias 10.53.0.$ns + ifconfig lo0 inet6 alias -dad fd92:7065:b8e:ffff::$ns/64 + ;; + hpux) + ifconfig lo0:$int 10.53.0.$ns netmask 255.255.255.0 up + ifconfig lo0:$int inet6 fd92:7065:b8e:ffff::$ns up + ;; + *-sco3.2v*) + ifconfig lo0 alias 10.53.0.$ns + ;; + *-darwin*) + ifconfig lo0 alias 10.53.0.$ns + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias + ;; + *) + echo "Don't know how to set up interface. Giving up." + exit 1 + esac + done + ;; + + stop|down) + for ns in 7 6 5 4 3 2 1 + do + if test -n "$base" + then + int=`expr $ns + $base - 1` + else + int=$ns + fi + case "$sys" in + *-pc-solaris2.5.1) + ifconfig lo0:$int 0.0.0.0 down + ;; + *-sun-solaris2.[6-7]) + ifconfig lo0:$int 10.53.0.$ns down + ;; + *-*-solaris2.[8-9]|*-*-solaris2.1[0-9]) + ifconfig lo0:$int 10.53.0.$ns down + ifconfig lo0:$int 10.53.0.$ns unplumb + if test -n "$int6" + then + ifconfig lo0:$int6 inet6 down + ifconfig lo0:$int6 inet6 unplumb + fi + ;; + *-*-linux*) + ifconfig lo:$int 10.53.0.$ns down + ifconfig lo inet6 del fd92:7065:b8e:ffff::$ns/64 + ;; + *-unknown-freebsd*) + ifconfig lo0 10.53.0.$ns delete + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete + ;; + *-unknown-netbsd*) + ifconfig lo0 10.53.0.$ns delete + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete + ;; + *-unknown-openbsd*) + ifconfig lo0 10.53.0.$ns delete + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete + ;; + *-*-bsdi[3-5].*) + ifconfig lo0 remove 10.53.0.$ns + ;; + *-dec-osf[4-5].*) + ifconfig lo0 -alias 10.53.0.$ns + ;; + *-sgi-irix6.*) + ifconfig lo0 -alias 10.53.0.$ns + ;; + *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*) + ifconfig lo0 -alias 10.53.0.$ns + ;; + *-ibm-aix4.*|*-ibm-aix5.*) + ifconfig lo0 delete 10.53.0.$ns + ifconfig lo0 delete inet6 fd92:7065:b8e:ffff::$ns/64 + ;; + hpux) + ifconfig lo0:$int 0.0.0.0 + ifconfig lo0:$int inet6 :: + ;; + *-sco3.2v*) + ifconfig lo0 -alias 10.53.0.$ns + ;; + *darwin*) + ifconfig lo0 -alias 10.53.0.$ns + ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete + ;; + *) + echo "Don't know how to destroy interface. Giving up." + exit 1 + esac + done + + ;; + + *) + echo "Usage: $0 { up | down } [base]" + exit 1 +esac diff --git a/tests/system/run.sh b/tests/system/run.sh new file mode 100644 index 0000000000..81f3f2b515 --- /dev/null +++ b/tests/system/run.sh @@ -0,0 +1,127 @@ +#!/bin/sh +# +# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# $Id: run.sh,v 1.45 2010-12-20 21:35:45 each Exp $ + +# +# Run a system test. +# + +SYSTEMTESTTOP=. +. $SYSTEMTESTTOP/conf.sh + +stopservers=true + +case $1 in + --keep) stopservers=false; shift ;; +esac + +test $# -gt 0 || { echo "usage: $0 [--keep] test-directory" >&2; exit 1; } + +test=$1 +shift + +test -d $test || { echo "$0: $test: no such test" >&2; exit 1; } + +echo "S:$test:`date`" >&2 +echo "T:$test:1:A" >&2 +echo "A:System test $test" >&2 + +if [ x$PERL = x ] +then + echo "I:Perl not available. Skipping test." >&2 + echo "R:UNTESTED" >&2 + echo "E:$test:`date`" >&2 + exit 0; +fi + +$PERL testsock.pl || { + echo "I:Network interface aliases not set up. Skipping test." >&2; + echo "R:UNTESTED" >&2; + echo "E:$test:`date`" >&2; + exit 0; +} + + +# Check for test-specific prerequisites. +test ! -f $test/prereq.sh || ( cd $test && sh prereq.sh "$@" ) +result=$? + +if [ $result -eq 0 ]; then + : prereqs ok +else + echo "I:Prerequisites for $test missing, skipping test." >&2 + [ $result -eq 255 ] && echo "R:SKIPPED" || echo "R:UNTESTED" + echo "E:$test:`date`" >&2 + exit 0 +fi + +# Check for PKCS#11 support +if + test ! -f $test/usepkcs11 || sh cleanpkcs11.sh +then + : pkcs11 ok +else + echo "I:Need PKCS#11 for $test, skipping test." >&2 + echo "R:PKCS11ONLY" >&2 + echo "E:$test:`date`" >&2 + exit 0 +fi + +# Set up any dynamically generated test data +if test -f $test/setup.sh +then + ( cd $test && sh setup.sh "$@" ) +fi + +# Start name servers running +$PERL start.pl $test || exit 1 + +# Run the tests +( cd $test ; sh tests.sh ) + +status=$? + +if $stopservers +then + : +else + exit $status +fi + +# Shutdown +$PERL stop.pl $test + +status=`expr $status + $?` + +if [ $status != 0 ]; then + echo "R:FAIL" + # Don't clean up - we need the evidence. + find . -name core -exec chmod 0644 '{}' \; +else + echo "R:PASS" + + # Clean up. + if test -f $test/clean.sh + then + ( cd $test && sh clean.sh "$@" ) + fi +fi + +echo "E:$test:`date`" + +exit $status diff --git a/tests/system/runall.sh b/tests/system/runall.sh new file mode 100644 index 0000000000..bf38cd7454 --- /dev/null +++ b/tests/system/runall.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# $Id: runall.sh,v 1.10 2010/03/04 23:50:34 tbox Exp $ + +# +# Run all the system tests. +# + +SYSTEMTESTTOP=. +. $SYSTEMTESTTOP/conf.sh + +status=0 + +for d in $SUBDIRS +do + sh run.sh $d || status=1 +done + +$PERL testsock.pl || { + cat <&2 +I: +I:NOTE: Many of the tests were skipped because they require that +I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured +I: as alias addresses on the loopback interface. Please run +I: "bin/tests/system/ifconfig.sh up" as root to configure them +I: and rerun the tests. +EOF + exit 0; +} + +exit $status From 70d71681b83dd3e0cb125e3f3e176d04e7461aae Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Wed, 2 Mar 2011 10:03:35 +0000 Subject: [PATCH 020/113] [trac499] Checkpoint commit --- src/lib/asiolink/tests/Makefile.am | 1 + .../tests/recursive_query_unittest_2.cc | 122 +++++++++++++----- 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index 13f63e676c..eba0fd90e0 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -26,6 +26,7 @@ run_unittests_SOURCES += io_socket_unittest.cc run_unittests_SOURCES += io_service_unittest.cc run_unittests_SOURCES += interval_timer_unittest.cc run_unittests_SOURCES += recursive_query_unittest.cc +run_unittests_SOURCES += recursive_query_unittest_2.cc run_unittests_SOURCES += tcp_endpoint_unittest.cc run_unittests_SOURCES += tcp_socket_unittest.cc run_unittests_SOURCES += udp_endpoint_unittest.cc diff --git a/src/lib/asiolink/tests/recursive_query_unittest_2.cc b/src/lib/asiolink/tests/recursive_query_unittest_2.cc index eb05db7d09..cde15d6144 100644 --- a/src/lib/asiolink/tests/recursive_query_unittest_2.cc +++ b/src/lib/asiolink/tests/recursive_query_unittest_2.cc @@ -32,6 +32,7 @@ #include #include +#include #include #include #include @@ -63,12 +64,16 @@ using namespace std; namespace asiolink { -const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1")); -const uint16_t TEST_PORT(5301); +const std::string TEST_ADDRESS = "127.0.0.1"; +const uint16_t TEST_PORT = 5301; +const uint16_t DNSSERVICE_PORT = 5302; + +const asio::ip::address TEST_HOST(asio::ip::address::from_string(TEST_ADDRESS)); /// \brief Test fixture for the asiolink::IOFetch. -class RecursiveQueryTest2 : public virtual ::testing::Test, public virtual IOFetch::Callback +class RecursiveQueryTest2 : public virtual ::testing::Test, + public virtual IOFetch::Callback { public: @@ -84,12 +89,15 @@ public: COMPLETE = 5 ///< Query is complete }; + // Common stuff IOService service_; ///< Service to run everything + DNSService dns_service_; ///< Resolver is part of "server" Question question_; ///< What to ask QueryStatus last_; ///< Last state QueryStatus expected_; ///< Expected next state OutputBufferPtr question_buffer_; ///< Question we expect to receive + // Data for TCP Server size_t tcp_cumulative_; ///< Cumulative TCP data received tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives size_t tcp_length_; ///< Expected length value @@ -108,6 +116,8 @@ public: /// \brief Constructor RecursiveQueryTest2() : service_(), + dns_service_(service_.get_io_service(), DNSSERVICE_PORT, true, false, + NULL, NULL, NULL), question_(Name("www.example.org"), RRClass::IN(), RRType::A()), last_(NONE), expected_(NONE), @@ -417,56 +427,104 @@ public: EXPECT_EQ(expected, length); // And that amount sent is as expected } - /// \brief Resolver Callback Completion +}; + +/// \brief Resolver Callback Object +/// +/// Holds the success and failure callback methods for the resolver +class ResolverCallback : public isc::resolve::resolverInterface::Callback { +public: + /// \brief Constructor + ResolverCallback(IOService& service) : service_(service) + {} + + /// \brief Destructor + virtual ~ResolverCallback() + {} + + /// \brief Resolver Callback Success /// - /// This is the callback's operator() method which is called when the - /// resolution of the query is complete. It checks that the data received - /// is the wire format of the data sent back by the server. + /// Called if the resolver detects that the call has succeeded. /// - /// \param result Result indicated by the callback - void operator()(IOFetch::Result result) { - - EXPECT_EQ(COMPLETE, expected_); - /* - EXPECT_EQ(expected_, result); // Check correct result returned - EXPECT_FALSE(run_); // Check it is run only once - run_ = true; // Note success - - // If the expected result for SUCCESS, then this should have been called - // when one of the "servers" in this class has sent back the TEST_DATA. - // Check the data is as expected/ - if (expected_ == IOFetch::SUCCESS) { - EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength()); - - const uint8_t* start = static_cast(result_buff_->getData()); - EXPECT_TRUE(equal(TEST_DATA, (TEST_DATA + sizeof(TEST_DATA) - 1), - start)); - } - - // ... and cause the run loop to exit. - * */ - service_.stop(); + /// \param response Answer to the question. + virtual void success(const isc::dns::MessagePtr response) { + service_.stop(); // Cause run() to exit. } + /// \brief Resolver Failure Completion + /// + /// Called if the resolver detects that the call has failed. + virtual void failure() { + FAIL() << "Resolver reported failure on completion"; + service_.stop(); // Cause run() to exit. + } + +private: + IOService& service_; ///< Service handling the run queue +}; + // Sets up the UDP and TCP "servers", then tries a resolution. TEST_F(RecursiveQueryTest2, Resolve) { - // Set the state of the // Set up the UDP server and issue the first read. udp_socket_.set_option(socket_base::reuse_address(true)); udp_socket_.bind(udp::endpoint(TEST_HOST, TEST_PORT)); udp_socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)), remote, - boost::bind(&IOFetchTest::udpReceiveHandler, this, &remote, &socket, + boost::bind(&RecursiveQuery2::udpReceiveHandler, this, &remote, &socket, _1, _2)); service_.get_io_service().post(udp_fetch_); service_.run(); + // Set up the TCP server and issue the accept. Acceptance will cause the + // read to be issued. + tcp::acceptor acceptor(service_.get_io_service(), + tcp::endpoint(tcp::v4(), TEST_PORT)); + acceptor.async_accept(socket, + boost::bind(&RecursiveQuery2::tcpAcceptHandler, this, _1)); + + // Set up the RecursiveQuery object. + std::vector > upstream; // Empty + std::vector > upstream_root; // Empty + RecursiveQuery query(dns_service_, upstream, upstream_root); + + std::pair test_server(TEST_ADDRESS, TEST_PORT); + query.setTestServer(test_server); + + /// Set up callback for the resolver + ResolverInterface::CallbackPtr + resolver_callback(new(ResolverCallback(service_))); + + + + socket.close(); EXPECT_TRUE(run_);; } + protocol_ = IOFetch::TCP; + expected_ = IOFetch::SUCCESS; + + // Socket into which the connection will be accepted + tcp::socket socket(service_.get_io_service()); + + // Acceptor object - called when the connection is made, the handler will + // initiate a read on the socket. + tcp::acceptor acceptor(service_.get_io_service(), + tcp::endpoint(tcp::v4(), TEST_PORT)); + acceptor.async_accept(socket, + boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); + + // Post the TCP fetch object to send the query and receive the response. + service_.get_io_service().post(tcp_fetch_); + + // ... and execute all the callbacks. This exits when the fetch completes. + service_.run(); + EXPECT_TRUE(run_); // Make sure the callback did execute + + socket.close(); + // Do the same tests for TCP transport From 6ba7a9cf0d9bcaa52ab17652ef1e6e3ad78094a0 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Wed, 2 Mar 2011 20:37:10 +0000 Subject: [PATCH 021/113] [trac499] Completed test of fallback to TCP Added recursive_query_unittest_2.cc which uses RecursiveQuery to send a series of queries to a "server" on the lookback interface and tests that a failover is made to TCP when a response is marked as truncated. --- src/lib/asiolink/recursive_query.cc | 4 +- src/lib/asiolink/tcp_socket.h | 4 +- .../tests/recursive_query_unittest_2.cc | 410 ++++++++++-------- 3 files changed, 238 insertions(+), 180 deletions(-) diff --git a/src/lib/asiolink/recursive_query.cc b/src/lib/asiolink/recursive_query.cc index 09c15e615b..406b176621 100644 --- a/src/lib/asiolink/recursive_query.cc +++ b/src/lib/asiolink/recursive_query.cc @@ -237,7 +237,7 @@ private: isc::resolve::ResponseClassifier::Category category = isc::resolve::ResponseClassifier::classify( - question_, incoming, cname_target, cname_count_, true); + question_, incoming, cname_target, cname_count_); bool found_ns_address = false; @@ -331,7 +331,7 @@ private: if (protocol_ == IOFetch::UDP) { dlog("Response truncated, re-querying over TCP"); send(IOFetch::TCP); - break; + return false; } // Was a TCP query so we have received a packet over TCP with the TC // bit set: drop through to common error processing. diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index 492671e112..766df9934e 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -306,7 +306,7 @@ TCPSocket::asyncReceive(void* data, size_t length, size_t offset, // return type. assert(endpoint->getProtocol() == IPPROTO_TCP); TCPEndpoint* tcp_endpoint = static_cast(endpoint); - + // Write the endpoint details from the comminications link. Ideally // we should make IOEndpoint assignable, but this runs in to all sorts // of problems concerning the management of the underlying Boost @@ -325,7 +325,7 @@ TCPSocket::asyncReceive(void* data, size_t length, size_t offset, // ... and kick off the read. socket_.async_receive(asio::buffer(buffer_start, length - offset), callback); - + } else { isc_throw(SocketNotOpen, "attempt to receive from a TCP socket that is not open"); diff --git a/src/lib/asiolink/tests/recursive_query_unittest_2.cc b/src/lib/asiolink/tests/recursive_query_unittest_2.cc index cde15d6144..38ff645e85 100644 --- a/src/lib/asiolink/tests/recursive_query_unittest_2.cc +++ b/src/lib/asiolink/tests/recursive_query_unittest_2.cc @@ -14,8 +14,9 @@ #include #include -#include +#include #include +#include #include #include @@ -30,6 +31,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -37,43 +42,51 @@ #include #include #include +#include +#include using namespace asio; -using namespace isc::dns; using namespace asio::ip; +using namespace isc::dns; +using namespace isc::dns::rdata; +using namespace isc::resolve; using namespace std; /// RecursiveQuery Test - 2 /// /// The second part of the RecursiveQuery unit tests, this attempts to get the /// RecursiveQuery object to follow a set of referrals for "www.example.org" to -/// and to invoke TCP fallback on one of the queries. In particular, we -/// expect that the test will do the following in an attempt to resolve +/// and to invoke TCP fallback on one of the queries. In particular, we expect +/// that the test will do the following in an attempt to resolve /// www.example.org: /// -/// - Send a question over UDP to the root servers - get referral to "org". +/// - Send question over UDP to "root" - get referral to "org". /// - Send question over UDP to "org" - get referral to "example.org" with TC bit set. /// - Send question over TCP to "org" - get referral to "example.org". /// - Send question over UDP to "example.org" - get response for www.example.org. /// -/// The order of queries is partly to test that after there is a fallover to TCP, -/// queries revert to UDP. +/// (The order of queries is set in this way in order to also test that after a +/// failover to TCP, queries revert to UDP). /// /// By using the "test_server_" element of RecursiveQuery, all queries are -/// directed to one or other of the "servers" in the RecursiveQueryTest2 class. +/// directed to one or other of the "servers" in the RecursiveQueryTest2 class, +/// regardless of the glue returned in referrals. namespace asiolink { -const std::string TEST_ADDRESS = "127.0.0.1"; -const uint16_t TEST_PORT = 5301; -const uint16_t DNSSERVICE_PORT = 5302; +const std::string TEST_ADDRESS = "127.0.0.1"; ///< Servers are on this address +const uint16_t TEST_PORT = 5301; ///< ... and this port +const size_t BUFFER_SIZE = 1024; ///< For all buffers +const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Answer to question -const asio::ip::address TEST_HOST(asio::ip::address::from_string(TEST_ADDRESS)); +// As the test is fairly long and complex, debugging "print" statements have +// been left in although they are disabled. Set the following to "true" to +// enable them. +const bool DEBUG_PRINT = false; -/// \brief Test fixture for the asiolink::IOFetch. -class RecursiveQueryTest2 : public virtual ::testing::Test, - public virtual IOFetch::Callback +/// \brief Test fixture for the RecursiveQuery Test +class RecursiveQueryTest2 : public virtual ::testing::Test { public: @@ -90,10 +103,11 @@ public: }; // Common stuff + bool debug_; ///< Set true for debug print IOService service_; ///< Service to run everything DNSService dns_service_; ///< Resolver is part of "server" - Question question_; ///< What to ask - QueryStatus last_; ///< Last state + QuestionPtr question_; ///< What to ask + QueryStatus last_; ///< What was the last state QueryStatus expected_; ///< Expected next state OutputBufferPtr question_buffer_; ///< Question we expect to receive @@ -101,43 +115,42 @@ public: size_t tcp_cumulative_; ///< Cumulative TCP data received tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives size_t tcp_length_; ///< Expected length value - uint8_t tcp_receive_buffer_[512]; ///< Receive buffer for TCP I/O + uint8_t tcp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for TCP I/O OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O tcp::socket tcp_socket_; ///< Socket used by TCP server /// Data for UDP - udp::endpoint udp_endpoint_; ///< Endpoint for UDP receives + udp::endpoint udp_remote_; ///< Endpoint for UDP receives size_t udp_length_; ///< Expected length value - uint8_t udp_receive_buffer_[512]; ///< Receive buffer for UDP I/O + uint8_t udp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for UDP I/O OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O udp::socket udp_socket_; ///< Socket used by UDP server /// \brief Constructor RecursiveQueryTest2() : + debug_(DEBUG_PRINT), service_(), - dns_service_(service_.get_io_service(), DNSSERVICE_PORT, true, false, - NULL, NULL, NULL), - question_(Name("www.example.org"), RRClass::IN(), RRType::A()), + dns_service_(service_, NULL, NULL, NULL), + question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())), last_(NONE), expected_(NONE), - question_buffer_(), + question_buffer_(new OutputBuffer(BUFFER_SIZE)), tcp_cumulative_(0), - tcp_endpoint_(TEST_HOST, TEST_PORT), + tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS), TEST_PORT), tcp_length_(0), tcp_receive_buffer_(), - tcp_send_buffer_(), + tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)), tcp_socket_(service_.get_io_service()), - udp_endpoint_(), + udp_remote_(), udp_length_(0), udp_receive_buffer_(), - udp_send_buffer_(), + udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)), udp_socket_(service_.get_io_service(), udp::v4()) { } - /// \brief Set Common Message Bits /// /// Sets up the common bits of a response message returned by the handlers. @@ -150,7 +163,7 @@ public: msg.setOpcode(Opcode::QUERY()); msg.setHeaderFlag(Message::HEADERFLAG_AA); msg.setRcode(Rcode::NOERROR()); - msg.addQuestion(question_); + msg.addQuestion(*question_); } /// \brief Set Referral to "org" @@ -160,21 +173,24 @@ public: /// /// \param msg Message to update with referral information. void setReferralOrg(isc::dns::Message& msg) { + if (debug_) { + cout << "setReferralOrg(): creating referral to .org nameservers" << endl; + } // Do a referral to org. We'll define all NS records as "in-zone" // nameservers (and so supply glue) to avoid the possibility of // the resolver doing another lookup. - RRSetPtr org_ns(new RRSet(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300))); - org_ns->addRdata(NS("ns1.org.")); - org_ns->addRdata(NS("ns2.org.")); + RRsetPtr org_ns(new RRset(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300))); + org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.org.")); + org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.org.")); msg.addRRset(Message::SECTION_AUTHORITY, org_ns); - RRsetPtr org_ns1(new RRSet(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300))); - org_ns1->addRdata(A("192.0.2.1")); + RRsetPtr org_ns1(new RRset(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.1")); msg.addRRset(Message::SECTION_ADDITIONAL, org_ns1); - RRsetPtr org_ns1(new RRSet(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300))); - org_ns2->addRdata(A("192.0.2.2")); + RRsetPtr org_ns2(new RRset(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.2")); msg.addRRset(Message::SECTION_ADDITIONAL, org_ns2); } @@ -185,21 +201,24 @@ public: /// /// \param msg Message to update with referral information. void setReferralExampleOrg(isc::dns::Message& msg) { + if (debug_) { + cout << "setReferralExampleOrg(): creating referral to example.org nameservers" << endl; + } // Do a referral to example.org. As before, we'll define all NS // records as "in-zone" nameservers (and so supply glue) to avoid // the possibility of the resolver doing another lookup. - RRSetPtr example_org_ns(new RRSet(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300))); - example_org_ns->addRdata(NS("ns1.example.org.")); - example_org_ns->addRdata(NS("ns2.example.org.")); + RRsetPtr example_org_ns(new RRset(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300))); + example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.example.org.")); + example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.example.org.")); msg.addRRset(Message::SECTION_AUTHORITY, example_org_ns); - RRsetPtr example_org_ns1(new RRSet(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); - example_org_ns1->addRdata(A("192.0.2.11")); + RRsetPtr example_org_ns1(new RRset(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + example_org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.11")); msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1); - RRsetPtr org_ns1(new RRSet(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); - example_org_ns2->addRdata(A("192.0.2.12")); + RRsetPtr example_org_ns2(new RRset(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + example_org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.21")); msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns2); } @@ -210,11 +229,14 @@ public: /// /// \param msg Message to update with referral information. void setAnswerWwwExampleOrg(isc::dns::Message& msg) { + if (debug_) { + cout << "setAnswerWwwExampleOrg(): creating answer for www.example.org" << endl; + } // Give a response for www.example.org. - RRsetPtr www_example_org_a(new RRSet(Name("www.example.org."), RRClass::IN(), RRType::NS(), RRTTL(300))); - www_example_org_a->addRdata(A("192.0.2.21")); - msg.addRRset(Message::SECTION_ANSWER, example_org_ns); + RRsetPtr www_example_org_a(new RRset(Name("www.example.org."), RRClass::IN(), RRType::A(), RRTTL(300))); + www_example_org_a->addRdata(createRdata(RRType::A(), RRClass::IN(), WWW_EXAMPLE_ORG)); + msg.addRRset(Message::SECTION_ANSWER, www_example_org_a); // ... and add the Authority and Additional sections. (These are the // same as in the referral to example.org from the .org nameserver.) @@ -227,12 +249,15 @@ public: /// Object. It formats an answer and sends it, with the UdpSendHandler /// method being specified as the completion handler. /// - /// \param remote Endpoint to which to send the answer - /// \param socket Socket to use to send the answer /// \param ec ASIO error code, completion code of asynchronous I/O issued /// by the "server" to receive data. /// \param length Amount of data received. void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "udpReceiveHandler(): error = " << ec.value() << + ", length = " << length << ", last state = " << last_ << + ", expected state = " << expected_ << endl; + } // Expected state should be one greater than the last state. EXPECT_EQ(static_cast(expected_), static_cast(last_) + 1); @@ -244,11 +269,12 @@ public: uint16_t qid = readUint16(udp_receive_buffer_); udp_receive_buffer_[0] = udp_receive_buffer_[1] = 0; - // Check that length of the received data and the expected data are - // identical, then check that the data is identical as well. - EXPECT_EQ(question_buff_->getLength(), length); - EXPECT_TRUE(equal(udp_receive_buffer_, (udp_receive_buffer_ + length - 1), - static_cast(question_buff_->getData()))); + // Check that question we received is what was expected. + Message received_message(Message::PARSE); + InputBuffer received_buffer(udp_receive_buffer_, length); + received_message.fromWire(received_buffer); + Question received_question = **(received_message.beginQuestion()); + EXPECT_TRUE(received_question == *question_); // The message returned depends on what state we are in. Set up // common stuff first: bits not mentioned are set to 0. @@ -268,6 +294,9 @@ public: // Return a referral to example.org. We explicitly set the TC bit to // force a repeat query to the .org nameservers over TCP. setReferralExampleOrg(msg); + if (debug_) { + cout << "udpReceiveHandler(): setting TC bit" << endl; + } msg.setHeaderFlag(Message::HEADERFLAG_TC); expected_ = TCP_ORG; break; @@ -283,17 +312,19 @@ public: } // Convert to wire format + udp_send_buffer_->clear(); MessageRenderer renderer(*udp_send_buffer_); msg.toWire(renderer); // Return a message back to the IOFetch object. - udp_socket_.send_to(asio::buffer(udp_send_buffer_.getData(), - udp_send_buffer_.getLength()), - boost::bind(&RecursiveQueryTest2::UdpSendHandler, - this, _1, _2)); + udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(), + udp_send_buffer_->getLength()), + udp_remote_, + boost::bind(&RecursiveQueryTest2::udpSendHandler, + this, _1, _2)); // Set the expected length for the send handler. - udp_length_ = udp_send_buffer_.getLength(); + udp_length_ = udp_send_buffer_->getLength(); } /// \brief UDP Send Handler @@ -302,6 +333,10 @@ public: /// being sent to the RecursiveQuery) has completed, this re-issues /// a read call. void udpSendHandler(error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "udpSendHandler(): error = " << ec.value() << + ", length = " << length << endl; + } // Check send was OK EXPECT_EQ(0, ec.value()); @@ -310,8 +345,8 @@ public: // Reissue the receive. udp_socket_.async_receive_from( asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)), - udp_endpoint_ - boost::bind(&recursiveQuery2::udpReceiveHandler, this, _1, _2)); + udp_remote_, + boost::bind(&RecursiveQueryTest2::udpReceiveHandler, this, _1, _2)); } /// \brief Completion Handler for Accepting TCP Data @@ -321,8 +356,12 @@ public: /// /// \param socket Socket on which data will be received /// \param ec Boost error code, value should be zero. - void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) - { + void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "tcpAcceptHandler(): error = " << ec.value() << + ", length = " << length << endl; + } + // Expect that the accept completed without a problem. EXPECT_EQ(0, ec.value()); @@ -331,7 +370,7 @@ public: tcp_cumulative_ = 0; tcp_socket_.async_receive( asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)), - boost::bind(&recursiveQuery2::tcpReceiveHandler, this, _1, _2)); + boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2)); } /// \brief Completion Handler for Receiving TCP Data @@ -343,8 +382,13 @@ public: /// \param ec ASIO error code, completion code of asynchronous I/O issued /// by the "server" to receive data. /// \param length Amount of data received. - void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) - { + void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "tcpReceiveHandler(): error = " << ec.value() << + ", length = " << length << + ", cumulative = " << tcp_cumulative_ << endl; + } + // Expect that the receive completed without a problem. EXPECT_EQ(0, ec.value()); @@ -358,12 +402,16 @@ public: } if (!complete) { + if (debug_) { + cout << "tcpReceiveHandler(): read not complete, " << + "issuing another read" << endl; + } // Not complete yet, issue another read. tcp_socket_.async_receive( asio::buffer(tcp_receive_buffer_ + tcp_cumulative_, sizeof(tcp_receive_buffer_) - tcp_cumulative_), - boost::bind(&recursiveQuery2::tcpReceiveHandler, this, _1, _2)); + boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2)); return; } @@ -372,42 +420,47 @@ public: EXPECT_EQ(static_cast(expected_), static_cast(last_) + 1); last_ = expected_; - // Check that length of the received data and the expected data are - // identical (taking into account the two-byte count), then check that - // the data is identical as well (after zeroing the QID). - EXPECT_EQ(question_buff_->getLength() + 2, tcp_cumulative_); - uint16_t qid = readUint16(&udp_receive_buffer_[2]); - tcp_receive_buffer_[2] = tcp_receive_buffer_[3] = 0; - EXPECT_TRUE(equal((tcp_receive_buffer_ + 2), - (tcp_receive_buffer_ + tcp_cumulative_), - static_cast(question_buff_->getData()))); - + // Check that question we received is what was expected. Note that we + // have to ignore the two-byte header in order to parse the message. + Message received_message(Message::PARSE); + InputBuffer received_buffer(tcp_receive_buffer_ + 2, tcp_cumulative_ - 2); + received_message.fromWire(received_buffer); + Question received_question = **(received_message.beginQuestion()); + EXPECT_TRUE(received_question == *question_); // Return a message back. This is a referral to example.org, which - // should result in another query over UDP. + // should result in another query over UDP. Note the setting of the + // QID in the returned message with what was in the received message. Message msg(Message::RENDER); - setCommonMessage(msg, qid); + setCommonMessage(msg, readUint16(tcp_receive_buffer_)); setReferralExampleOrg(msg); // Convert to wire format + tcp_send_buffer_->clear(); MessageRenderer renderer(*tcp_send_buffer_); msg.toWire(renderer); - + // Expected next state (when checked) is the UDP query to example.org. + // Also, take this opportunity to clear the accumulated read count in + // readiness for the next read. (If any - at present, there is only + // one read in the test,, although extensions to this test suite could + // change that.) expected_ = UDP_EXAMPLE_ORG; - + // We'll write the message in two parts, the count and the message - // itself. When specifying the send handler, the expected size of the - // data written is passed as the first parameter. + // itself. This saves having to prepend the count onto the start of a + // buffer. When specifying the send handler, the expected size of the + // data written is passed as the first parameter so that the handler + // can check it. uint8_t count[2]; writeUint16(tcp_send_buffer_->getLength(), count); - socket->async_send(asio::buffer(count, 2), - boost::bind(&IOFetchTest::tcpSendHandler, this, - 2, _1, _2)); - socket->async_send(asio::buffer(tcp_send_buffer_->getData(), - tcp_send_buffer_->getLength()), - boost::bind(&RecursiveQuery2::tcpSendHandler, this, - sizeof(TEST_DATA), _1, _2)); + tcp_socket_.async_send(asio::buffer(count, 2), + boost::bind(&RecursiveQueryTest2::tcpSendHandler, this, + 2, _1, _2)); + tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(), + tcp_send_buffer_->getLength()), + boost::bind(&RecursiveQueryTest2::tcpSendHandler, this, + tcp_send_buffer_->getLength(), _1, _2)); } /// \brief Completion Handler for Sending TCP data @@ -417,14 +470,19 @@ public: /// be asynchronous because control needs to return to the caller in order /// for the IOService "run()" method to be called to run the handlers.) /// - /// \param expected Number of bytes that were expected to have been sent. + /// \param expected_length Number of bytes that were expected to have been sent. /// \param ec Boost error code, value should be zero. /// \param length Number of bytes sent. - void tcpSendHandler(size_t expected = 0, error_code ec = error_code(), + void tcpSendHandler(size_t expected_length = 0, error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "tcpSendHandler(): error = " << ec.value() << + ", length = " << length << + ", (expected length = " << expected_length << ")" << endl; + } EXPECT_EQ(0, ec.value()); // Expect no error - EXPECT_EQ(expected, length); // And that amount sent is as expected + EXPECT_EQ(expected_length, length); // And that amount sent is as expected } }; @@ -432,10 +490,11 @@ public: /// \brief Resolver Callback Object /// /// Holds the success and failure callback methods for the resolver -class ResolverCallback : public isc::resolve::resolverInterface::Callback { +class ResolverCallback : public isc::resolve::ResolverInterface::Callback { public: /// \brief Constructor - ResolverCallback(IOService& service) : service_(service) + ResolverCallback(IOService& service) : + service_(service), run_(false), status_(false), debug_(DEBUG_PRINT) {} /// \brief Destructor @@ -448,120 +507,119 @@ public: /// /// \param response Answer to the question. virtual void success(const isc::dns::MessagePtr response) { + if (debug_) { + cout << "ResolverCallback::success(): answer received" << endl; + } + + // There should be one RR each in the question and answer sections, and + // two RRs in each of the the authority and additional sections. + EXPECT_EQ(1, response->getRRCount(Message::SECTION_QUESTION)); + EXPECT_EQ(1, response->getRRCount(Message::SECTION_ANSWER)); + EXPECT_EQ(2, response->getRRCount(Message::SECTION_AUTHORITY)); + EXPECT_EQ(2, response->getRRCount(Message::SECTION_ADDITIONAL)); + + // Check the answer - that the RRset is there... + EXPECT_TRUE(response->hasRRset(Message::SECTION_ANSWER, + RRsetPtr(new RRset(Name("www.example.org."), + RRClass::IN(), + RRType::A(), + RRTTL(300))))); + const RRsetIterator rrset_i = response->beginSection(Message::SECTION_ANSWER); + + // ... get iterator into the Rdata of this RRset and point to first + // element... + const RdataIteratorPtr rdata_i = (*rrset_i)->getRdataIterator(); + rdata_i->first(); + + // ... and check it is what we expect. + EXPECT_EQ(string(WWW_EXAMPLE_ORG), rdata_i->getCurrent().toText()); + + // Flag completion + run_ = true; + status_ = true; + service_.stop(); // Cause run() to exit. } /// \brief Resolver Failure Completion /// - /// Called if the resolver detects that the call has failed. + /// Called if the resolver detects that the resolution has failed. virtual void failure() { - FAIL() << "Resolver reported failure on completion"; + if (debug_) { + cout << "ResolverCallback::success(): resolution failure" << endl; + } + FAIL() << "Resolver reported completion failure"; + + // Flag completion + run_ = true; + status_ = false; + service_.stop(); // Cause run() to exit. } + /// \brief Return status of "run" flag + bool getRun() const { + return (run_); + } + + /// \brief Return "status" flag + bool getStatus() const { + return (status_); + } + private: IOService& service_; ///< Service handling the run queue + bool run_; ///< Set true when completion handler run + bool status_; ///< Set true for success, false on error + bool debug_; ///< Debug flag }; // Sets up the UDP and TCP "servers", then tries a resolution. TEST_F(RecursiveQueryTest2, Resolve) { - // Set up the UDP server and issue the first read. + // Set up the UDP server and issue the first read. The endpoint from which + // the query is sent is put in udp_endpoint_ when the read completes, which + // is referenced in the callback as the place to which the response is sent. udp_socket_.set_option(socket_base::reuse_address(true)); - udp_socket_.bind(udp::endpoint(TEST_HOST, TEST_PORT)); - udp_socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)), - remote, - boost::bind(&RecursiveQuery2::udpReceiveHandler, this, &remote, &socket, - _1, _2)); - service_.get_io_service().post(udp_fetch_); - service_.run(); + udp_socket_.bind(udp::endpoint(address::from_string(TEST_ADDRESS), TEST_PORT)); + udp_socket_.async_receive_from(asio::buffer(udp_receive_buffer_, + sizeof(udp_receive_buffer_)), + udp_remote_, + boost::bind(&RecursiveQueryTest2::udpReceiveHandler, + this, _1, _2)); // Set up the TCP server and issue the accept. Acceptance will cause the // read to be issued. tcp::acceptor acceptor(service_.get_io_service(), tcp::endpoint(tcp::v4(), TEST_PORT)); - acceptor.async_accept(socket, - boost::bind(&RecursiveQuery2::tcpAcceptHandler, this, _1)); + acceptor.async_accept(tcp_socket_, + boost::bind(&RecursiveQueryTest2::tcpAcceptHandler, + this, _1, 0)); // Set up the RecursiveQuery object. std::vector > upstream; // Empty std::vector > upstream_root; // Empty RecursiveQuery query(dns_service_, upstream, upstream_root); + query.setTestServer(TEST_ADDRESS, TEST_PORT); - std::pair test_server(TEST_ADDRESS, TEST_PORT); - query.setTestServer(test_server); - - /// Set up callback for the resolver + // Set up callback for the tor eceive notification that the query has + // completed. ResolverInterface::CallbackPtr - resolver_callback(new(ResolverCallback(service_))); + resolver_callback(new ResolverCallback(service_)); - - - - socket.close(); - - EXPECT_TRUE(run_);; -} - protocol_ = IOFetch::TCP; - expected_ = IOFetch::SUCCESS; - - // Socket into which the connection will be accepted - tcp::socket socket(service_.get_io_service()); - - // Acceptor object - called when the connection is made, the handler will - // initiate a read on the socket. - tcp::acceptor acceptor(service_.get_io_service(), - tcp::endpoint(tcp::v4(), TEST_PORT)); - acceptor.async_accept(socket, - boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); - - // Post the TCP fetch object to send the query and receive the response. - service_.get_io_service().post(tcp_fetch_); - - // ... and execute all the callbacks. This exits when the fetch completes. + // Kick off the resolution process. We expect the first question to go to + // "root". + expected_ = UDP_ROOT; + query.resolve(question_, resolver_callback); service_.run(); - EXPECT_TRUE(run_); // Make sure the callback did execute - socket.close(); - - -// Do the same tests for TCP transport - -TEST_F(IOFetchTest, TcpStop) { - stopTest(IOFetch::TCP, tcp_fetch_); -} - -TEST_F(IOFetchTest, TcpPrematureStop) { - prematureStopTest(IOFetch::TCP, tcp_fetch_); -} - -TEST_F(IOFetchTest, TcpTimeout) { - timeoutTest(IOFetch::TCP, tcp_fetch_); -} - -TEST_F(IOFetchTest, TcpSendReceive) { - protocol_ = IOFetch::TCP; - expected_ = IOFetch::SUCCESS; - - // Socket into which the connection will be accepted - tcp::socket socket(service_.get_io_service()); - - // Acceptor object - called when the connection is made, the handler will - // initiate a read on the socket. - tcp::acceptor acceptor(service_.get_io_service(), - tcp::endpoint(tcp::v4(), TEST_PORT)); - acceptor.async_accept(socket, - boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); - - // Post the TCP fetch object to send the query and receive the response. - service_.get_io_service().post(tcp_fetch_); - - // ... and execute all the callbacks. This exits when the fetch completes. - service_.run(); - EXPECT_TRUE(run_); // Make sure the callback did execute - - socket.close(); + // Check what ran. (We have to cast the callback to ResolverCallback as we + // lost the information on the derived class when we used a + // ResolverInterface::CallbackPtr to store a pointer to it.) + ResolverCallback* rc = static_cast(resolver_callback.get()); + EXPECT_TRUE(rc->getRun()); + EXPECT_TRUE(rc->getStatus()); } } // namespace asiolink From 1f682b8172e368d7609aa2f066b778989a7fdf7e Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Thu, 3 Mar 2011 12:14:07 +0000 Subject: [PATCH 022/113] [trac499] Final tidy-up before review --- src/lib/asiolink/tests/io_fetch_unittest.cc | 108 +++++++++++++----- .../tests/recursive_query_unittest_2.cc | 81 +++++++------ 2 files changed, 129 insertions(+), 60 deletions(-) diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index 2869efd343..fc845e1317 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -19,7 +19,7 @@ #include #include - +#include #include @@ -47,7 +47,8 @@ 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 response from server to client"; +const char TEST_DATA[] = "Test response from server to client (longer than 30 bytes)"; +const int SEND_INTERVAL = 500; // Interval in ms between TCP sends /// \brief Test fixture for the asiolink::IOFetch. class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback @@ -63,11 +64,15 @@ public: IOFetch tcp_fetch_; ///< For TCP query test IOFetch::Protocol protocol_; ///< Protocol being tested size_t cumulative_; ///< Cumulative data received by "server". + deadline_timer timer_; ///< Timer to measure timeouts // The next member is the buffer in which the "server" (implemented by the // response handler methods in this class) receives the question sent by the // fetch object. - uint8_t server_buff_[512]; ///< Server buffer + uint8_t receive_buffer_[512]; ///< Server receive buffer + uint8_t send_buffer_[512]; ///< Server send buffer + uint16_t send_size_; ///< Amount of data to sent + uint16_t send_cumulative_; ///< Data sent so far /// \brief Constructor IOFetchTest() : @@ -80,9 +85,15 @@ public: udp_fetch_(IOFetch::UDP, service_, question_, IOAddress(TEST_HOST), TEST_PORT, result_buff_, this, 100), tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST), - TEST_PORT, result_buff_, this, 1000), + TEST_PORT, result_buff_, this, (4 * SEND_INTERVAL)), + // Timeout interval chosen to ensure no timeout protocol_(IOFetch::TCP), // for initialization - will be changed - cumulative_(0) + cumulative_(0), + timer_(service_.get_io_service()), + receive_buffer_(), + send_buffer_(), + send_size_(0), + send_cumulative_(0) { // Construct the data buffer for question we expect to receive. Message msg(Message::RENDER); @@ -112,12 +123,12 @@ public: // The QID in the incoming data is random so set it to 0 for the // data comparison check. (It is set to 0 in the buffer containing // the expected data.) - server_buff_[0] = server_buff_[1] = 0; + receive_buffer_[0] = receive_buffer_[1] = 0; // Check that length of the received data and the expected data are // identical, then check that the data is identical as well. EXPECT_EQ(msgbuf_->getLength(), length); - EXPECT_TRUE(equal(server_buff_, (server_buff_ + length - 1), + EXPECT_TRUE(equal(receive_buffer_, (receive_buffer_ + length - 1), static_cast(msgbuf_->getData()))); // Return a message back to the IOFetch object. @@ -138,7 +149,7 @@ public: // Initiate a read on the socket. cumulative_ = 0; - socket->async_receive(asio::buffer(server_buff_, sizeof(server_buff_)), + socket->async_receive(asio::buffer(receive_buffer_, sizeof(receive_buffer_)), boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2)); } @@ -163,13 +174,13 @@ public: cumulative_ += length; bool complete = false; if (cumulative_ > 2) { - uint16_t dns_length = readUint16(server_buff_); + uint16_t dns_length = readUint16(receive_buffer_); complete = ((dns_length + 2) == cumulative_); } if (!complete) { - socket->async_receive(asio::buffer((server_buff_ + cumulative_), - (sizeof(server_buff_) - cumulative_)), + socket->async_receive(asio::buffer((receive_buffer_ + cumulative_), + (sizeof(receive_buffer_) - cumulative_)), boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2)); return; } @@ -180,24 +191,44 @@ public: // field the QID in the received buffer is in the third and fourth // bytes. EXPECT_EQ(msgbuf_->getLength() + 2, cumulative_); - server_buff_[2] = server_buff_[3] = 0; - EXPECT_TRUE(equal((server_buff_ + 2), (server_buff_ + cumulative_ - 2), + receive_buffer_[2] = receive_buffer_[3] = 0; + EXPECT_TRUE(equal((receive_buffer_ + 2), (receive_buffer_ + cumulative_ - 2), static_cast(msgbuf_->getData()))); // ... and return a message back. This has to be preceded by a two-byte - // count field. It's simpler to do this as two writes - it shouldn't - // make any difference to the IOFetch object. - // - // When specifying the callback handler, the expected size of the - // data written is passed as the first parameter. - uint8_t count[2]; - writeUint16(sizeof(TEST_DATA), count); - socket->async_send(asio::buffer(count, 2), + // count field. Construct the message. + assert(sizeof(send_buffer_) > (sizeof(TEST_DATA) + 2)); + writeUint16(sizeof(TEST_DATA), send_buffer_); + copy(TEST_DATA, TEST_DATA + sizeof(TEST_DATA) - 1, send_buffer_ + 2); + send_size_ = sizeof(TEST_DATA) + 2; + + // Send the data. This is done in multiple writes with a delay between + // each to check that the reassembly of TCP packets from fragments works. + send_cumulative_ = 0; + tcpSendData(socket); + } + + /// \brief Sent Data Over TCP + /// + /// Send the TCP data back to the IOFetch object. The data is sent in + /// three chunks - two of 16 bytes and the remainder, with a 250ms gap + /// between each. + /// + /// \param socket Socket over which send should take place + void tcpSendData(tcp::socket* socket) { + // Decide what to send based on the cumulative count + uint8_t* send_ptr = &send_buffer_[send_cumulative_]; + // Pointer to data to send + size_t amount = 16; // Amount of data to send + if (send_cumulative_ > 30) { + amount = send_size_ - send_cumulative_; + } + + // ... and send it. The amount sent is also passed as the first argument + // of the send callback, as a check. + socket->async_send(asio::buffer(send_ptr, amount), boost::bind(&IOFetchTest::tcpSendHandler, this, - 2, _1, _2)); - socket->async_send(asio::buffer(TEST_DATA, sizeof(TEST_DATA)), - boost::bind(&IOFetchTest::tcpSendHandler, this, - sizeof(TEST_DATA), _1, _2)); + amount, socket, _1, _2)); } /// \brief Completion Handler for Sending TCP data @@ -207,14 +238,35 @@ public: /// be asynchronous because control needs to return to the caller in order /// for the IOService "run()" method to be called to run the handlers.) /// + /// If not all the data has been sent, a short delay is instigated (during + /// which control returns to the IOService). This should force the queued + /// data to actually be sent and the IOFetch receive handler to be triggered. + /// In this way, the ability of IOFetch to handle fragmented TCP packets + /// should be checked. + /// /// \param expected Number of bytes that were expected to have been sent. + /// \param socket Socket over which the send took place. Only used to + /// pass back to the send method. /// \param ec Boost error code, value should be zero. /// \param length Number of bytes sent. - void tcpSendHandler(size_t expected = 0, error_code ec = error_code(), - size_t length = 0) + void tcpSendHandler(size_t expected, tcp::socket* socket, + error_code ec = error_code(), size_t length = 0) { EXPECT_EQ(0, ec.value()); // Expect no error EXPECT_EQ(expected, length); // And that amount sent is as expected + + // Do we need to send more? + send_cumulative_ += length; + if (send_cumulative_ < send_size_) { + + // Yes - set up a timer: the callback handler for the timer is + // tcpSendData, which will then send the next chunk. We pass the + // socket over which data should be sent as an argument to that + // function. + timer_.expires_from_now(boost::posix_time::milliseconds(SEND_INTERVAL)); + timer_.async_wait(boost::bind(&IOFetchTest::tcpSendData, this, + socket)); + } } /// \brief Fetch completion callback @@ -343,7 +395,7 @@ TEST_F(IOFetchTest, UdpSendReceive) { socket.bind(udp::endpoint(TEST_HOST, TEST_PORT)); udp::endpoint remote; - socket.async_receive_from(asio::buffer(server_buff_, sizeof(server_buff_)), + socket.async_receive_from(asio::buffer(receive_buffer_, sizeof(receive_buffer_)), remote, boost::bind(&IOFetchTest::udpReceiveHandler, this, &remote, &socket, _1, _2)); diff --git a/src/lib/asiolink/tests/recursive_query_unittest_2.cc b/src/lib/asiolink/tests/recursive_query_unittest_2.cc index 38ff645e85..ce51bbf88e 100644 --- a/src/lib/asiolink/tests/recursive_query_unittest_2.cc +++ b/src/lib/asiolink/tests/recursive_query_unittest_2.cc @@ -77,12 +77,11 @@ namespace asiolink { const std::string TEST_ADDRESS = "127.0.0.1"; ///< Servers are on this address const uint16_t TEST_PORT = 5301; ///< ... and this port const size_t BUFFER_SIZE = 1024; ///< For all buffers -const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Answer to question +const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Address of www.example.org // As the test is fairly long and complex, debugging "print" statements have // been left in although they are disabled. Set the following to "true" to // enable them. - const bool DEBUG_PRINT = false; /// \brief Test fixture for the RecursiveQuery Test @@ -126,7 +125,6 @@ public: OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O udp::socket udp_socket_; ///< Socket used by UDP server - /// \brief Constructor RecursiveQueryTest2() : debug_(DEBUG_PRINT), @@ -147,9 +145,7 @@ public: udp_receive_buffer_(), udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)), udp_socket_(service_.get_io_service(), udp::v4()) - { - - } + {} /// \brief Set Common Message Bits /// @@ -178,8 +174,9 @@ public: } // Do a referral to org. We'll define all NS records as "in-zone" - // nameservers (and so supply glue) to avoid the possibility of - // the resolver doing another lookup. + // nameservers (and supply glue) to avoid the possibility of the + // resolver starting another recursive query to resolve the address of + // a nameserver. RRsetPtr org_ns(new RRset(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300))); org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.org.")); org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.org.")); @@ -206,8 +203,9 @@ public: } // Do a referral to example.org. As before, we'll define all NS - // records as "in-zone" nameservers (and so supply glue) to avoid - // the possibility of the resolver doing another lookup. + // records as "in-zone" nameservers (and supply glue) to avoid the + // possibility of the resolver starting another recursive query to look + // up the address of the nameserver. RRsetPtr example_org_ns(new RRset(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300))); example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.example.org.")); example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.example.org.")); @@ -224,7 +222,7 @@ public: /// \brief Set Answer to "www.example.org" /// - /// Sets up the passed-in message (expected to be in "RENDER" mode to + /// Sets up the passed-in message (expected to be in "RENDER" mode) to /// indicate an authoritative answer to www.example.org. /// /// \param msg Message to update with referral information. @@ -245,9 +243,10 @@ public: /// \brief UDP Receive Handler /// - /// This is invoked when a message is received from the RecursiveQuery - /// Object. It formats an answer and sends it, with the UdpSendHandler - /// method being specified as the completion handler. + /// This is invoked when a message is received over UDP from the + /// RecursiveQuery object under test. It formats an answer and sends it + /// asynchronously, with the UdpSendHandler method being specified as the + /// completion handler. /// /// \param ec ASIO error code, completion code of asynchronous I/O issued /// by the "server" to receive data. @@ -270,11 +269,7 @@ public: udp_receive_buffer_[0] = udp_receive_buffer_[1] = 0; // Check that question we received is what was expected. - Message received_message(Message::PARSE); - InputBuffer received_buffer(udp_receive_buffer_, length); - received_message.fromWire(received_buffer); - Question received_question = **(received_message.beginQuestion()); - EXPECT_TRUE(received_question == *question_); + checkReceivedPacket(udp_receive_buffer_, length); // The message returned depends on what state we are in. Set up // common stuff first: bits not mentioned are set to 0. @@ -316,15 +311,14 @@ public: MessageRenderer renderer(*udp_send_buffer_); msg.toWire(renderer); - // Return a message back to the IOFetch object. + // Return a message back to the IOFetch object (after setting the + // expected length of data for the check in the send handler). + udp_length_ = udp_send_buffer_->getLength(); udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(), udp_send_buffer_->getLength()), udp_remote_, boost::bind(&RecursiveQueryTest2::udpSendHandler, this, _1, _2)); - - // Set the expected length for the send handler. - udp_length_ = udp_send_buffer_->getLength(); } /// \brief UDP Send Handler @@ -332,6 +326,9 @@ public: /// Called when a send operation of the UDP server (i.e. a response /// being sent to the RecursiveQuery) has completed, this re-issues /// a read call. + /// + /// \param ec Completion error code of the send. + /// \param length Actual number of bytes sent. void udpSendHandler(error_code ec = error_code(), size_t length = 0) { if (debug_) { cout << "udpSendHandler(): error = " << ec.value() << @@ -342,7 +339,7 @@ public: EXPECT_EQ(0, ec.value()); EXPECT_EQ(udp_length_, length); - // Reissue the receive. + // Reissue the receive call to await the next message. udp_socket_.async_receive_from( asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)), udp_remote_, @@ -351,7 +348,7 @@ public: /// \brief Completion Handler for Accepting TCP Data /// - /// Called when the remote system connects to the "server". It issues + /// Called when the remote system connects to the "TCP server". It issues /// an asynchronous read on the socket to read data. /// /// \param socket Socket on which data will be received @@ -376,7 +373,8 @@ public: /// \brief Completion Handler for Receiving TCP Data /// /// Reads data from the RecursiveQuery object and loops, reissuing reads, - /// until all the message has been read. It then sends + /// until all the message has been read. It then returns an appropriate + /// response. /// /// \param socket Socket to use to send the answer /// \param ec ASIO error code, completion code of asynchronous I/O issued @@ -422,11 +420,7 @@ public: // Check that question we received is what was expected. Note that we // have to ignore the two-byte header in order to parse the message. - Message received_message(Message::PARSE); - InputBuffer received_buffer(tcp_receive_buffer_ + 2, tcp_cumulative_ - 2); - received_message.fromWire(received_buffer); - Question received_question = **(received_message.beginQuestion()); - EXPECT_TRUE(received_question == *question_); + checkReceivedPacket(tcp_receive_buffer_ + 2, length - 2); // Return a message back. This is a referral to example.org, which // should result in another query over UDP. Note the setting of the @@ -443,9 +437,10 @@ public: // Expected next state (when checked) is the UDP query to example.org. // Also, take this opportunity to clear the accumulated read count in // readiness for the next read. (If any - at present, there is only - // one read in the test,, although extensions to this test suite could + // one read in the test, although extensions to this test suite could // change that.) expected_ = UDP_EXAMPLE_ORG; + tcp_cumulative_ = 0; // We'll write the message in two parts, the count and the message // itself. This saves having to prepend the count onto the start of a @@ -485,6 +480,28 @@ public: EXPECT_EQ(expected_length, length); // And that amount sent is as expected } + /// \brief Check Received Packet + /// + /// Checks the packet received from the RecursiveQuery object to ensure + /// that the question is what is expected. + /// + /// \param data Start of data. This is the start of the received buffer in + /// the case of UDP data, and an offset into the buffer past the + /// count field for TCP data. + /// \param length Length of data. + void checkReceivedPacket(uint8_t* data, size_t length) { + + // Decode the received buffer. + InputBuffer buffer(data, length); + Message message(Message::PARSE); + message.fromWire(buffer); + + // Check the packet. + EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR)); + + Question question = **(message.beginQuestion()); + EXPECT_TRUE(question == *question_); + } }; /// \brief Resolver Callback Object From 5cde27560e4b8e3cd098daae572ca4d0124358de Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Thu, 3 Mar 2011 06:34:52 -0600 Subject: [PATCH 023/113] [master] add asterisk for operation change; fix typo --- ChangeLog | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index 517954853d..cd3db95868 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,8 @@ - 184. [func] vorner - Listening address and port configuration of b10-auth is the same as for - b10-resolver now. That means, it is configured trough bindctl at runtime, - in the Auth/listen_on list, not trough command line arguments. + 184. [func]* vorner + Listening address and port configuration of b10-auth is the same as + for b10-resolver now. That means, it is configured through bindctl + at runtime, in the Auth/listen_on list, not through command line + arguments. (Trac #575, #576, git f06ce638877acf6f8e1994962bf2dbfbab029edf) 183. [bug] jerry From 996e2593c867478eec8f2b3700aa52776528394b Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 3 Mar 2011 15:11:53 +0100 Subject: [PATCH 024/113] [trac595] slightly tweak the comment text --- src/lib/cache/message_entry.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc index fb59916fb3..de684a056a 100644 --- a/src/lib/cache/message_entry.cc +++ b/src/lib/cache/message_entry.cc @@ -28,9 +28,9 @@ namespace { // Get the shortest existing ancestor which is the owner name of // one DNAME record for the given query name. -// Note: there maybe multiple DNAME records(DNAME chain) in answer section, -// in most case they are in order, but the code can't depends on it, it has to -// find the starter by iterating the DNAME chain. +// Note: there may be multiple DNAME records(DNAME chain) in answer +// section. In most cases they are in order, but the code can't depend +// on that, it has to find the starter by iterating the DNAME chain. Name getDNAMEChainStarter(const Message& message, const Name& query_name) { Name dname = query_name; @@ -171,12 +171,12 @@ MessageEntry::getRRsetTrustLevel(const Message& message, name == getDNAMEChainStarter(message, query_name))) { return (RRSET_TRUST_ANSWER_AA); } else { - // If there is one CNAME record whose ower name is same with - // query name in answer section, the left records in answer + // If there is a CNAME record whose ower name is the same as + // the query name in answer section, the other records in answer // section are non-authoritative, except the starter of DNAME - // chain(only checking CNAME is enough, because if the CNAME - // record is synchronized from one DNAME record, that DNAME - // record must be the starter of DNAME chain). + // chain (only checking CNAME is enough, because if the CNAME + // record is synthesized from a DNAME record, that DNAME + // record must be the starter of the DNAME chain). RRsetIterator iter = message.beginSection(Message::SECTION_ANSWER); while(iter != message.endSection(Message::SECTION_ANSWER)) { if ((*iter)->getType() == RRType::CNAME() && From b723719cdad0da4db9b2099596c09f407dbdb135 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 3 Mar 2011 18:00:53 +0100 Subject: [PATCH 025/113] [trac583] Added a qid_gen for random qids Simple uniform random number generator using boost. At this moment, it is in asiolink, because that is where it is used. It should be moved to a lib/util (or something), together with the non-uniform nsas rng. Also note that IOFetch now *sets* a random qid, but does not actually check it when it receives an answer. --- src/lib/asiolink/Makefile.am | 1 + src/lib/asiolink/io_fetch.cc | 7 +- src/lib/asiolink/qid_gen.cc | 62 ++++++++++++++++++ src/lib/asiolink/qid_gen.h | 74 ++++++++++++++++++++++ src/lib/asiolink/tests/Makefile.am | 1 + src/lib/asiolink/tests/qid_gen_unittest.cc | 63 ++++++++++++++++++ 6 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 src/lib/asiolink/qid_gen.cc create mode 100644 src/lib/asiolink/qid_gen.h create mode 100644 src/lib/asiolink/tests/qid_gen_unittest.cc diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am index b3968f0661..b0ce027ee2 100644 --- a/src/lib/asiolink/Makefile.am +++ b/src/lib/asiolink/Makefile.am @@ -35,6 +35,7 @@ libasiolink_la_SOURCES += tcp_socket.h libasiolink_la_SOURCES += udp_endpoint.h libasiolink_la_SOURCES += udp_server.h udp_server.cc libasiolink_la_SOURCES += udp_socket.h +libasiolink_la_SOURCES += qid_gen.cc qid_gen.h # Note: the ordering matters: -Wno-... must follow -Wextra (defined in # B10_CXXFLAGS) libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index d1f722cf86..0711bb939d 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -26,6 +26,8 @@ #include #include +#include + #include #include @@ -34,8 +36,9 @@ using namespace isc::dns; using namespace isc::log; using namespace std; -namespace asiolink { + +namespace asiolink { /// IOFetch Constructor - just initialize the private data IOFetch::IOFetch(int protocol, IOService& service, @@ -65,7 +68,7 @@ IOFetch::operator()(error_code ec, size_t length) { Message msg(Message::RENDER); // TODO: replace with boost::random or some other suitable PRNG - msg.setQid(0); + msg.setQid(QidGenerator::getInstance()->generateQid()); msg.setOpcode(Opcode::QUERY()); msg.setRcode(Rcode::NOERROR()); msg.setHeaderFlag(Message::HEADERFLAG_RD); diff --git a/src/lib/asiolink/qid_gen.cc b/src/lib/asiolink/qid_gen.cc new file mode 100644 index 0000000000..c570ee14dd --- /dev/null +++ b/src/lib/asiolink/qid_gen.cc @@ -0,0 +1,62 @@ +// 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. + +// qid_gen defines a generator for query id's +// +// We probably want to merge this with the weighted random in the nsas +// (and other parts where we need randomness, perhaps another thing +// for a general libutil?) + +#include + +#include + +namespace { + asiolink::QidGenerator* qid_generator_instance = NULL; +} + +namespace asiolink { +QidGenerator* +QidGenerator::getInstance() { + if (!qid_generator_instance) { + qid_generator_instance = new QidGenerator(); + qid_generator_instance->seed(); + } + + return qid_generator_instance; +} + +void +QidGenerator::cleanInstance() { + delete qid_generator_instance; + qid_generator_instance = NULL; +} + +QidGenerator::QidGenerator() : dist(0, 65535), vgen(generator, dist) {} + +void +QidGenerator::seed() { + struct timeval tv; + gettimeofday(&tv, 0); + long int seed; + seed = (tv.tv_sec * 1000000) + tv.tv_usec; + generator.seed(seed); +} + +isc::dns::qid_t +QidGenerator::generateQid() { + return vgen(); +} + +} // namespace asiolink diff --git a/src/lib/asiolink/qid_gen.h b/src/lib/asiolink/qid_gen.h new file mode 100644 index 0000000000..89fd506554 --- /dev/null +++ b/src/lib/asiolink/qid_gen.h @@ -0,0 +1,74 @@ +// 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. + +// qid_gen defines a generator for query id's +// +// We probably want to merge this with the weighted random in the nsas +// (and other parts where we need randomness, perhaps another thing +// for a general libutil?) + +#ifndef __QID_GEN_ +#define __QID_GEN_ + +#include +#include +#include +#include + + +namespace asiolink { + +/// This class generates Qids for outgoing queries +/// +/// It is implemented as a singleton; the public way to access it +/// is to call getInstance()->generateQid(). +/// +/// It automatically seeds it with the current time when it is first +/// used. +class QidGenerator { +public: + /// \brief Returns the singleton instance of the QidGenerator + /// + /// Initializes a new instance if there is none. + static QidGenerator* getInstance(); + + /// \brief Cleans up the singleton instance. + /// + /// This is added so that we can run memory checkers and + /// not get complaints about leaking data. If getInstance() + /// is called after cleanInstance, a new one would be created. + static void cleanInstance(); + + /// Generate a Qid + /// + /// \return A random Qid + isc::dns::qid_t generateQid(); + + /// \brief Seeds the QidGenerator (based on the current time) + /// + /// This is automatically called when getInstance() is called + /// the first time. + void seed(); + +private: + QidGenerator(); + boost::mt19937 generator; + boost::uniform_int<> dist; + boost::variate_generator > vgen; +}; + + +} // namespace asiolink + +#endif // __QID_GEN_ diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am index b4f0a87739..ba5d962e98 100644 --- a/src/lib/asiolink/tests/Makefile.am +++ b/src/lib/asiolink/tests/Makefile.am @@ -27,6 +27,7 @@ run_unittests_SOURCES += interval_timer_unittest.cc run_unittests_SOURCES += recursive_query_unittest.cc run_unittests_SOURCES += udp_endpoint_unittest.cc run_unittests_SOURCES += udp_socket_unittest.cc +run_unittests_SOURCES += qid_gen_unittest.cc run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) diff --git a/src/lib/asiolink/tests/qid_gen_unittest.cc b/src/lib/asiolink/tests/qid_gen_unittest.cc new file mode 100644 index 0000000000..aa315828f7 --- /dev/null +++ b/src/lib/asiolink/tests/qid_gen_unittest.cc @@ -0,0 +1,63 @@ +// 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. + +// 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. + + +/// \brief Test of QidGenerator +/// + +#include + +#include +#include + +// Tests the operation of the Qid generator + +// Check that getInstance returns a singleton +TEST(QidGenerator, singleton) { + asiolink::QidGenerator* g1 = asiolink::QidGenerator::getInstance(); + asiolink::QidGenerator* g2 = asiolink::QidGenerator::getInstance(); + + EXPECT_TRUE(g1 == g2); + + asiolink::QidGenerator::cleanInstance(); + // Is there any way to make sure a newly allocated one gets + // a new address? +} + +TEST(QidGenerator, generate) { + // We'll assume that boost's generator is 'good enough', and won't + // do full statistical checking here. Let's just call it the xkcd + // test (http://xkcd.com/221/), and check if three consecutive + // generates are not all the same. + isc::dns::qid_t one, two, three; + asiolink::QidGenerator* gen = asiolink::QidGenerator::getInstance(); + one = gen->generateQid(); + two = gen->generateQid(); + three = gen->generateQid(); + ASSERT_FALSE((one == two) && (one == three)); +} From ff55e63140852f4c7cf8a0404dde36442c45ff4a Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Thu, 3 Mar 2011 18:02:08 +0100 Subject: [PATCH 026/113] [trac583] forgot to remove the TODO line --- src/lib/asiolink/io_fetch.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 0711bb939d..08a5f8f521 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -67,7 +67,6 @@ IOFetch::operator()(error_code ec, size_t length) { { Message msg(Message::RENDER); - // TODO: replace with boost::random or some other suitable PRNG msg.setQid(QidGenerator::getInstance()->generateQid()); msg.setOpcode(Opcode::QUERY()); msg.setRcode(Rcode::NOERROR()); From 87829d23b5d698611c94ee92f758558d8ad95468 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 11:38:47 -0800 Subject: [PATCH 027/113] [master] minor editorial correction. going to push directly. --- src/cppcheck-suppress.lst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cppcheck-suppress.lst b/src/cppcheck-suppress.lst index 2c82b74df5..3e4dcd6948 100644 --- a/src/cppcheck-suppress.lst +++ b/src/cppcheck-suppress.lst @@ -4,7 +4,7 @@ debug missingInclude // This is a template, and should be excluded from the check unreadVariable:src/lib/dns/rdata/template.cc:59 -// These two trigger warnings due to the incomplete implementation. This is +// These three trigger warnings due to the incomplete implementation. This is // our problem, but we need to suppress the warnings for now. functionConst:src/lib/cache/resolver_cache.h functionConst:src/lib/cache/message_cache.h From 48211526895ce56de771e65a7e6d23892c639660 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 16:30:56 -0800 Subject: [PATCH 028/113] [trac606] added ability to change the location of config file runtime. this is one of prerequisites to perform various system tests. --- src/bin/cfgmgr/b10-cfgmgr.py.in | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in index 659426d5dd..169f12bdac 100755 --- a/src/bin/cfgmgr/b10-cfgmgr.py.in +++ b/src/bin/cfgmgr/b10-cfgmgr.py.in @@ -26,10 +26,18 @@ import os isc.util.process.rename() # If B10_FROM_SOURCE is set in the environment, we use data files -# from a directory relative to that, otherwise we use the ones -# installed on the system +# from a directory relative to the value of that variable, or, if defined, +# relative to the value of B10_FROM_SOURCE_CONFIG_DATA_PATH. Otherwise +# we use the ones installed on the system. +# B10_FROM_SOURCE_CONFIG_DATA_PATH is specifically intended to be used for +# tests where we want to use variuos types of configuration within the test +# environment. (We may want to make it even more generic so that the path is +# passed from the boss process) if "B10_FROM_SOURCE" in os.environ: - DATA_PATH = os.environ["B10_FROM_SOURCE"] + if "B10_FROM_SOURCE_CONFIG_DATA_PATH" in os.environ: + DATA_PATH = os.environ["B10_FROM_SOURCE_CONFIG_DATA_PATH"] + else: + DATA_PATH = os.environ["B10_FROM_SOURCE"] else: PREFIX = "@prefix@" DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX) From a9211d7973042503353e61f0e16ea092c0f621bf Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 16:47:30 -0800 Subject: [PATCH 029/113] [trac606] added a new option for bob, --pid-file, to record the PID of bob himself. mainly intended for testing purposes, so disabled by default. for testing this feature bind10_test.py is renamed to bind10_test.py.in and is auto generated by the configure script. --- configure.ac | 1 + src/bin/bind10/bind10.py.in | 35 ++++++++++++++ .../{bind10_test.py => bind10_test.py.in} | 48 ++++++++++++++++++- 3 files changed, 83 insertions(+), 1 deletion(-) rename src/bin/bind10/tests/{bind10_test.py => bind10_test.py.in} (89%) diff --git a/configure.ac b/configure.ac index 0f34b0da22..296b8d1500 100644 --- a/configure.ac +++ b/configure.ac @@ -712,6 +712,7 @@ AC_OUTPUT([doc/version.ent src/bin/stats/tests/stats_test src/bin/bind10/bind10.py src/bin/bind10/tests/bind10_test + src/bin/bind10/tests/bind10_test.py src/bin/bind10/run_bind10.sh src/bin/bindctl/run_bindctl.sh src/bin/bindctl/bindctl-source.py diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in index 83acf1fdfe..0a1e8b1110 100755 --- a/src/bin/bind10/bind10.py.in +++ b/src/bin/bind10/bind10.py.in @@ -785,6 +785,36 @@ def process_rename(option, opt_str, value, parser): """Function that renames the process if it is requested by a option.""" isc.util.process.rename(value) +def dump_pid(pid_file): + """ + Dump the PID of the current process to the specified file. If the given + file is None this function does nothing. If the file already exists, + the existing content will be removed. If a system error happens in + creating or writing to the file, the corresponding exception will be + propagated to the caller. + """ + if pid_file is None: + return + unlink_pid_file(pid_file) + f = open(pid_file, "w") + f.write('%d\n' % os.getpid()) + f.close() + +def unlink_pid_file(pid_file): + """ + Remove the given file, which is basically expected to be the PID file + created by dump_pid(). If the specified may or may not exist; if it + doesn't this function does nothing. Other system level errors in removing + the file will be propagated as the corresponding exception. + """ + if pid_file is None: + return + try: + os.unlink(pid_file) + except OSError as error: + if error.errno is not errno.ENOENT: + raise + def main(): global options global boss_of_bind @@ -805,6 +835,9 @@ def main(): parser.add_option("--pretty-name", type="string", action="callback", callback=process_rename, help="Set the process name (displayed in ps, top, ...)") + parser.add_option("--pid-file", dest="pid_file", type="string", + default=None, + help="file to dump the PID of the BIND 10 process") (options, args) = parser.parse_args() if args: parser.print_help() @@ -865,6 +898,7 @@ def main(): sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result) sys.exit(1) sys.stdout.write("[bind10] BIND 10 started\n") + dump_pid(options.pid_file) # send "bind10.boot_time" to b10-stats time.sleep(1) # wait a second @@ -918,6 +952,7 @@ def main(): signal.signal(signal.SIGCHLD, signal.SIG_DFL) boss_of_bind.shutdown() sys.stdout.write("[bind10] BIND 10 exiting\n"); + unlink_pid_file(options.pid_file) sys.exit(0) if __name__ == "__main__": diff --git a/src/bin/bind10/tests/bind10_test.py b/src/bin/bind10/tests/bind10_test.py.in similarity index 89% rename from src/bin/bind10/tests/bind10_test.py rename to src/bin/bind10/tests/bind10_test.py.in index f3fe949912..6e0539d31c 100644 --- a/src/bin/bind10/tests/bind10_test.py +++ b/src/bin/bind10/tests/bind10_test.py.in @@ -1,4 +1,4 @@ -from bind10 import ProcessInfo, BoB +from bind10 import ProcessInfo, BoB, dump_pid, unlink_pid_file # XXX: environment tests are currently disabled, due to the preprocessor # setup that we have now complicating the environment @@ -412,5 +412,51 @@ class TestStartStopProcessesBob(unittest.TestCase): bob.config_handler({'start_auth': True, 'start_resolver': True}) +class TestPIDFile(unittest.TestCase): + def setUp(self): + self.pid_file = '@builddir@' + os.sep + 'bind10.pid' + if os.path.exists(self.pid_file): + os.unlink(self.pid_file) + + def tearDown(self): + if os.path.exists(self.pid_file): + os.unlink(self.pid_file) + + def check_pid_file(self): + # dump PID to the file, and confirm the content is correct + dump_pid(self.pid_file) + my_pid = os.getpid() + self.assertEqual(my_pid, int(open(self.pid_file, "r").read())) + + def test_dump_pid(self): + self.check_pid_file() + + # make sure any existing content will be removed + open(self.pid_file, "w").write('dummy data\n') + self.check_pid_file() + + def test_dump_pid_notexist(self): + dummy_data = 'dummy_data\n' + open(self.pid_file, "w").write(dummy_data) + dump_pid('no_such_pid_file') + # the original content of the file should be intact. + self.assertEqual(dummy_data, open(self.pid_file, "r").read()) + + def test_dump_pid_with_none(self): + # Check the behavior of dump_pid() and unlink_pid_file() with None. + # This should be no-op. + dump_pid(None) + self.assertFalse(os.path.exists(self.pid_file)) + + dummy_data = 'dummy_data\n' + open(self.pid_file, "w").write(dummy_data) + unlink_pid_file(None) + self.assertEqual(dummy_data, open(self.pid_file, "r").read()) + + def test_dump_pid_failure(self): + # the attempt to open file will fail, which should result in exception. + self.assertRaises(IOError, dump_pid, + 'nonexistent_dir' + os.sep + 'bind10.pid') + if __name__ == '__main__': unittest.main() From eeacd1d5adeb27ee536fb04296cc20ffa898cdab Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 16:52:46 -0800 Subject: [PATCH 030/113] [master] Revert "[trac606] (blindly) imported basic framework of BIND 9 system tests" It was merged to a different branch. This reverts commit e55ec73b0bdcc2b7d6286c6a18886de194280947. --- tests/system/cleanall.sh | 38 ------- tests/system/conf.sh.in | 77 ------------- tests/system/ifconfig.sh | 228 --------------------------------------- tests/system/run.sh | 127 ---------------------- tests/system/runall.sh | 46 -------- 5 files changed, 516 deletions(-) delete mode 100644 tests/system/cleanall.sh delete mode 100644 tests/system/conf.sh.in delete mode 100644 tests/system/ifconfig.sh delete mode 100644 tests/system/run.sh delete mode 100644 tests/system/runall.sh diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh deleted file mode 100644 index 9b6aa6f85f..0000000000 --- a/tests/system/cleanall.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") -# Copyright (C) 2000, 2001 Internet Software Consortium. -# -# 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. - -# $Id: cleanall.sh,v 1.11 2007/06/19 23:47:00 tbox Exp $ - -# -# Clean up after system tests. -# - -SYSTEMTESTTOP=. -. $SYSTEMTESTTOP/conf.sh - - -find . -type f \( \ - -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \ - -o -name '*.pid' -o -name '*.keyset' -o -name named.run \ - -o -name lwresd.run -o -name ans.run \) -print | xargs rm -f - -status=0 - -for d in $SUBDIRS -do - test ! -f $d/clean.sh || ( cd $d && sh clean.sh ) -done diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in deleted file mode 100644 index 09993033c0..0000000000 --- a/tests/system/conf.sh.in +++ /dev/null @@ -1,77 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2004-2011 Internet Systems Consortium, Inc. ("ISC") -# Copyright (C) 2000-2003 Internet Software Consortium. -# -# 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. - -# $Id: conf.sh.in,v 1.59 2011-01-13 04:59:24 tbox Exp $ - -# -# Common configuration data for system tests, to be sourced into -# other shell scripts. -# - -# Find the top of the BIND9 tree. -TOP=${SYSTEMTESTTOP:=.}/../../.. - -# Make it absolute so that it continues to work after we cd. -TOP=`cd $TOP && pwd` - -NAMED=$TOP/bin/named/named -# We must use "named -l" instead of "lwresd" because argv[0] is lost -# if the program is libtoolized. -LWRESD="$TOP/bin/named/named -l" -DIG=$TOP/bin/dig/dig -RNDC=$TOP/bin/rndc/rndc -NSUPDATE=$TOP/bin/nsupdate/nsupdate -DDNSCONFGEN=$TOP/bin/confgen/ddns-confgen -KEYGEN=$TOP/bin/dnssec/dnssec-keygen -KEYFRLAB=$TOP/bin/dnssec/dnssec-keyfromlabel -SIGNER=$TOP/bin/dnssec/dnssec-signzone -REVOKE=$TOP/bin/dnssec/dnssec-revoke -SETTIME=$TOP/bin/dnssec/dnssec-settime -DSFROMKEY=$TOP/bin/dnssec/dnssec-dsfromkey -CHECKZONE=$TOP/bin/check/named-checkzone -CHECKCONF=$TOP/bin/check/named-checkconf -PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -s 0 -p 1234" -PK11LIST="$TOP/bin/pkcs11/pkcs11-list -s 0 -p 1234" -PK11DEL="$TOP/bin/pkcs11/pkcs11-destroy -s 0 -p 1234" -JOURNALPRINT=$TOP/bin/tools/named-journalprint - -# The "stress" test is not run by default since it creates enough -# load on the machine to make it unusable to other users. -# v6synth -SUBDIRS="acl allow_query addzone autosign cacheclean checkconf checknames - dlv @DLZ_SYSTEM_TEST@ dlzexternal dns64 dnssec forward glue gost ixfr limits - lwresd masterfile masterformat metadata notify nsupdate pending pkcs11 - resolver rpz rrsetorder sortlist smartsign staticstub stub tkey - tsig tsiggss unknown upforwd views xfer xferquota zonechecks" - -# PERL will be an empty string if no perl interpreter was found. -PERL=@PERL@ -if test -n "$PERL" -then - if $PERL -e "use IO::Socket::INET6;" 2> /dev/null - then - TESTSOCK6="$PERL $TOP/bin/tests/system/testsock6.pl" - else - TESTSOCK6=false - fi -else - TESTSOCK6=false -fi - -export NAMED LWRESD DIG NSUPDATE KEYGEN KEYFRLAB SIGNER KEYSIGNER KEYSETTOOL \ - PERL SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ - JOURNALPRINT diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh deleted file mode 100644 index dd204cb92d..0000000000 --- a/tests/system/ifconfig.sh +++ /dev/null @@ -1,228 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2004, 2007-2010 Internet Systems Consortium, Inc. ("ISC") -# Copyright (C) 2000-2003 Internet Software Consortium. -# -# 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. - -# $Id: ifconfig.sh,v 1.59 2010/06/11 23:46:49 tbox Exp $ - -# -# Set up interface aliases for bind9 system tests. -# -# IPv4: 10.53.0.{1..7} RFC 1918 -# IPv6: fd92:7065:b8e:ffff::{1..7} ULA -# - -config_guess="" -for f in ./config.guess ../../../config.guess -do - if test -f $f - then - config_guess=$f - fi -done - -if test "X$config_guess" = "X" -then - cat <&2 -$0: must be run from the top level source directory or the -bin/tests/system directory -EOF - exit 1 -fi - -# If running on hp-ux, don't even try to run config.guess. -# It will try to create a temporary file in the current directory, -# which fails when running as root with the current directory -# on a NFS mounted disk. - -case `uname -a` in - *HP-UX*) sys=hpux ;; - *) sys=`sh $config_guess` ;; -esac - -case "$2" in -[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base=$2;; -*) base="" -esac - -case "$3" in -[0-9]|[1-9][0-9]|[1-9][0-9][0-9]) base6=$2;; -*) base6="" -esac - -case "$1" in - - start|up) - for ns in 1 2 3 4 5 6 7 - do - if test -n "$base" - then - int=`expr $ns + $base - 1` - else - int=$ns - fi - if test -n "$base6" - then - int6=`expr $ns + $base6 - 1` - else - int6=$ns - fi - case "$sys" in - *-pc-solaris2.5.1) - ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up - ;; - *-sun-solaris2.[6-7]) - ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up - ;; - *-*-solaris2.[8-9]|*-*-solaris2.1[0-9]) - /sbin/ifconfig lo0:$int plumb - /sbin/ifconfig lo0:$int 10.53.0.$ns up - if test -n "$int6" - then - /sbin/ifconfig lo0:$int6 inet6 plumb - /sbin/ifconfig lo0:$int6 \ - inet6 fd92:7065:b8e:ffff::$ns up - fi - ;; - *-*-linux*) - ifconfig lo:$int 10.53.0.$ns up netmask 255.255.255.0 - ifconfig lo inet6 add fd92:7065:b8e:ffff::$ns/64 - ;; - *-unknown-freebsd*) - ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias - ;; - *-unknown-netbsd*) - ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0 - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias - ;; - *-unknown-openbsd*) - ifconfig lo0 10.53.0.$ns alias netmask 255.255.255.0 - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias - ;; - *-*-bsdi[3-5].*) - ifconfig lo0 add 10.53.0.$ns netmask 255.255.255.0 - ;; - *-dec-osf[4-5].*) - ifconfig lo0 alias 10.53.0.$ns - ;; - *-sgi-irix6.*) - ifconfig lo0 alias 10.53.0.$ns - ;; - *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*) - ifconfig lo0 10.53.0.$ns alias netmask 0xffffffff - ;; - *-ibm-aix4.*|*-ibm-aix5.*) - ifconfig lo0 alias 10.53.0.$ns - ifconfig lo0 inet6 alias -dad fd92:7065:b8e:ffff::$ns/64 - ;; - hpux) - ifconfig lo0:$int 10.53.0.$ns netmask 255.255.255.0 up - ifconfig lo0:$int inet6 fd92:7065:b8e:ffff::$ns up - ;; - *-sco3.2v*) - ifconfig lo0 alias 10.53.0.$ns - ;; - *-darwin*) - ifconfig lo0 alias 10.53.0.$ns - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns alias - ;; - *) - echo "Don't know how to set up interface. Giving up." - exit 1 - esac - done - ;; - - stop|down) - for ns in 7 6 5 4 3 2 1 - do - if test -n "$base" - then - int=`expr $ns + $base - 1` - else - int=$ns - fi - case "$sys" in - *-pc-solaris2.5.1) - ifconfig lo0:$int 0.0.0.0 down - ;; - *-sun-solaris2.[6-7]) - ifconfig lo0:$int 10.53.0.$ns down - ;; - *-*-solaris2.[8-9]|*-*-solaris2.1[0-9]) - ifconfig lo0:$int 10.53.0.$ns down - ifconfig lo0:$int 10.53.0.$ns unplumb - if test -n "$int6" - then - ifconfig lo0:$int6 inet6 down - ifconfig lo0:$int6 inet6 unplumb - fi - ;; - *-*-linux*) - ifconfig lo:$int 10.53.0.$ns down - ifconfig lo inet6 del fd92:7065:b8e:ffff::$ns/64 - ;; - *-unknown-freebsd*) - ifconfig lo0 10.53.0.$ns delete - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete - ;; - *-unknown-netbsd*) - ifconfig lo0 10.53.0.$ns delete - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete - ;; - *-unknown-openbsd*) - ifconfig lo0 10.53.0.$ns delete - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete - ;; - *-*-bsdi[3-5].*) - ifconfig lo0 remove 10.53.0.$ns - ;; - *-dec-osf[4-5].*) - ifconfig lo0 -alias 10.53.0.$ns - ;; - *-sgi-irix6.*) - ifconfig lo0 -alias 10.53.0.$ns - ;; - *-*-sysv5uw7*|*-*-sysv*UnixWare*|*-*-sysv*OpenUNIX*) - ifconfig lo0 -alias 10.53.0.$ns - ;; - *-ibm-aix4.*|*-ibm-aix5.*) - ifconfig lo0 delete 10.53.0.$ns - ifconfig lo0 delete inet6 fd92:7065:b8e:ffff::$ns/64 - ;; - hpux) - ifconfig lo0:$int 0.0.0.0 - ifconfig lo0:$int inet6 :: - ;; - *-sco3.2v*) - ifconfig lo0 -alias 10.53.0.$ns - ;; - *darwin*) - ifconfig lo0 -alias 10.53.0.$ns - ifconfig lo0 inet6 fd92:7065:b8e:ffff::$ns delete - ;; - *) - echo "Don't know how to destroy interface. Giving up." - exit 1 - esac - done - - ;; - - *) - echo "Usage: $0 { up | down } [base]" - exit 1 -esac diff --git a/tests/system/run.sh b/tests/system/run.sh deleted file mode 100644 index 81f3f2b515..0000000000 --- a/tests/system/run.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC") -# Copyright (C) 2000, 2001 Internet Software Consortium. -# -# 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. - -# $Id: run.sh,v 1.45 2010-12-20 21:35:45 each Exp $ - -# -# Run a system test. -# - -SYSTEMTESTTOP=. -. $SYSTEMTESTTOP/conf.sh - -stopservers=true - -case $1 in - --keep) stopservers=false; shift ;; -esac - -test $# -gt 0 || { echo "usage: $0 [--keep] test-directory" >&2; exit 1; } - -test=$1 -shift - -test -d $test || { echo "$0: $test: no such test" >&2; exit 1; } - -echo "S:$test:`date`" >&2 -echo "T:$test:1:A" >&2 -echo "A:System test $test" >&2 - -if [ x$PERL = x ] -then - echo "I:Perl not available. Skipping test." >&2 - echo "R:UNTESTED" >&2 - echo "E:$test:`date`" >&2 - exit 0; -fi - -$PERL testsock.pl || { - echo "I:Network interface aliases not set up. Skipping test." >&2; - echo "R:UNTESTED" >&2; - echo "E:$test:`date`" >&2; - exit 0; -} - - -# Check for test-specific prerequisites. -test ! -f $test/prereq.sh || ( cd $test && sh prereq.sh "$@" ) -result=$? - -if [ $result -eq 0 ]; then - : prereqs ok -else - echo "I:Prerequisites for $test missing, skipping test." >&2 - [ $result -eq 255 ] && echo "R:SKIPPED" || echo "R:UNTESTED" - echo "E:$test:`date`" >&2 - exit 0 -fi - -# Check for PKCS#11 support -if - test ! -f $test/usepkcs11 || sh cleanpkcs11.sh -then - : pkcs11 ok -else - echo "I:Need PKCS#11 for $test, skipping test." >&2 - echo "R:PKCS11ONLY" >&2 - echo "E:$test:`date`" >&2 - exit 0 -fi - -# Set up any dynamically generated test data -if test -f $test/setup.sh -then - ( cd $test && sh setup.sh "$@" ) -fi - -# Start name servers running -$PERL start.pl $test || exit 1 - -# Run the tests -( cd $test ; sh tests.sh ) - -status=$? - -if $stopservers -then - : -else - exit $status -fi - -# Shutdown -$PERL stop.pl $test - -status=`expr $status + $?` - -if [ $status != 0 ]; then - echo "R:FAIL" - # Don't clean up - we need the evidence. - find . -name core -exec chmod 0644 '{}' \; -else - echo "R:PASS" - - # Clean up. - if test -f $test/clean.sh - then - ( cd $test && sh clean.sh "$@" ) - fi -fi - -echo "E:$test:`date`" - -exit $status diff --git a/tests/system/runall.sh b/tests/system/runall.sh deleted file mode 100644 index bf38cd7454..0000000000 --- a/tests/system/runall.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh -# -# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC") -# Copyright (C) 2000, 2001 Internet Software Consortium. -# -# 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. - -# $Id: runall.sh,v 1.10 2010/03/04 23:50:34 tbox Exp $ - -# -# Run all the system tests. -# - -SYSTEMTESTTOP=. -. $SYSTEMTESTTOP/conf.sh - -status=0 - -for d in $SUBDIRS -do - sh run.sh $d || status=1 -done - -$PERL testsock.pl || { - cat <&2 -I: -I:NOTE: Many of the tests were skipped because they require that -I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured -I: as alias addresses on the loopback interface. Please run -I: "bin/tests/system/ifconfig.sh up" as root to configure them -I: and rerun the tests. -EOF - exit 0; -} - -exit $status From de130e6d9b6dec3586126f660123a905cc9a284a Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 16:55:58 -0800 Subject: [PATCH 031/113] [trac606] copied some other necessary stuff from BIND 9's system test framework. --- tests/system/digcomp.pl | 111 ++++++++++++++++++++ tests/system/start.pl | 213 +++++++++++++++++++++++++++++++++++++++ tests/system/stop.pl | 188 ++++++++++++++++++++++++++++++++++ tests/system/testsock.pl | 51 ++++++++++ 4 files changed, 563 insertions(+) create mode 100644 tests/system/digcomp.pl create mode 100644 tests/system/start.pl create mode 100644 tests/system/stop.pl create mode 100644 tests/system/testsock.pl diff --git a/tests/system/digcomp.pl b/tests/system/digcomp.pl new file mode 100644 index 0000000000..87e64cb1b3 --- /dev/null +++ b/tests/system/digcomp.pl @@ -0,0 +1,111 @@ +#!/usr/bin/perl +# +# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# $Id: digcomp.pl,v 1.14 2007/06/19 23:47:00 tbox Exp $ + +# Compare two files, each with the output from dig, for differences. +# Ignore "unimportant" differences, like ordering of NS lines, TTL's, +# etc... + +$file1 = $ARGV[0]; +$file2 = $ARGV[1]; + +$count = 0; +$firstname = ""; +$status = 0; +$rcode1 = "none"; +$rcode2 = "none"; + +open(FILE1, $file1) || die("open: $file1: $!\n"); +while () { + chomp; + if (/^;.+status:\s+(\S+).+$/) { + $rcode1 = $1; + } + next if (/^;/); + if (/^(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+(.+)$/) { + $name = $1; + $class = $2; + $type = $3; + $value = $4; + if ($type eq "SOA") { + $firstname = $name if ($firstname eq ""); + if ($name eq $firstname) { + $name = "$name$count"; + $count++; + } + } + if ($entry{"$name ; $class.$type ; $value"} ne "") { + $line = $entry{"$name ; $class.$type ; $value"}; + print("Duplicate entry in $file1:\n> $_\n< $line\n"); + } else { + $entry{"$name ; $class.$type ; $value"} = $_; + } + } +} +close(FILE1); + +$printed = 0; + +open(FILE2, $file2) || die("open: $file2: $!\n"); +while () { + chomp; + if (/^;.+status:\s+(\S+).+$/) { + $rcode2 = $1; + } + next if (/^;/); + if (/^(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+(.+)$/) { + $name = $1; + $class = $2; + $type = $3; + $value = $4; + if (($name eq $firstname) && ($type eq "SOA")) { + $count--; + $name = "$name$count"; + } + if ($entry{"$name ; $class.$type ; $value"} ne "") { + $entry{"$name ; $class.$type ; $value"} = ""; + } else { + print("Only in $file2 (missing from $file1):\n") + if ($printed == 0); + print("> $_\n"); + $printed++; + $status = 1; + } + } +} +close(FILE2); + +$printed = 0; + +foreach $key (keys(%entry)) { + if ($entry{$key} ne "") { + print("Only in $file1 (missing from $file2):\n") + if ($printed == 0); + print("< $entry{$key}\n"); + $status = 1; + $printed++; + } +} + +if ($rcode1 ne $rcode2) { + print("< status: $rcode1\n"); + print("> status: $rcode2\n"); + $status = 1; +} + +exit($status); diff --git a/tests/system/start.pl b/tests/system/start.pl new file mode 100644 index 0000000000..75b03d994a --- /dev/null +++ b/tests/system/start.pl @@ -0,0 +1,213 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2004-2008, 2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2001 Internet Software Consortium. +# +# 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. + +# $Id: start.pl,v 1.16 2010/09/15 12:07:55 marka Exp $ + +# Framework for starting test servers. +# Based on the type of server specified, check for port availability, remove +# temporary files, start the server, and verify that the server is running. +# If a server is specified, start it. Otherwise, start all servers for test. + +use strict; +use Cwd 'abs_path'; +use Getopt::Long; + +# Option handling +# --noclean test [server [options]] +# +# --noclean - Do not cleanup files in server directory +# test - name of the test directory +# server - name of the server directory +# options - alternate options for the server + +my $usage = "usage: $0 [--noclean] test-directory [server-directory [server-options]]"; +my $noclean; +GetOptions('noclean' => \$noclean); +my $test = $ARGV[0]; +my $server = $ARGV[1]; +my $options = $ARGV[2]; + +if (!$test) { + print "$usage\n"; +} +if (!-d $test) { + print "No test directory: \"$test\"\n"; +} +if ($server && !-d "$test/$server") { + print "No server directory: \"$test/$server\"\n"; +} + +# Global variables +my $topdir = abs_path("$test/.."); +my $testdir = abs_path("$test"); +my $NAMED = $ENV{'NAMED'}; +my $LWRESD = $ENV{'LWRESD'}; +my $DIG = $ENV{'DIG'}; +my $PERL = $ENV{'PERL'}; + +# Start the server(s) + +if ($server) { + if ($server =~ /^ns/) { + &check_ports($server); + } + &start_server($server, $options); + if ($server =~ /^ns/) { + &verify_server($server); + } +} else { + # Determine which servers need to be started for this test. + opendir DIR, $testdir; + my @files = sort readdir DIR; + closedir DIR; + + my @ns = grep /^ns[0-9]*$/, @files; + my @lwresd = grep /^lwresd[0-9]*$/, @files; + my @ans = grep /^ans[0-9]*$/, @files; + + # Start the servers we found. + &check_ports(); + foreach (@ns, @lwresd, @ans) { + &start_server($_); + } + foreach (@ns) { + &verify_server($_); + } +} + +# Subroutines + +sub check_ports { + my $server = shift; + my $options = ""; + + if ($server && $server =~ /(\d+)$/) { + $options = "-i $1"; + } + + my $tries = 0; + while (1) { + my $return = system("$PERL $topdir/testsock.pl -p 5300 $options"); + last if ($return == 0); + if (++$tries > 4) { + print "$0: could not bind to server addresses, still running?\n"; + print "I:server sockets not available\n"; + print "R:FAIL\n"; + system("$PERL $topdir/stop.pl $testdir"); # Is this the correct behavior? + exit 1; + } + print "I:Couldn't bind to socket (yet)\n"; + sleep 2; + } +} + +sub start_server { + my $server = shift; + my $options = shift; + + my $cleanup_files; + my $command; + my $pid_file; + + if ($server =~ /^ns/) { + $cleanup_files = "{*.jnl,*.bk,*.st,named.run}"; + $command = "$NAMED "; + if ($options) { + $command .= "$options"; + } else { + $command .= "-m record,size,mctx "; + $command .= "-T clienttest "; + $command .= "-T nosoa " + if (-e "$testdir/$server/named.nosoa"); + $command .= "-T noaa " + if (-e "$testdir/$server/named.noaa"); + $command .= "-c named.conf -d 99 -g"; + } + $command .= " >named.run 2>&1 &"; + $pid_file = "named.pid"; + } elsif ($server =~ /^lwresd/) { + $cleanup_files = "{lwresd.run}"; + $command = "$LWRESD "; + if ($options) { + $command .= "$options"; + } else { + $command .= "-m record,size,mctx "; + $command .= "-T clienttest "; + $command .= "-C resolv.conf -d 99 -g "; + $command .= "-i lwresd.pid -P 9210 -p 5300"; + } + $command .= " >lwresd.run 2>&1 &"; + $pid_file = "lwresd.pid"; + } elsif ($server =~ /^ans/) { + $cleanup_files = "{ans.run}"; + $command = "$PERL ./ans.pl "; + if ($options) { + $command .= "$options"; + } else { + $command .= ""; + } + $command .= " >ans.run 2>&1 &"; + $pid_file = "ans.pid"; + } else { + print "I:Unknown server type $server\n"; + print "R:FAIL\n"; + system "$PERL $topdir/stop.pl $testdir"; + exit 1; + } + + # print "I:starting server $server\n"; + + chdir "$testdir/$server"; + + unless ($noclean) { + unlink glob $cleanup_files; + } + + system "$command"; + + my $tries = 0; + while (!-f $pid_file) { + if (++$tries > 14) { + print "I:Couldn't start server $server\n"; + print "R:FAIL\n"; + system "$PERL $topdir/stop.pl $testdir"; + exit 1; + } + sleep 1; + } +} + +sub verify_server { + my $server = shift; + my $n = $server; + $n =~ s/^ns//; + + my $tries = 0; + while (1) { + my $return = system("$DIG +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p 5300 version.bind. chaos txt \@10.53.0.$n > dig.out"); + last if ($return == 0); + print `grep ";" dig.out`; + if (++$tries >= 30) { + print "I:no response from $server\n"; + print "R:FAIL\n"; + system("$PERL $topdir/stop.pl $testdir"); + exit 1; + } + sleep 2; + } + unlink "dig.out"; +} diff --git a/tests/system/stop.pl b/tests/system/stop.pl new file mode 100644 index 0000000000..3d4b804995 --- /dev/null +++ b/tests/system/stop.pl @@ -0,0 +1,188 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2001 Internet Software Consortium. +# +# 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. + +# $Id: stop.pl,v 1.12 2007/06/19 23:47:00 tbox Exp $ + +# Framework for stopping test servers +# Based on the type of server specified, signal the server to stop, wait +# briefly for it to die, and then kill it if it is still alive. +# If a server is specified, stop it. Otherwise, stop all servers for test. + +use strict; +use Cwd 'abs_path'; + +# Option handling +# [--use-rndc] test [server] +# +# test - name of the test directory +# server - name of the server directory + +my $usage = "usage: $0 [--use-rndc] test-directory [server-directory]"; +my $use_rndc; + +while (@ARGV && $ARGV[0] =~ /^-/) { + my $opt = shift @ARGV; + if ($opt eq '--use-rndc') { + $use_rndc = 1; + } else { + die "$usage\n"; + } +} + +my $test = $ARGV[0]; +my $server = $ARGV[1]; + +my $errors = 0; + +die "$usage\n" unless defined($test); +die "No test directory: \"$test\"\n" unless (-d $test); +die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server"); + +# Global variables +my $testdir = abs_path($test); +my @servers; + + +# Determine which servers need to be stopped. +if (defined $server) { + @servers = ($server); +} else { + local *DIR; + opendir DIR, $testdir or die "$testdir: $!\n"; + my @files = sort readdir DIR; + closedir DIR; + + my @ns = grep /^ns[0-9]*$/, @files; + my @lwresd = grep /^lwresd[0-9]*$/, @files; + my @ans = grep /^ans[0-9]*$/, @files; + + push @servers, @ns, @lwresd, @ans; +} + + +# Stop the server(s), pass 1: rndc. +if ($use_rndc) { + foreach my $server (grep /^ns/, @servers) { + stop_rndc($server); + } + + wait_for_servers(30, grep /^ns/, @servers); +} + + +# Pass 2: SIGTERM +foreach my $server (@servers) { + stop_signal($server, "TERM"); +} + +wait_for_servers(60, @servers); + +# Pass 3: SIGABRT +foreach my $server (@servers) { + stop_signal($server, "ABRT"); +} + +exit($errors ? 1 : 0); + +# Subroutines + +# Return the full path to a given server's PID file. +sub server_pid_file { + my($server) = @_; + + my $pid_file; + if ($server =~ /^ns/) { + $pid_file = "named.pid"; + } elsif ($server =~ /^lwresd/) { + $pid_file = "lwresd.pid"; + } elsif ($server =~ /^ans/) { + $pid_file = "ans.pid"; + } else { + print "I:Unknown server type $server\n"; + exit 1; + } + $pid_file = "$testdir/$server/$pid_file"; +} + +# Read a PID. +sub read_pid { + my($pid_file) = @_; + + local *FH; + my $result = open FH, "< $pid_file"; + if (!$result) { + print "I:$pid_file: $!\n"; + unlink $pid_file; + return; + } + + my $pid = ; + chomp($pid); + return $pid; +} + +# Stop a named process with rndc. +sub stop_rndc { + my($server) = @_; + + return unless ($server =~ /^ns(\d+)$/); + my $ip = "10.53.0.$1"; + + # Ugly, but should work. + system("$ENV{RNDC} -c $testdir/../common/rndc.conf -s $ip -p 9953 stop | sed 's/^/I:$server /'"); + return; +} + +# Stop a server by sending a signal to it. +sub stop_signal { + my($server, $sig) = @_; + + my $pid_file = server_pid_file($server); + return unless -f $pid_file; + + my $pid = read_pid($pid_file); + return unless defined($pid); + + if ($sig eq 'ABRT') { + print "I:$server didn't die when sent a SIGTERM\n"; + $errors++; + } + + my $result = kill $sig, $pid; + if (!$result) { + print "I:$server died before a SIG$sig was sent\n"; + unlink $pid_file; + $errors++; + } + + return; +} + +sub wait_for_servers { + my($timeout, @servers) = @_; + + my @pid_files = grep { defined($_) } + map { server_pid_file($_) } @servers; + + while ($timeout > 0 && @pid_files > 0) { + @pid_files = grep { -f $_ } @pid_files; + sleep 1 if (@pid_files > 0); + $timeout--; + } + + return; +} diff --git a/tests/system/testsock.pl b/tests/system/testsock.pl new file mode 100644 index 0000000000..a17505012a --- /dev/null +++ b/tests/system/testsock.pl @@ -0,0 +1,51 @@ +#!/usr/bin/perl +# +# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# $Id: testsock.pl,v 1.18 2010/08/17 23:46:46 tbox Exp $ + +# Test whether the interfaces on 10.53.0.* are up. + +require 5.001; + +use Socket; +use Getopt::Long; + +my $port = 0; +my $id = 0; +GetOptions("p=i" => \$port, + "i=i" => \$id); + +my @ids; +if ($id != 0) { + @ids = ($id); +} else { + @ids = (1..7); +} + +foreach $id (@ids) { + my $addr = pack("C4", 10, 53, 0, $id); + my $sa = pack_sockaddr_in($port, $addr); + socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) + or die "$0: socket: $!\n"; + setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)); + + bind(SOCK, $sa) + or die sprintf("$0: bind(%s, %d): $!\n", + inet_ntoa($addr), $port); + close(SOCK); + sleep(1); +} From a2609d0762b9dfdf2750a1bc44ea33525c634f5b Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 17:02:09 -0800 Subject: [PATCH 032/113] [trac606] cleanup: removed unnecessary white spaces. --- tests/system/ifconfig.sh | 4 ++-- tests/system/runall.sh | 2 +- tests/system/start.pl | 4 ++-- tests/system/stop.pl | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) mode change 100644 => 100755 tests/system/ifconfig.sh mode change 100644 => 100755 tests/system/runall.sh mode change 100644 => 100755 tests/system/start.pl mode change 100644 => 100755 tests/system/stop.pl diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh old mode 100644 new mode 100755 index dd204cb92d..0b9f3ef078 --- a/tests/system/ifconfig.sh +++ b/tests/system/ifconfig.sh @@ -87,7 +87,7 @@ case "$1" in ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up ;; *-*-solaris2.[8-9]|*-*-solaris2.1[0-9]) - /sbin/ifconfig lo0:$int plumb + /sbin/ifconfig lo0:$int plumb /sbin/ifconfig lo0:$int 10.53.0.$ns up if test -n "$int6" then @@ -153,7 +153,7 @@ case "$1" in then int=`expr $ns + $base - 1` else - int=$ns + int=$ns fi case "$sys" in *-pc-solaris2.5.1) diff --git a/tests/system/runall.sh b/tests/system/runall.sh old mode 100644 new mode 100755 index bf38cd7454..a16c10bed6 --- a/tests/system/runall.sh +++ b/tests/system/runall.sh @@ -35,7 +35,7 @@ $PERL testsock.pl || { cat <&2 I: I:NOTE: Many of the tests were skipped because they require that -I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured +I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured I: as alias addresses on the loopback interface. Please run I: "bin/tests/system/ifconfig.sh up" as root to configure them I: and rerun the tests. diff --git a/tests/system/start.pl b/tests/system/start.pl old mode 100644 new mode 100755 index 75b03d994a..be4308f36d --- a/tests/system/start.pl +++ b/tests/system/start.pl @@ -131,9 +131,9 @@ sub start_server { } else { $command .= "-m record,size,mctx "; $command .= "-T clienttest "; - $command .= "-T nosoa " + $command .= "-T nosoa " if (-e "$testdir/$server/named.nosoa"); - $command .= "-T noaa " + $command .= "-T noaa " if (-e "$testdir/$server/named.noaa"); $command .= "-c named.conf -d 99 -g"; } diff --git a/tests/system/stop.pl b/tests/system/stop.pl old mode 100644 new mode 100755 index 3d4b804995..195d6ab695 --- a/tests/system/stop.pl +++ b/tests/system/stop.pl @@ -51,7 +51,7 @@ my $errors = 0; die "$usage\n" unless defined($test); die "No test directory: \"$test\"\n" unless (-d $test); die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server"); - + # Global variables my $testdir = abs_path($test); my @servers; @@ -69,7 +69,7 @@ if (defined $server) { my @ns = grep /^ns[0-9]*$/, @files; my @lwresd = grep /^lwresd[0-9]*$/, @files; my @ans = grep /^ans[0-9]*$/, @files; - + push @servers, @ns, @lwresd, @ans; } @@ -153,7 +153,7 @@ sub stop_signal { my $pid_file = server_pid_file($server); return unless -f $pid_file; - + my $pid = read_pid($pid_file); return unless defined($pid); From ddd7b7c3dfdebae68e3742e175e4931f0a5f6c5e Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 17:03:27 -0800 Subject: [PATCH 033/113] [trac606] cleanup: removed $Id$s --- tests/system/cleanall.sh | 2 -- tests/system/conf.sh.in | 2 -- tests/system/digcomp.pl | 2 -- tests/system/ifconfig.sh | 2 -- tests/system/run.sh | 2 -- tests/system/runall.sh | 2 -- tests/system/start.pl | 2 -- tests/system/stop.pl | 2 -- tests/system/testsock.pl | 2 -- 9 files changed, 18 deletions(-) mode change 100644 => 100755 tests/system/cleanall.sh mode change 100644 => 100755 tests/system/conf.sh.in mode change 100644 => 100755 tests/system/digcomp.pl mode change 100644 => 100755 tests/system/run.sh mode change 100644 => 100755 tests/system/testsock.pl diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh old mode 100644 new mode 100755 index 9b6aa6f85f..6e1f695206 --- a/tests/system/cleanall.sh +++ b/tests/system/cleanall.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: cleanall.sh,v 1.11 2007/06/19 23:47:00 tbox Exp $ - # # Clean up after system tests. # diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in old mode 100644 new mode 100755 index 09993033c0..c2e6222f45 --- a/tests/system/conf.sh.in +++ b/tests/system/conf.sh.in @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: conf.sh.in,v 1.59 2011-01-13 04:59:24 tbox Exp $ - # # Common configuration data for system tests, to be sourced into # other shell scripts. diff --git a/tests/system/digcomp.pl b/tests/system/digcomp.pl old mode 100644 new mode 100755 index 87e64cb1b3..506e62b32a --- a/tests/system/digcomp.pl +++ b/tests/system/digcomp.pl @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: digcomp.pl,v 1.14 2007/06/19 23:47:00 tbox Exp $ - # Compare two files, each with the output from dig, for differences. # Ignore "unimportant" differences, like ordering of NS lines, TTL's, # etc... diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh index 0b9f3ef078..f18d57d46a 100755 --- a/tests/system/ifconfig.sh +++ b/tests/system/ifconfig.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: ifconfig.sh,v 1.59 2010/06/11 23:46:49 tbox Exp $ - # # Set up interface aliases for bind9 system tests. # diff --git a/tests/system/run.sh b/tests/system/run.sh old mode 100644 new mode 100755 index 81f3f2b515..3a02a056a1 --- a/tests/system/run.sh +++ b/tests/system/run.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: run.sh,v 1.45 2010-12-20 21:35:45 each Exp $ - # # Run a system test. # diff --git a/tests/system/runall.sh b/tests/system/runall.sh index a16c10bed6..b623f769bd 100755 --- a/tests/system/runall.sh +++ b/tests/system/runall.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: runall.sh,v 1.10 2010/03/04 23:50:34 tbox Exp $ - # # Run all the system tests. # diff --git a/tests/system/start.pl b/tests/system/start.pl index be4308f36d..663ab12bba 100755 --- a/tests/system/start.pl +++ b/tests/system/start.pl @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: start.pl,v 1.16 2010/09/15 12:07:55 marka Exp $ - # Framework for starting test servers. # Based on the type of server specified, check for port availability, remove # temporary files, start the server, and verify that the server is running. diff --git a/tests/system/stop.pl b/tests/system/stop.pl index 195d6ab695..91616fb402 100755 --- a/tests/system/stop.pl +++ b/tests/system/stop.pl @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: stop.pl,v 1.12 2007/06/19 23:47:00 tbox Exp $ - # Framework for stopping test servers # Based on the type of server specified, signal the server to stop, wait # briefly for it to die, and then kill it if it is still alive. diff --git a/tests/system/testsock.pl b/tests/system/testsock.pl old mode 100644 new mode 100755 index a17505012a..44dd9702db --- a/tests/system/testsock.pl +++ b/tests/system/testsock.pl @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: testsock.pl,v 1.18 2010/08/17 23:46:46 tbox Exp $ - # Test whether the interfaces on 10.53.0.* are up. require 5.001; From 309d02a51ee22ff4c0226784ecd7bb4394d19542 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 17:07:08 -0800 Subject: [PATCH 034/113] [trac606-pre] adjusted original scripts so that it can work with BIND 10. --- tests/system/cleanall.sh | 5 +- tests/system/conf.sh.in | 58 ++------- tests/system/digcomp.pl | 109 +++++++++++++++++ tests/system/ifconfig.sh | 8 +- tests/system/run.sh | 2 - tests/system/runall.sh | 4 +- tests/system/start.pl | 246 +++++++++++++++++++++++++++++++++++++++ tests/system/stop.pl | 191 ++++++++++++++++++++++++++++++ tests/system/testsock.pl | 49 ++++++++ 9 files changed, 610 insertions(+), 62 deletions(-) mode change 100644 => 100755 tests/system/cleanall.sh mode change 100644 => 100755 tests/system/conf.sh.in create mode 100755 tests/system/digcomp.pl mode change 100644 => 100755 tests/system/ifconfig.sh mode change 100644 => 100755 tests/system/run.sh mode change 100644 => 100755 tests/system/runall.sh create mode 100755 tests/system/start.pl create mode 100755 tests/system/stop.pl create mode 100755 tests/system/testsock.pl diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh old mode 100644 new mode 100755 index 9b6aa6f85f..48efb3332e --- a/tests/system/cleanall.sh +++ b/tests/system/cleanall.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: cleanall.sh,v 1.11 2007/06/19 23:47:00 tbox Exp $ - # # Clean up after system tests. # @@ -28,7 +26,8 @@ SYSTEMTESTTOP=. find . -type f \( \ -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \ -o -name '*.pid' -o -name '*.keyset' -o -name named.run \ - -o -name lwresd.run -o -name ans.run \) -print | xargs rm -f + -o name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \ + xargs rm -f status=0 diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in old mode 100644 new mode 100755 index 09993033c0..5c6a44d240 --- a/tests/system/conf.sh.in +++ b/tests/system/conf.sh.in @@ -15,63 +15,23 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: conf.sh.in,v 1.59 2011-01-13 04:59:24 tbox Exp $ - # # Common configuration data for system tests, to be sourced into # other shell scripts. # -# Find the top of the BIND9 tree. -TOP=${SYSTEMTESTTOP:=.}/../../.. +# Find the top of the source tree. +TOP=@abs_top_srcdir@ -# Make it absolute so that it continues to work after we cd. -TOP=`cd $TOP && pwd` +RUN_BIND10=$TOP/src/bin/bind10/run_bind10.sh +B10_LOADZONE=$TOP/src/bin/loadzone/run_loadzone.sh +BIND9_NAMED=$BIND9_TOP/bin/named/named +DIG=$BIND9_TOP/bin/dig/dig -NAMED=$TOP/bin/named/named -# We must use "named -l" instead of "lwresd" because argv[0] is lost -# if the program is libtoolized. -LWRESD="$TOP/bin/named/named -l" -DIG=$TOP/bin/dig/dig -RNDC=$TOP/bin/rndc/rndc -NSUPDATE=$TOP/bin/nsupdate/nsupdate -DDNSCONFGEN=$TOP/bin/confgen/ddns-confgen -KEYGEN=$TOP/bin/dnssec/dnssec-keygen -KEYFRLAB=$TOP/bin/dnssec/dnssec-keyfromlabel -SIGNER=$TOP/bin/dnssec/dnssec-signzone -REVOKE=$TOP/bin/dnssec/dnssec-revoke -SETTIME=$TOP/bin/dnssec/dnssec-settime -DSFROMKEY=$TOP/bin/dnssec/dnssec-dsfromkey -CHECKZONE=$TOP/bin/check/named-checkzone -CHECKCONF=$TOP/bin/check/named-checkconf -PK11GEN="$TOP/bin/pkcs11/pkcs11-keygen -s 0 -p 1234" -PK11LIST="$TOP/bin/pkcs11/pkcs11-list -s 0 -p 1234" -PK11DEL="$TOP/bin/pkcs11/pkcs11-destroy -s 0 -p 1234" -JOURNALPRINT=$TOP/bin/tools/named-journalprint - -# The "stress" test is not run by default since it creates enough -# load on the machine to make it unusable to other users. -# v6synth -SUBDIRS="acl allow_query addzone autosign cacheclean checkconf checknames - dlv @DLZ_SYSTEM_TEST@ dlzexternal dns64 dnssec forward glue gost ixfr limits - lwresd masterfile masterformat metadata notify nsupdate pending pkcs11 - resolver rpz rrsetorder sortlist smartsign staticstub stub tkey - tsig tsiggss unknown upforwd views xfer xferquota zonechecks" +SUBDIRS="glue" +#SUBDIRS="dnssec glue masterfile xfer" # PERL will be an empty string if no perl interpreter was found. PERL=@PERL@ -if test -n "$PERL" -then - if $PERL -e "use IO::Socket::INET6;" 2> /dev/null - then - TESTSOCK6="$PERL $TOP/bin/tests/system/testsock6.pl" - else - TESTSOCK6=false - fi -else - TESTSOCK6=false -fi -export NAMED LWRESD DIG NSUPDATE KEYGEN KEYFRLAB SIGNER KEYSIGNER KEYSETTOOL \ - PERL SUBDIRS RNDC CHECKZONE PK11GEN PK11LIST PK11DEL TESTSOCK6 \ - JOURNALPRINT +export RUN_BIND10 BIND9_NAMED DIG SUBDIRS PERL diff --git a/tests/system/digcomp.pl b/tests/system/digcomp.pl new file mode 100755 index 0000000000..506e62b32a --- /dev/null +++ b/tests/system/digcomp.pl @@ -0,0 +1,109 @@ +#!/usr/bin/perl +# +# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# Compare two files, each with the output from dig, for differences. +# Ignore "unimportant" differences, like ordering of NS lines, TTL's, +# etc... + +$file1 = $ARGV[0]; +$file2 = $ARGV[1]; + +$count = 0; +$firstname = ""; +$status = 0; +$rcode1 = "none"; +$rcode2 = "none"; + +open(FILE1, $file1) || die("open: $file1: $!\n"); +while () { + chomp; + if (/^;.+status:\s+(\S+).+$/) { + $rcode1 = $1; + } + next if (/^;/); + if (/^(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+(.+)$/) { + $name = $1; + $class = $2; + $type = $3; + $value = $4; + if ($type eq "SOA") { + $firstname = $name if ($firstname eq ""); + if ($name eq $firstname) { + $name = "$name$count"; + $count++; + } + } + if ($entry{"$name ; $class.$type ; $value"} ne "") { + $line = $entry{"$name ; $class.$type ; $value"}; + print("Duplicate entry in $file1:\n> $_\n< $line\n"); + } else { + $entry{"$name ; $class.$type ; $value"} = $_; + } + } +} +close(FILE1); + +$printed = 0; + +open(FILE2, $file2) || die("open: $file2: $!\n"); +while () { + chomp; + if (/^;.+status:\s+(\S+).+$/) { + $rcode2 = $1; + } + next if (/^;/); + if (/^(\S+)\s+\S+\s+(\S+)\s+(\S+)\s+(.+)$/) { + $name = $1; + $class = $2; + $type = $3; + $value = $4; + if (($name eq $firstname) && ($type eq "SOA")) { + $count--; + $name = "$name$count"; + } + if ($entry{"$name ; $class.$type ; $value"} ne "") { + $entry{"$name ; $class.$type ; $value"} = ""; + } else { + print("Only in $file2 (missing from $file1):\n") + if ($printed == 0); + print("> $_\n"); + $printed++; + $status = 1; + } + } +} +close(FILE2); + +$printed = 0; + +foreach $key (keys(%entry)) { + if ($entry{$key} ne "") { + print("Only in $file1 (missing from $file2):\n") + if ($printed == 0); + print("< $entry{$key}\n"); + $status = 1; + $printed++; + } +} + +if ($rcode1 ne $rcode2) { + print("< status: $rcode1\n"); + print("> status: $rcode2\n"); + $status = 1; +} + +exit($status); diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh old mode 100644 new mode 100755 index dd204cb92d..c0c365ad38 --- a/tests/system/ifconfig.sh +++ b/tests/system/ifconfig.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: ifconfig.sh,v 1.59 2010/06/11 23:46:49 tbox Exp $ - # # Set up interface aliases for bind9 system tests. # @@ -25,7 +23,7 @@ # config_guess="" -for f in ./config.guess ../../../config.guess +for f in ./config.guess ../../config.guess do if test -f $f then @@ -87,7 +85,7 @@ case "$1" in ifconfig lo0:$int 10.53.0.$ns netmask 0xffffffff up ;; *-*-solaris2.[8-9]|*-*-solaris2.1[0-9]) - /sbin/ifconfig lo0:$int plumb + /sbin/ifconfig lo0:$int plumb /sbin/ifconfig lo0:$int 10.53.0.$ns up if test -n "$int6" then @@ -153,7 +151,7 @@ case "$1" in then int=`expr $ns + $base - 1` else - int=$ns + int=$ns fi case "$sys" in *-pc-solaris2.5.1) diff --git a/tests/system/run.sh b/tests/system/run.sh old mode 100644 new mode 100755 index 81f3f2b515..3a02a056a1 --- a/tests/system/run.sh +++ b/tests/system/run.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: run.sh,v 1.45 2010-12-20 21:35:45 each Exp $ - # # Run a system test. # diff --git a/tests/system/runall.sh b/tests/system/runall.sh old mode 100644 new mode 100755 index bf38cd7454..b623f769bd --- a/tests/system/runall.sh +++ b/tests/system/runall.sh @@ -15,8 +15,6 @@ # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -# $Id: runall.sh,v 1.10 2010/03/04 23:50:34 tbox Exp $ - # # Run all the system tests. # @@ -35,7 +33,7 @@ $PERL testsock.pl || { cat <&2 I: I:NOTE: Many of the tests were skipped because they require that -I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured +I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured I: as alias addresses on the loopback interface. Please run I: "bin/tests/system/ifconfig.sh up" as root to configure them I: and rerun the tests. diff --git a/tests/system/start.pl b/tests/system/start.pl new file mode 100755 index 0000000000..8e7704603e --- /dev/null +++ b/tests/system/start.pl @@ -0,0 +1,246 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2004-2008, 2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2001 Internet Software Consortium. +# +# 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. + +# Framework for starting test servers. +# Based on the type of server specified, check for port availability, remove +# temporary files, start the server, and verify that the server is running. +# If a server is specified, start it. Otherwise, start all servers for test. + +use strict; +use Cwd 'abs_path'; +use Getopt::Long; + +# Option handling +# --noclean test [server [options]] +# +# --noclean - Do not cleanup files in server directory +# test - name of the test directory +# server - name of the server directory +# options - alternate options for the server + +my $usage = "usage: $0 [--noclean] test-directory [server-directory [server-options]]"; +my $noclean; +GetOptions('noclean' => \$noclean); +my $test = $ARGV[0]; +my $server = $ARGV[1]; +my $options = $ARGV[2]; + +if (!$test) { + print "$usage\n"; +} +if (!-d $test) { + print "No test directory: \"$test\"\n"; +} +if ($server && !-d "$test/$server") { + print "No server directory: \"$test/$server\"\n"; +} + +# Global variables +my $topdir = abs_path("$test/.."); +my $testdir = abs_path("$test"); +my $RUN_BIND10 = $ENV{'RUN_BIND10'}; +my $NAMED = $ENV{'NAMED'}; +my $LWRESD = $ENV{'LWRESD'}; +my $DIG = $ENV{'DIG'}; +my $PERL = $ENV{'PERL'}; + +# Start the server(s) + +if ($server) { + if ($server =~ /^ns/) { + &check_ports($server); + } + &start_server($server, $options); + if ($server =~ /^ns/) { + &verify_server($server); + } +} else { + # Determine which servers need to be started for this test. + opendir DIR, $testdir; + my @files = sort readdir DIR; + closedir DIR; + + my @ns = grep /^nsx?[0-9]*$/, @files; + my @lwresd = grep /^lwresd[0-9]*$/, @files; + my @ans = grep /^ans[0-9]*$/, @files; + + # Start the servers we found. + &check_ports(); + foreach my $s (@ns, @lwresd, @ans) { + &start_server($s); + } + foreach my $s (@ns) { + &verify_server($s); + } +} + +# Subroutines + +sub check_ports { + my $server = shift; + my $options = ""; + + if ($server && $server =~ /(\d+)$/) { + $options = "-i $1"; + } + + my $tries = 0; + while (1) { + my $return = system("$PERL $topdir/testsock.pl -p 5300 $options"); + last if ($return == 0); + if (++$tries > 4) { + print "$0: could not bind to server addresses, still running?\n"; + print "I:server sockets not available\n"; + print "R:FAIL\n"; + system("$PERL $topdir/stop.pl $testdir"); # Is this the correct behavior? + exit 1; + } + print "I:Couldn't bind to socket (yet)\n"; + sleep 2; + } +} + +sub start_server { + my $server = shift; + my $options = shift; + + my $cleanup_files; + my $command; + my $pid_file; + + if ($server =~ /^nsx/) { + $cleanup_files = "{bind10.run}"; + $command = "B10_FROM_SOURCE_CONFIG_DATA_PATH=$testdir/$server/ "; + $command .= "$RUN_BIND10 "; + if ($options) { + $command .= "$options"; + } else { + $command .= "--msgq-socket-file=$testdir/$server/msgq_socket "; + $command .= "--pid-file=$testdir/$server/bind10.pid "; + $command .= "-v"; + } + $command .= " >bind10.run 2>&1 &"; + $pid_file = "bind10.pid"; + } elsif ($server =~ /^ns/) { + $cleanup_files = "{*.jnl,*.bk,*.st,named.run}"; + $command = "$NAMED "; + if ($options) { + $command .= "$options"; + } else { + $command .= "-m record,size,mctx "; + $command .= "-T clienttest "; + $command .= "-T nosoa " + if (-e "$testdir/$server/named.nosoa"); + $command .= "-T noaa " + if (-e "$testdir/$server/named.noaa"); + $command .= "-c named.conf -d 99 -g"; + } + $command .= " >named.run 2>&1 &"; + $pid_file = "named.pid"; + } elsif ($server =~ /^lwresd/) { + $cleanup_files = "{lwresd.run}"; + $command = "$LWRESD "; + if ($options) { + $command .= "$options"; + } else { + $command .= "-m record,size,mctx "; + $command .= "-T clienttest "; + $command .= "-C resolv.conf -d 99 -g "; + $command .= "-i lwresd.pid -P 9210 -p 5300"; + } + $command .= " >lwresd.run 2>&1 &"; + $pid_file = "lwresd.pid"; + } elsif ($server =~ /^ans/) { + $cleanup_files = "{ans.run}"; + $command = "$PERL ./ans.pl "; + if ($options) { + $command .= "$options"; + } else { + $command .= ""; + } + $command .= " >ans.run 2>&1 &"; + $pid_file = "ans.pid"; + } else { + print "I:Unknown server type $server\n"; + print "R:FAIL\n"; + system "$PERL $topdir/stop.pl $testdir"; + exit 1; + } + + # print "I:starting server $server\n"; + + chdir "$testdir/$server"; + + unless ($noclean) { + unlink glob $cleanup_files; + } + + system "$command"; + + my $tries = 0; + while (!-f $pid_file) { + if (++$tries > 14) { + print "I:Couldn't start server $server\n"; + print "R:FAIL\n"; + system "$PERL $topdir/stop.pl $testdir"; + exit 1; + } + sleep 1; + } +} + +sub verify_server { + my $server = shift; + my $n = $server; + $n =~ s/^nsx?//; + + my $tries = 0; + while (1) { + my $return = system("$DIG +tcp +noadd +nosea +nostat +noquest +nocomm +nocmd -p 5300 version.bind. chaos txt \@10.53.0.$n > dig.out"); + last if ($return == 0); + print `grep ";" dig.out`; + if (++$tries >= 30) { + print "I:no response from $server\n"; + print "R:FAIL\n"; + system("$PERL $topdir/stop.pl $testdir"); + exit 1; + } + sleep 2; + } + unlink "dig.out"; +} + +sub get_bind10_pid { + my $runfile = shift; + my $pidfile = shift; + my $pid; + + return if (!-f $runfile); + open(RUNFILE, $runfile) || return; + while () { + if (/BIND 10 started \(PID ([\d]+)\)/) { + $pid = $1; + last; + } + } + close(RUNFILE); + if ($pid) { + open(PIDFILE, ">$pidfile") || return; + print PIDFILE "$pid\n"; + close(PIDFILE); + } +} diff --git a/tests/system/stop.pl b/tests/system/stop.pl new file mode 100755 index 0000000000..693b9f0ad5 --- /dev/null +++ b/tests/system/stop.pl @@ -0,0 +1,191 @@ +#!/usr/bin/perl -w +# +# Copyright (C) 2004-2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2001 Internet Software Consortium. +# +# 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. + +# Framework for stopping test servers +# Based on the type of server specified, signal the server to stop, wait +# briefly for it to die, and then kill it if it is still alive. +# If a server is specified, stop it. Otherwise, stop all servers for test. + +use strict; +use Cwd 'abs_path'; + +# Option handling +# [--use-rndc] test [server] +# +# test - name of the test directory +# server - name of the server directory + +my $usage = "usage: $0 [--use-rndc] test-directory [server-directory]"; +my $use_rndc; + +while (@ARGV && $ARGV[0] =~ /^-/) { + my $opt = shift @ARGV; + if ($opt eq '--use-rndc') { + $use_rndc = 1; + } else { + die "$usage\n"; + } +} + +my $test = $ARGV[0]; +my $server = $ARGV[1]; + +my $errors = 0; + +die "$usage\n" unless defined($test); +die "No test directory: \"$test\"\n" unless (-d $test); +die "No server directory: \"$server\"\n" if (defined($server) && !-d "$test/$server"); + +# Global variables +my $testdir = abs_path($test); +my @servers; + + +# Determine which servers need to be stopped. +if (defined $server) { + @servers = ($server); +} else { + local *DIR; + opendir DIR, $testdir or die "$testdir: $!\n"; + my @files = sort readdir DIR; + closedir DIR; + + my @ns = grep /^nsx?[0-9]*$/, @files; + my @lwresd = grep /^lwresd[0-9]*$/, @files; + my @ans = grep /^ans[0-9]*$/, @files; + + push @servers, @ns, @lwresd, @ans; +} + + +# Stop the server(s), pass 1: rndc. +if ($use_rndc) { + foreach my $server (grep /^ns/, @servers) { + stop_rndc($server); + } + + wait_for_servers(30, grep /^ns/, @servers); +} + + +# Pass 2: SIGTERM +foreach my $server (@servers) { + stop_signal($server, "TERM"); +} + +wait_for_servers(60, @servers); + +# Pass 3: SIGABRT +foreach my $server (@servers) { + stop_signal($server, "ABRT"); +} + +exit($errors ? 1 : 0); + +# Subroutines + +# Return the full path to a given server's PID file. +sub server_pid_file { + my($server) = @_; + + my $pid_file; + if ($server =~ /^nsx/) { + $pid_file = "bind10.pid"; + } elsif ($server =~ /^ns/) { + $pid_file = "named.pid"; + } elsif ($server =~ /^lwresd/) { + $pid_file = "lwresd.pid"; + } elsif ($server =~ /^ans/) { + $pid_file = "ans.pid"; + } else { + print "I:Unknown server type $server\n"; + exit 1; + } + $pid_file = "$testdir/$server/$pid_file"; + + + return $pid_file; +} + +# Read a PID. +sub read_pid { + my($pid_file) = @_; + + local *FH; + my $result = open FH, "< $pid_file"; + if (!$result) { + print "I:$pid_file: $!\n"; + unlink $pid_file; + return; + } + + my $pid = ; + chomp($pid); + return $pid; +} + +# Stop a named process with rndc. +sub stop_rndc { + my($server) = @_; + + return unless ($server =~ /^ns(\d+)$/); + my $ip = "10.53.0.$1"; + + # Ugly, but should work. + system("$ENV{RNDC} -c $testdir/../common/rndc.conf -s $ip -p 9953 stop | sed 's/^/I:$server /'"); + return; +} + +# Stop a server by sending a signal to it. +sub stop_signal { + my($server, $sig) = @_; + + my $pid_file = server_pid_file($server); + return unless -f $pid_file; + + my $pid = read_pid($pid_file); + return unless defined($pid); + + if ($sig eq 'ABRT') { + print "I:$server didn't die when sent a SIGTERM\n"; + $errors++; + } + + my $result = kill $sig, $pid; + if (!$result) { + print "I:$server died before a SIG$sig was sent\n"; + unlink $pid_file; + $errors++; + } + + return; +} + +sub wait_for_servers { + my($timeout, @servers) = @_; + + my @pid_files = grep { defined($_) } + map { server_pid_file($_) } @servers; + + while ($timeout > 0 && @pid_files > 0) { + @pid_files = grep { -f $_ } @pid_files; + sleep 1 if (@pid_files > 0); + $timeout--; + } + + return; +} diff --git a/tests/system/testsock.pl b/tests/system/testsock.pl new file mode 100755 index 0000000000..44dd9702db --- /dev/null +++ b/tests/system/testsock.pl @@ -0,0 +1,49 @@ +#!/usr/bin/perl +# +# Copyright (C) 2004, 2007, 2010 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# Test whether the interfaces on 10.53.0.* are up. + +require 5.001; + +use Socket; +use Getopt::Long; + +my $port = 0; +my $id = 0; +GetOptions("p=i" => \$port, + "i=i" => \$id); + +my @ids; +if ($id != 0) { + @ids = ($id); +} else { + @ids = (1..7); +} + +foreach $id (@ids) { + my $addr = pack("C4", 10, 53, 0, $id); + my $sa = pack_sockaddr_in($port, $addr); + socket(SOCK, PF_INET, SOCK_STREAM, getprotobyname("tcp")) + or die "$0: socket: $!\n"; + setsockopt(SOCK, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)); + + bind(SOCK, $sa) + or die sprintf("$0: bind(%s, %d): $!\n", + inet_ntoa($addr), $port); + close(SOCK); + sleep(1); +} From 56df4f9ab31b300b83c26fbeb1a7cf7951f8338e Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 17:16:55 -0800 Subject: [PATCH 035/113] [trac606] check availability of perl, which is used in system tests. --- configure.ac | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/configure.ac b/configure.ac index 296b8d1500..36ab28bbe1 100644 --- a/configure.ac +++ b/configure.ac @@ -583,6 +583,12 @@ if test "X$ac_cv_have_devpoll" = "Xyes" -a "X$GXX" = "Xyes"; then CPPFLAGS="$CPPFLAGS -DASIO_DISABLE_DEV_POLL=1" fi +# +# Perl is optional; it is used only by some of the system test scripts. +# +AC_PATH_PROGS(PERL, perl5 perl) +AC_SUBST(PERL) + AC_ARG_ENABLE(man, [AC_HELP_STRING([--enable-man], [regenerate man pages [default=no]])], enable_man=yes, enable_man=no) @@ -740,6 +746,7 @@ AC_OUTPUT([doc/version.ent src/lib/cc/session_config.h.pre src/lib/cc/tests/session_unittests_config.h src/lib/log/tests/run_time_init_test.sh + tests/system/conf.sh ], [ chmod +x src/bin/cmdctl/run_b10-cmdctl.sh chmod +x src/bin/xfrin/run_b10-xfrin.sh From 9c3f27137967b5f92c430baab6ab442258d9d692 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 17:20:05 -0800 Subject: [PATCH 036/113] [trac606] make sure conf.sh is executable. --- configure.ac | 1 + 1 file changed, 1 insertion(+) diff --git a/configure.ac b/configure.ac index 36ab28bbe1..be93242cb0 100644 --- a/configure.ac +++ b/configure.ac @@ -771,6 +771,7 @@ AC_OUTPUT([doc/version.ent chmod +x src/lib/dns/gen-rdatacode.py chmod +x src/lib/dns/tests/testdata/gen-wiredata.py chmod +x src/lib/log/tests/run_time_init_test.sh + chmod +x tests/system/conf.sh ]) AC_OUTPUT From 7396247046ccc046d711265100aa8d46be057626 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 18:07:35 -0800 Subject: [PATCH 037/113] [trac606] ported BIND 9's 'glue' system test --- tests/system/glue/auth.good | 15 +++++ tests/system/glue/clean.sh | 23 ++++++++ tests/system/glue/example.good | 19 +++++++ tests/system/glue/noglue.good | 14 +++++ tests/system/glue/nsx1/b10-config.db.in | 6 ++ tests/system/glue/nsx1/com.db | 31 ++++++++++ tests/system/glue/nsx1/net.db | 32 +++++++++++ tests/system/glue/nsx1/root-servers.nil.db | 26 +++++++++ tests/system/glue/nsx1/root.db | 55 ++++++++++++++++++ tests/system/glue/setup.sh.in | 29 ++++++++++ tests/system/glue/test.good | 19 +++++++ tests/system/glue/tests.sh | 66 ++++++++++++++++++++++ 12 files changed, 335 insertions(+) create mode 100644 tests/system/glue/auth.good create mode 100755 tests/system/glue/clean.sh create mode 100644 tests/system/glue/example.good create mode 100644 tests/system/glue/noglue.good create mode 100644 tests/system/glue/nsx1/b10-config.db.in create mode 100644 tests/system/glue/nsx1/com.db create mode 100644 tests/system/glue/nsx1/net.db create mode 100644 tests/system/glue/nsx1/root-servers.nil.db create mode 100644 tests/system/glue/nsx1/root.db create mode 100755 tests/system/glue/setup.sh.in create mode 100644 tests/system/glue/test.good create mode 100755 tests/system/glue/tests.sh diff --git a/tests/system/glue/auth.good b/tests/system/glue/auth.good new file mode 100644 index 0000000000..2c619f6792 --- /dev/null +++ b/tests/system/glue/auth.good @@ -0,0 +1,15 @@ + +; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example.org. a +;; global options: printcmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41239 +;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1 + +;; QUESTION SECTION: +;foo.bar.example.org. IN A + +;; AUTHORITY SECTION: +example.org. 172800 IN NS b.root-servers.nil. + +;; ADDITIONAL SECTION: +b.root-servers.nil. 300 IN A 10.53.0.2 diff --git a/tests/system/glue/clean.sh b/tests/system/glue/clean.sh new file mode 100755 index 0000000000..b2c1e02608 --- /dev/null +++ b/tests/system/glue/clean.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001 Internet Software Consortium. +# +# 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. + +# +# Clean up after glue tests. +# + +rm -f dig.out.* +rm -f */msgq_socket */zone.sqlite3 diff --git a/tests/system/glue/example.good b/tests/system/glue/example.good new file mode 100644 index 0000000000..3b7bbb8166 --- /dev/null +++ b/tests/system/glue/example.good @@ -0,0 +1,19 @@ + +; <<>> DiG 9.0 <<>> +norec @10.53.0.1 -p 5300 foo.bar.example. A +;; global options: printcmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 58772 +;; flags: qr ad; QUERY: 1, ANSWER: 0, AUTHORITY: 6, ADDITIONAL: 7 + +;; QUESTION SECTION: +;foo.bar.example. IN A + +;; AUTHORITY SECTION: +example. 172800 IN NS NS1.example.COM. +example. 172800 IN NS NS.example. + +;; ADDITIONAL SECTION: +NS.example. 172800 IN A 192.0.2.1 +NS.example. 172800 IN A 192.0.2.2 +NS1.example.COM. 172800 IN A 192.0.2.101 +NS1.example.COM. 172800 IN AAAA 2001:db8::1 diff --git a/tests/system/glue/noglue.good b/tests/system/glue/noglue.good new file mode 100644 index 0000000000..57a2211666 --- /dev/null +++ b/tests/system/glue/noglue.good @@ -0,0 +1,14 @@ + +; <<>> DiG 9.0 <<>> @10.53.0.1 -p 5300 example.net a +;; global options: printcmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 29409 +;; flags: qr rd ad; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 0 + +;; QUESTION SECTION: +;example.net. IN A + +;; AUTHORITY SECTION: +example.net. 300 IN NS ns2.example.info. +example.net. 300 IN NS ns1.example.info. + diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in new file mode 100644 index 0000000000..eaecb12bd2 --- /dev/null +++ b/tests/system/glue/nsx1/b10-config.db.in @@ -0,0 +1,6 @@ +{"version": 2, + "Auth": { + "listen_on": [{"address": "10.53.0.1", "port": 5300}], + "database_file": "@abs_builddir@/zone.sqlite3" + } +} diff --git a/tests/system/glue/nsx1/com.db b/tests/system/glue/nsx1/com.db new file mode 100644 index 0000000000..c4b94e1f79 --- /dev/null +++ b/tests/system/glue/nsx1/com.db @@ -0,0 +1,31 @@ +; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2000, 2001 Internet Software Consortium. +; +; 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. + +$ORIGIN com. +$TTL 300 +@ IN SOA root.example.com. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +@ NS a.root-servers.nil. + +example.com. NS ns1.example.com. +example.com. NS ns2.example.com. +ns1.example.com. 172800 IN A 192.0.2.101 +ns1.example.com. 172800 IN AAAA 2001:db8::1 +ns2.example.com. 172800 IN A 192.0.2.102 diff --git a/tests/system/glue/nsx1/net.db b/tests/system/glue/nsx1/net.db new file mode 100644 index 0000000000..8b66521968 --- /dev/null +++ b/tests/system/glue/nsx1/net.db @@ -0,0 +1,32 @@ +; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2000, 2001 Internet Software Consortium. +; +; 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. + +$ORIGIN net. +$TTL 300 +@ IN SOA root.example.net. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +@ NS a.root-servers.nil. + +; Referral outside of server authority, but with glue records present. +; Don't hand out the glue. +example.net. NS ns1.example.info. +example.net. NS ns2.example.info. +ns1.example.info. 172800 IN A 192.0.2.101 +ns2.example.info. 172800 IN A 192.0.2.102 diff --git a/tests/system/glue/nsx1/root-servers.nil.db b/tests/system/glue/nsx1/root-servers.nil.db new file mode 100644 index 0000000000..45050a9b52 --- /dev/null +++ b/tests/system/glue/nsx1/root-servers.nil.db @@ -0,0 +1,26 @@ +; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2000, 2001 Internet Software Consortium. +; +; 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. + +$TTL 300 +@ IN SOA ns hostmaster ( + 1 + 3600 + 1800 + 1814400 + 3600 + ) + NS a +a A 10.53.0.1 +b A 10.53.0.2 diff --git a/tests/system/glue/nsx1/root.db b/tests/system/glue/nsx1/root.db new file mode 100644 index 0000000000..e43f2d2567 --- /dev/null +++ b/tests/system/glue/nsx1/root.db @@ -0,0 +1,55 @@ +; Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +; Copyright (C) 2000, 2001 Internet Software Consortium. +; +; 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. + +$TTL 300 +. IN SOA postmaster.example. a.root.servers.nil. ( + 2000042100 ; serial + 600 ; refresh + 600 ; retry + 1200 ; expire + 600 ; minimum + ) +. NS a.root-servers.nil. + +root-servers.nil. NS a.root-servers.nil. +a.root-servers.nil. A 10.53.0.1 + +; Delegate some domains that contain name servers for the sample +; ccTLDs below. +com. 172800 IN NS a.root-servers.nil. + +; +; A sample TLD +; +example. 172800 IN NS NS.example. +example. 172800 IN NS NS1.example.COM. +NS.example. 172800 IN A 192.0.2.1 +NS.example. 172800 IN A 192.0.2.2 +; this "glue" is below a zone cut for com. BIND 9 still uses it for +; the delegation to example. BIND 10 (with sqlite3 data source) doesn't. +NS1.example.COM. 172800 IN A 192.0.2.3 + +; +; +; +test. 172800 IN NS ns.test. +test. 172800 IN NS ns1.example.net. +ns.test. 172800 IN A 192.0.2.200 +ns1.example.net. 172800 IN A 192.0.2.201 + +; +; A hypothetical ccTLD where we are authoritative for the NS glue. +; +example.org 172800 IN NS b.root-servers.nil. diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in new file mode 100755 index 0000000000..4e19588505 --- /dev/null +++ b/tests/system/glue/setup.sh.in @@ -0,0 +1,29 @@ +#!/bin/sh +# +# 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. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +# +# Clean up after glue tests. +# + +rm -f */zone.sqlite3 +${B10_LOADZONE} -o . -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/root.db +${B10_LOADZONE} -o root-servers.nil -d @builddir@/nsx1/zone.sqlite3 \ + @builddir@/nsx1/root-servers.nil.db +${B10_LOADZONE} -o com -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/com.db +${B10_LOADZONE} -o net -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/net.db diff --git a/tests/system/glue/test.good b/tests/system/glue/test.good new file mode 100644 index 0000000000..b9b4719bd0 --- /dev/null +++ b/tests/system/glue/test.good @@ -0,0 +1,19 @@ + +; <<>> DiG 9.8.0 <<>> @127.0.0.1 -p 5300 foo.bar.test +; (1 server found) +;; global options: +cmd +;; Got answer: +;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 55069 +;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 2 +;; WARNING: recursion requested but not available + +;; QUESTION SECTION: +;foo.bar.test. IN A + +;; AUTHORITY SECTION: +test. 172800 IN NS ns.test. +test. 172800 IN NS ns1.example.net. + +;; ADDITIONAL SECTION: +ns.test. 172800 IN A 192.0.2.200 +ns1.example.net. 172800 IN A 192.0.2.201 diff --git a/tests/system/glue/tests.sh b/tests/system/glue/tests.sh new file mode 100755 index 0000000000..21f93f52a1 --- /dev/null +++ b/tests/system/glue/tests.sh @@ -0,0 +1,66 @@ +#!/bin/sh +# +# Copyright (C) 2004, 2007 Internet Systems Consortium, Inc. ("ISC") +# Copyright (C) 2000, 2001, 2003 Internet Software Consortium. +# +# 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. + +SYSTEMTESTTOP=.. +. $SYSTEMTESTTOP/conf.sh + +# +# Do glue tests. +# + +status=0 +n=0 + +# This query should result in a delegation with two NS; one in the delegated +# zone and one in a so called out-of-bailiwick zone for which the auth server +# has authority, too. For the former, the server should return glue in the +# parent zone. For the latter, BIND 9 and BIND 10 behave differently; BIND 9 +# uses "glue" in the parent zone (since this is the root zone everything can +# be considered a valid glue). BIND 10 (using sqlite3 data source) searches +# the other zone and uses the authoritative data in that zone (which is +# intentionally different from the glue in the root zone). +echo "I:testing that a TLD referral gets a full glue set from the root zone ($n)" +$DIG +norec @10.53.0.1 -p 5300 foo.bar.example. A >dig.out.$n || status=1 +$PERL ../digcomp.pl example.good dig.out.$n || status=1 +n=`expr $n + 1` + +echo "I:testing that we find glue A RRs we are authoritative for ($n)" +$DIG +norec @10.53.0.1 -p 5300 foo.bar.example.org. a >dig.out.$n || status=1 +$PERL ../digcomp.pl auth.good dig.out.$n || status=1 +n=`expr $n + 1` + +# We cannot do this test for BIND 10 because b10-auth doesn't act as a +# recursive (caching) server (by design) +# echo "I:testing that we find glue A/AAAA RRs in the cache ($n)" +# $DIG +norec @10.53.0.1 -p 5300 foo.bar.yy. a >dig.out.$n || status=1 +# $PERL ../digcomp.pl yy.good dig.out.$n || status=1 +# n=`expr $n + 1` + +echo "I:testing that we don't find out-of-zone glue ($n)" +$DIG +norec @10.53.0.1 -p 5300 example.net. a > dig.out.$n || status=1 +$PERL ../digcomp.pl noglue.good dig.out.$n || status=1 +n=`expr $n + 1` + +# This test currently fails (additional section will be empty, which is +# incorrect). See Trac ticket #646. +#echo "I:testing that we are finding partial out-of-zone glue ($n)" +#$DIG +norec @10.53.0.1 -p 5300 foo.bar.test. a >dig.out.$n || status=1 +#$PERL ../digcomp.pl test.good dig.out.$n || status=1 +#n=`expr $n + 1` + +echo "I:exit status: $status" +exit $status From edfcbcaccbf4b8098f5cfc1bd1943fe4542b3306 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 19:07:06 -0800 Subject: [PATCH 038/113] [trac606] make cleanall.sh not dependent on conf.sh so that it will work in 'make distclean' --- tests/system/cleanall.sh | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/system/cleanall.sh b/tests/system/cleanall.sh index 48efb3332e..17c3d4a6eb 100755 --- a/tests/system/cleanall.sh +++ b/tests/system/cleanall.sh @@ -19,19 +19,15 @@ # Clean up after system tests. # -SYSTEMTESTTOP=. -. $SYSTEMTESTTOP/conf.sh - - find . -type f \( \ -name 'K*' -o -name '*~' -o -name '*.core' -o -name '*.log' \ -o -name '*.pid' -o -name '*.keyset' -o -name named.run \ - -o name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \ + -o -name bind10.run -o -name lwresd.run -o -name ans.run \) -print | \ xargs rm -f status=0 -for d in $SUBDIRS +for d in `find . -type d -maxdepth 1 -mindepth 1 -print` do test ! -f $d/clean.sh || ( cd $d && sh clean.sh ) done From 8be2bf19228473f5d54f5015e893b44791a5c0c2 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 19:35:33 -0800 Subject: [PATCH 039/113] [trac606] added/updated some Makefile.am's so that the tests can be run via make. --- Makefile.am | 7 ++++++- configure.ac | 4 ++++ tests/Makefile.am | 1 + tests/system/Makefile.am | 5 +++++ 4 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 tests/Makefile.am create mode 100644 tests/system/Makefile.am diff --git a/Makefile.am b/Makefile.am index 9a28f200f0..e2203dba4d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = doc src +SUBDIRS = doc src tests USE_LCOV=@USE_LCOV@ LCOV=@LCOV@ GENHTML=@GENHTML@ @@ -77,6 +77,11 @@ cppcheck: --template '{file}:{line}: check_fail: {message} ({severity},{id})' \ src +# system tests +systest: + cd tests/system; \ + sh runall.sh + #### include external sources in the distributed tarball: EXTRA_DIST = ext/asio/README EXTRA_DIST += ext/asio/asio/local/stream_protocol.hpp diff --git a/configure.ac b/configure.ac index be93242cb0..56a57441f1 100644 --- a/configure.ac +++ b/configure.ac @@ -689,6 +689,8 @@ AC_CONFIG_FILES([Makefile src/lib/cache/tests/Makefile src/lib/server_common/Makefile src/lib/server_common/tests/Makefile + tests/Makefile + tests/system/Makefile ]) AC_OUTPUT([doc/version.ent src/bin/cfgmgr/b10-cfgmgr.py @@ -747,6 +749,8 @@ AC_OUTPUT([doc/version.ent src/lib/cc/tests/session_unittests_config.h src/lib/log/tests/run_time_init_test.sh tests/system/conf.sh + tests/system/glue/setup.sh + tests/system/glue/nsx1/b10-config.db ], [ chmod +x src/bin/cmdctl/run_b10-cmdctl.sh chmod +x src/bin/xfrin/run_b10-xfrin.sh diff --git a/tests/Makefile.am b/tests/Makefile.am new file mode 100644 index 0000000000..d4008c0dd0 --- /dev/null +++ b/tests/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = system diff --git a/tests/system/Makefile.am b/tests/system/Makefile.am new file mode 100644 index 0000000000..44e09e080b --- /dev/null +++ b/tests/system/Makefile.am @@ -0,0 +1,5 @@ +systest: + sh runall.sh + +distclean-local: + sh cleanall.sh From 5851a6cc19356b49fcc2500f5d40e7861874561e Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 19:36:06 -0800 Subject: [PATCH 040/113] [trac606] imported BIND 9's test README, which will be adjusted for our case. --- tests/system/README | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/system/README diff --git a/tests/system/README b/tests/system/README new file mode 100644 index 0000000000..9b8dbf6092 --- /dev/null +++ b/tests/system/README @@ -0,0 +1,61 @@ +Copyright (C) 2004, 2010, 2011 Internet Systems Consortium, Inc. ("ISC") +Copyright (C) 2000, 2001 Internet Software Consortium. +See COPYRIGHT in the source root or http://isc.org/copyright.html for terms. + +This is a simple test environment for running bind9 system tests +involving multiple name servers. + +There are multiple test suites, each in a separate subdirectory and +involving a different DNS setup. They are: + + dnssec/ DNSSEC tests + forward/ Forwarding tests + glue/ Glue handling tests + limits/ Tests of handling of large data (close to server limits) + lwresd/ Tests of the lightweight resolver library and daemon + notify/ More NOTIFY tests + nsupdate/ Dynamic update and IXFR tests + resolver/ Regression tests for resolver bugs that have been fixed + (not a complete resolver test suite) + rpz/ Tests of response policy zone (RPZ) rewriting + stub/ Tests of stub zone functionality + unknown/ Unknown type and class tests + upforwd/ Update forwarding tests + views/ Tests of the "views" statement + xfer/ Zone transfer tests + xferquota/ Zone transfer quota tests + +Typically each test suite sets up 2-5 name servers and then performs +one or more tests against them. Within the test suite subdirectory, +each name server has a separate subdirectory containing its +configuration data. By convention, these subdirectories are named +"ns1", "ns2", etc. + +The tests are completely self-contained and do not require access to +the real DNS. Generally, one of the test servers (ns1) is set up as a +root name server and is listed in the hints file of the others. + +To enable all servers to run on the same machine, they bind to +separate virtual IP address on the loopback interface. ns1 runs on +10.53.0.1, ns2 on 10.53.0.2, etc. Before running any tests, you must +set up these addresses by running "ifconfig.sh up" as root. + +Mac OS X: +If you wish to make the interfaces survive across reboots +copy org.isc.bind.system and org.isc.bind.system to +/Library/LaunchDaemons then run +"launchctl load /Library/LaunchDaemons/org.isc.bind.system.plist" as +root. + +The servers use port 5300 instead of the usual port 53, so they can be +run without root privileges once the interfaces have been set up. + +The tests can be run individually like this: + + sh run.sh xfer + sh run.sh notify + etc. + +To run all the tests, just type "make test". + +$Id: README,v 1.16 2011-01-13 04:59:24 tbox Exp $ From 19f9f10fa5594bfe311688823fba5dd149e76a59 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 20:05:34 -0800 Subject: [PATCH 041/113] [trac606] updated the README doc to reflect BIND 10 specific adjustments. --- tests/system/README | 61 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/tests/system/README b/tests/system/README index 9b8dbf6092..7da8e90765 100644 --- a/tests/system/README +++ b/tests/system/README @@ -2,43 +2,43 @@ Copyright (C) 2004, 2010, 2011 Internet Systems Consortium, Inc. ("ISC") Copyright (C) 2000, 2001 Internet Software Consortium. See COPYRIGHT in the source root or http://isc.org/copyright.html for terms. -This is a simple test environment for running bind9 system tests -involving multiple name servers. +This is a simple test environment for running BIND 10 system tests +involving multiple name servers. It was originally developed for BIND +9, and has been ported to test BIND 10 implementations. Ideally we +should share the same framework for both versions, so some part of +the original setup are kept, even though they are BIND 9 specific and +not currently used. + +Also, these tests generally rely on BIND 9 programs, most commonly its +dig, and will sometimes be its name server (named). So, the test +environment assumes that there's a source tree of BIND 9 where its +programs are built, and that an environment variable "BIND9_TOP" is +set to point to the top directory of the source tree. There are multiple test suites, each in a separate subdirectory and involving a different DNS setup. They are: - dnssec/ DNSSEC tests - forward/ Forwarding tests glue/ Glue handling tests - limits/ Tests of handling of large data (close to server limits) - lwresd/ Tests of the lightweight resolver library and daemon - notify/ More NOTIFY tests - nsupdate/ Dynamic update and IXFR tests - resolver/ Regression tests for resolver bugs that have been fixed - (not a complete resolver test suite) - rpz/ Tests of response policy zone (RPZ) rewriting - stub/ Tests of stub zone functionality - unknown/ Unknown type and class tests - upforwd/ Update forwarding tests - views/ Tests of the "views" statement +(the following test are planned to be added soon) + dnssec/ DNSSEC tests + masterfile/ Master file parser xfer/ Zone transfer tests - xferquota/ Zone transfer quota tests -Typically each test suite sets up 2-5 name servers and then performs -one or more tests against them. Within the test suite subdirectory, -each name server has a separate subdirectory containing its -configuration data. By convention, these subdirectories are named -"ns1", "ns2", etc. +Typically each test suite sets up 2-5 instances of BIND 10 (or BIND 9 +named) and then performs one or more tests against them. Within the +test suite subdirectory, each instance has a separate subdirectory +containing its configuration data. By convention, these +subdirectories are named "nsx1", "nsx2", etc for BIND 10 ("x" means +BIND 10), and "ns1", "ns2", etc. for BIND 9. The tests are completely self-contained and do not require access to -the real DNS. Generally, one of the test servers (ns1) is set up as a -root name server and is listed in the hints file of the others. +the real DNS. Generally, one of the test servers (ns[x]1) is set up +as a root name server and is listed in the hints file of the others. To enable all servers to run on the same machine, they bind to -separate virtual IP address on the loopback interface. ns1 runs on -10.53.0.1, ns2 on 10.53.0.2, etc. Before running any tests, you must -set up these addresses by running "ifconfig.sh up" as root. +separate virtual IP address on the loopback interface. ns[x]1 runs on +10.53.0.1, ns[x]2 on 10.53.0.2, etc. Before running any tests, you +must set up these addresses by running "ifconfig.sh up" as root. Mac OS X: If you wish to make the interfaces survive across reboots @@ -53,9 +53,10 @@ run without root privileges once the interfaces have been set up. The tests can be run individually like this: sh run.sh xfer - sh run.sh notify + sh run.sh glue etc. -To run all the tests, just type "make test". - -$Id: README,v 1.16 2011-01-13 04:59:24 tbox Exp $ +To run all the tests, just type "make test" either on this directory +or on the top source directory. Note: currently these tests cannot be +run when built under a separate build directory. Everything must be +run within the original source tree. From 8af2ccd45c7f070d2aa168763815fe8a82808e79 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 20:08:31 -0800 Subject: [PATCH 042/113] [trac606] renamed B10_FROM_SOURCE_CONFIG_DATA_PATH to B10_FROM_SOURCE_LOCALSTATEDIR so that we can use the same env variable for similar purposes, e.g. communication path betwen b10-auth and xfrout. --- src/bin/cfgmgr/b10-cfgmgr.py.in | 8 ++++---- tests/system/start.pl | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in index 169f12bdac..e37ec4871d 100755 --- a/src/bin/cfgmgr/b10-cfgmgr.py.in +++ b/src/bin/cfgmgr/b10-cfgmgr.py.in @@ -27,15 +27,15 @@ isc.util.process.rename() # If B10_FROM_SOURCE is set in the environment, we use data files # from a directory relative to the value of that variable, or, if defined, -# relative to the value of B10_FROM_SOURCE_CONFIG_DATA_PATH. Otherwise +# relative to the value of B10_FROM_SOURCE_LOCALSTATEDIR. Otherwise # we use the ones installed on the system. -# B10_FROM_SOURCE_CONFIG_DATA_PATH is specifically intended to be used for +# B10_FROM_SOURCE_LOCALSTATEDIR is specifically intended to be used for # tests where we want to use variuos types of configuration within the test # environment. (We may want to make it even more generic so that the path is # passed from the boss process) if "B10_FROM_SOURCE" in os.environ: - if "B10_FROM_SOURCE_CONFIG_DATA_PATH" in os.environ: - DATA_PATH = os.environ["B10_FROM_SOURCE_CONFIG_DATA_PATH"] + if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ: + DATA_PATH = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] else: DATA_PATH = os.environ["B10_FROM_SOURCE"] else: diff --git a/tests/system/start.pl b/tests/system/start.pl index c6284eea76..122c71d782 100755 --- a/tests/system/start.pl +++ b/tests/system/start.pl @@ -124,7 +124,7 @@ sub start_server { if ($server =~ /^nsx/) { $cleanup_files = "{bind10.run}"; - $command = "B10_FROM_SOURCE_CONFIG_DATA_PATH=$testdir/$server/ "; + $command = "B10_FROM_SOURCE_LOCALSTATEDIR=$testdir/$server/ "; $command .= "$RUN_BIND10 "; if ($options) { $command .= "$options"; From 333edc6244dafdab42258b84a46237c6dcf936a0 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 20:17:49 -0800 Subject: [PATCH 043/113] [trac606] use B10_FROM_SOURCE_LOCALSTATEDIR when defined for communication between auth and xfrout, so that we can run multiple instances of bind10 system (mainly for system purposes) --- src/bin/auth/main.cc | 8 +++++++- src/bin/xfrout/xfrout.py.in | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 275ae7dd8b..0701b94fdd 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -122,7 +122,13 @@ main(int argc, char* argv[]) { ModuleCCSession* config_session = NULL; string xfrout_socket_path; if (getenv("B10_FROM_BUILD") != NULL) { - xfrout_socket_path = string(getenv("B10_FROM_BUILD")) + "/auth_xfrout_conn"; + if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) { + xfrout_socket_path = string("B10_FROM_SOURCE_LOCALSTATEDIR") + + "/auth_xfrout_conn"; + } else { + xfrout_socket_path = string(getenv("B10_FROM_BUILD")) + + "/auth_xfrout_conn"; + } } else { xfrout_socket_path = UNIX_SOCKET_FILE; } diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in index fd1288dde4..f420d4b724 100755 --- a/src/bin/xfrout/xfrout.py.in +++ b/src/bin/xfrout/xfrout.py.in @@ -50,7 +50,11 @@ isc.util.process.rename() if "B10_FROM_BUILD" in os.environ: SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrout" AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth" - UNIX_SOCKET_FILE= os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn" + if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ: + UNIX_SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \ + "/auth_xfrout_conn" + else: + UNIX_SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn" else: PREFIX = "@prefix@" DATAROOTDIR = "@datarootdir@" From 6934390c92855d4006b727e1c8c1c89688afeb5f Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 20:24:02 -0800 Subject: [PATCH 044/113] [trac606] use a local dir for xfrout log to suppress warning in tests. --- tests/system/glue/nsx1/b10-config.db.in | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/system/glue/nsx1/b10-config.db.in b/tests/system/glue/nsx1/b10-config.db.in index eaecb12bd2..51f00f1313 100644 --- a/tests/system/glue/nsx1/b10-config.db.in +++ b/tests/system/glue/nsx1/b10-config.db.in @@ -2,5 +2,8 @@ "Auth": { "listen_on": [{"address": "10.53.0.1", "port": 5300}], "database_file": "@abs_builddir@/zone.sqlite3" + }, + "Xfrout": { + "log_file": "@abs_builddir@/Xfrout.log" } } From 26582526e20e56ea4547a447cfa93e122af0f0e1 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Thu, 3 Mar 2011 22:36:57 -0800 Subject: [PATCH 045/113] [trac606] editorial cleanup: removed white space after EOL --- src/bin/bind10/tests/bind10_test.py.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in index 6e0539d31c..30b9718a35 100644 --- a/src/bin/bind10/tests/bind10_test.py.in +++ b/src/bin/bind10/tests/bind10_test.py.in @@ -48,7 +48,7 @@ class TestProcessInfo(unittest.TestCase): # 'FOO': 'BAR' }) def test_setting_null_stdout(self): - pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ], + pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ], dev_null_stdout=True) os.dup2(self.old_stdout, sys.stdout.fileno()) self.assertEqual(pi.dev_null_stdout, True) From 2275e0d7afa15b69bd7511616a6ebae0b9de0d22 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Fri, 4 Mar 2011 10:01:30 +0100 Subject: [PATCH 046/113] Changelog for #523 --- ChangeLog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog b/ChangeLog index cd3db95868..695c78feee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ + 185. [bug] vorner + Tests use port from private range (53210), lowering chance of + a conflict with something else (eg. running bind 10). + (Trac #523, git 301da7d26d41e64d87c0cf72727f3347aa61fb40) + 184. [func]* vorner Listening address and port configuration of b10-auth is the same as for b10-resolver now. That means, it is configured through bindctl From 0ca10e6357d7a41bc308f90183848ce41582adf8 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Fri, 4 Mar 2011 10:24:18 +0100 Subject: [PATCH 047/113] [master] Update Changelog --- ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index 695c78feee..4ffd7575e3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ + 186. [bug] jelte + b10-resolver could stop with an assertion failure on certain kinds + of messages (there was a problem in error message creation). This + fixes that. + (Trac #607, git 25a5f4ec755bc09b54410fcdff22691283147f32) + 185. [bug] vorner Tests use port from private range (53210), lowering chance of a conflict with something else (eg. running bind 10). From fa83bb99518bd8c4dede832769fe4f7a6405ea62 Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Fri, 4 Mar 2011 19:57:22 +0800 Subject: [PATCH 048/113] [trac638] Fix the assert error by adding the check for empty pointer, also add test case for it. --- src/lib/cache/message_cache.h | 3 ++ src/lib/cache/message_entry.cc | 2 +- src/lib/cache/rrset_cache.h | 5 +-- src/lib/cache/tests/message_cache_unittest.cc | 31 +++++++++++++++++-- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/lib/cache/message_cache.h b/src/lib/cache/message_cache.h index ef0d16dc76..65c7381fd6 100644 --- a/src/lib/cache/message_cache.h +++ b/src/lib/cache/message_cache.h @@ -41,6 +41,9 @@ public: MessageCache(boost::shared_ptr rrset_cache_, uint32_t cache_size, uint16_t message_class); + /// \brief Destructor function + virtual ~MessageCache() {} + /// \brief Look up message in cache. /// \param message generated response message if the message entry /// can be found. diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc index d4de11f45a..f23b9d561b 100644 --- a/src/lib/cache/message_entry.cc +++ b/src/lib/cache/message_entry.cc @@ -48,7 +48,7 @@ MessageEntry::getRRsetEntries(vector& rrset_entry_vec, for (int index = 0; index < entry_count; ++index) { RRsetEntryPtr rrset_entry = rrset_cache_->lookup(rrsets_[index].name_, rrsets_[index].type_); - if (time_now < rrset_entry->getExpireTime()) { + if (rrset_entry && time_now < rrset_entry->getExpireTime()) { rrset_entry_vec.push_back(rrset_entry); } else { return (false); diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h index 9453f85115..5bf2730884 100644 --- a/src/lib/cache/rrset_cache.h +++ b/src/lib/cache/rrset_cache.h @@ -45,7 +45,7 @@ public: /// \param cache_size the size of rrset cache. /// \param rrset_class the class of rrset cache. RRsetCache(uint32_t cache_size, uint16_t rrset_class); - ~RRsetCache() {} + virtual ~RRsetCache() {} //@} /// \brief Look up rrset in cache. @@ -92,7 +92,8 @@ public: bool resize(uint32_t size); #endif -private: + /// \short Protected memebers, so they can be accessed by tests. +protected: uint16_t class_; // The class of the rrset cache. isc::nsas::HashTable rrset_table_; isc::nsas::LruList rrset_lru_; diff --git a/src/lib/cache/tests/message_cache_unittest.cc b/src/lib/cache/tests/message_cache_unittest.cc index e7184bdfee..b2c0cd37a3 100644 --- a/src/lib/cache/tests/message_cache_unittest.cc +++ b/src/lib/cache/tests/message_cache_unittest.cc @@ -43,20 +43,40 @@ public: } }; +/// \brief Derived from base class to make it easy to test +/// its internals. +class DerivedRRsetCache: public RRsetCache { +public: + DerivedRRsetCache(uint32_t cache_size, uint16_t rrset_class): + RRsetCache(cache_size, rrset_class) + {} + + /// \brief Remove one rrset entry from rrset cache. + void removeRRsetEntry(Name& name, const RRType& type) { + const string entry_name = genCacheEntryName(name, type); + HashKey entry_key = HashKey(entry_name, RRClass(class_)); + RRsetEntryPtr rrset_entry = rrset_table_.get(entry_key); + if (rrset_entry) { + rrset_lru_.remove(rrset_entry); + rrset_table_.remove(entry_key); + } + } +}; + class MessageCacheTest: public testing::Test { public: MessageCacheTest(): message_parse(Message::PARSE), message_render(Message::RENDER) { uint16_t class_ = RRClass::IN().getCode(); - rrset_cache_.reset(new RRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_)); - message_cache_.reset(new DerivedMessageCache(rrset_cache_, + rrset_cache_.reset(new DerivedRRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_)); + message_cache_.reset(new DerivedMessageCache(rrset_cache_, MESSAGE_CACHE_DEFAULT_SIZE, class_ )); } protected: boost::shared_ptr message_cache_; - RRsetCachePtr rrset_cache_; + boost::shared_ptr rrset_cache_; Message message_parse; Message message_render; }; @@ -75,6 +95,11 @@ TEST_F(MessageCacheTest, testLookup) { Name qname1("test.example.net."); EXPECT_TRUE(message_cache_->lookup(qname1, RRType::A(), message_render)); + + // Test looking up message which has expired rrsets. + // Remove one + rrset_cache_->removeRRsetEntry(qname1, RRType::A()); + EXPECT_FALSE(message_cache_->lookup(qname1, RRType::A(), message_render)); } TEST_F(MessageCacheTest, testUpdate) { From 6c1f3af003f02eee8952da8463d05732e667c986 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Fri, 4 Mar 2011 14:58:53 +0000 Subject: [PATCH 049/113] [trac499] Correct problem with receiving large responses The TCP handling reads into an intermediate staging buffer. The problem was that this buffer wasn't being emptied between multiple reads, only after the last one. If the total amount of data received was more than the staging buffer size, the code failed attempting to write beyond the buffer end. This fix empties the staging buffer after (more or less) every read from the network. --- src/lib/asiolink/io_asio_socket.h | 106 ++++++--- src/lib/asiolink/io_fetch.cc | 40 ++-- src/lib/asiolink/tcp_socket.h | 112 +++++---- src/lib/asiolink/tests/io_fetch_unittest.cc | 135 ++++++++--- src/lib/asiolink/tests/tcp_socket_unittest.cc | 221 +++++++++--------- src/lib/asiolink/tests/udp_socket_unittest.cc | 82 ++++--- src/lib/asiolink/udp_socket.h | 63 ++--- 7 files changed, 451 insertions(+), 308 deletions(-) diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index c8fea049bb..0fadca2148 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -203,43 +203,74 @@ public: /// /// \param data Buffer to receive incoming message /// \param length Length of the data buffer - /// \param offset Offset into buffer where data is to be put + /// \param offset Offset into buffer where data is to be put. Although the + /// offset could be implied by adjusting "data" and "length" + /// appropriately, using this argument allows data to be specified as + /// "const void*" - the overhead of converting it to a pointer to a + /// set of bytes is hidden away here. /// \param endpoint Source of the communication /// \param callback Callback object virtual void asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback) = 0; - /// \brief Checks if the data received is complete. + /// \brief Processes received data /// - /// 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 checks if the message is complete. + /// In the IOFetch code, data is received into a staging buffer before being + /// copied into the target buffer. (This is because (a) we don't know how + /// much data we will be receiving, so don't know how to size the output + /// buffer and (b) TCP data is preceded by a two-byte count field that needs + /// to be discarded before being returned to the user.) /// - /// For a UDP receive, all the data is received in one I/O, so this is - /// effectively a no-op). + /// An additional consideration is that TCP data is not received in one + /// I/O - it may take a number of I/Os - each receiving any non-zero number + /// of bytes - to read the entire message. /// - /// \param data Data buffer containing data to date - /// \param length Total amount of data in the buffer. + /// So the IOFetch code has to loop until it determines that all the data + /// has been read. This is where this method comes in. It has several + /// functions: + /// + /// - It checks if the received data is complete. + /// - If data is not complete, decides if the next set of data is to go into + /// the start of the staging buffer or at some offset into it. (This + /// simplifies the case we could have in a TCP receive where the two-byte + /// count field is received in one-byte chunks: we put off interpreting + /// the count until we have all of it. The alternative - copying the + /// data to the output buffer and interpreting the count from there - + /// would require moving the data in the output buffer by two bytes before + /// returning it to the caller.) + /// - Copies data from the staging buffer into the output buffer. + /// + /// This functionality mainly applies to TCP receives. For UDP, all the + /// data is received in one I/O, so this just copies the data into the + /// output buffer. + /// + /// \param staging Pointer to the start of the staging buffer. + /// \param length Amount of data in the staging buffer. + /// \param cumulative Amount of data received before the staging buffer is + /// processed (this includes the TCP count field if appropriate). + /// The value should be set to zero before the receive loop is + /// entered, and it will be updated by this method as required. + /// \param offset Offset into the staging buffer where the next read should + /// put the received data. It should be set to zero before the first + /// call and may be updated by this method. + /// \param expected Expected amount of data to be received. This is + /// really the TCP count field and is set to that value when enough + /// of a TCP message is received. It should be initialized to -1 + /// before the first read is executed. + /// \param outbuff Output buffer. Data in the staging buffer may be copied + /// to this output buffer in the call. /// /// \return true if the receive is complete, false if another receive is - /// needed. - virtual bool receiveComplete(const void* data, size_t length) = 0; - - /// \brief Append Normalized Data - /// - /// When a UDP buffer is received, the entire buffer contains the data. - /// When a TCP buffer is received, the first two bytes of the buffer hold - /// a length count. This method removes those bytes from the buffer. - /// - /// \param inbuf Input buffer. This contains the data received over the - /// network connection. - /// \param length Amount of data in the input buffer. If TCP, this includes - /// the two-byte count field. - /// \param outbuf Pointer to output buffer to which the data will be - /// appended. - virtual void appendNormalizedData(const void* inbuf, size_t length, - isc::dns::OutputBufferPtr outbuf) = 0; + /// needed. This is always true for UDP, but for TCP involves + /// checking the amount of data received so far against the amount + /// expected (as indicated by the two-byte count field). If this + /// method returns false, another read should be queued and data + /// should be read into the staging buffer at offset given by the + /// "offset" parameter. + virtual bool processReceivedData(const void* staging, size_t length, + size_t& cumulative, size_t& offset, + size_t& expected, + isc::dns::OutputBufferPtr& outbuff) = 0; /// \brief Cancel I/O On AsioSocket virtual void cancel() = 0; @@ -330,23 +361,22 @@ public: /// \brief Checks if the data received is complete. /// - /// \param data Unused + /// \param staging Unused /// \param length Unused /// \param cumulative Unused + /// \param offset Unused. + /// \param expected Unused. + /// \param outbuff Unused. /// /// \return Always true - virtual bool receiveComplete(void*, size_t, size_t&) { + virtual bool receiveComplete(const void* staging, size_t length, + size_t& cumulative, size_t& offset, + size_t& expected, + isc::dns::OutputBufferPtr& outbuff) + { return (true); } - /// \brief Append Normalized Data - /// - /// \param inbuf Unused. - /// \param length Unused. - /// \param outbuf unused. - virtual void appendNormalizedData(const void*, size_t, - isc::dns::OutputBufferPtr) - { - } + /// \brief Cancel I/O On AsioSocket /// diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 717d3d92f4..8b7efabbb9 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -78,6 +78,8 @@ struct IOFetchData { asio::deadline_timer timer; ///< Timer to measure timeouts IOFetch::Protocol protocol; ///< Protocol being used size_t cumulative; ///< Cumulative received amount + size_t expected; ///< Expected amount of data + size_t offset; ///< Offset to receive data bool stopped; ///< Have we stopped running? int timeout; ///< Timeout in ms @@ -129,6 +131,8 @@ struct IOFetchData { timer(service.get_io_service()), protocol(proto), cumulative(0), + expected(0), + offset(0), stopped(false), timeout(wait), origin(ASIO_UNKORIGIN) @@ -182,9 +186,6 @@ IOFetch::operator()(asio::error_code ec, size_t length) { msg.addQuestion(data_->question); MessageRenderer renderer(*data_->msgbuf); msg.toWire(renderer); - - // As this is a new fetch, clear the amount of data received - data_->cumulative = 0; } // If we timeout, we stop, which will can cancel outstanding I/Os and @@ -218,22 +219,29 @@ IOFetch::operator()(asio::error_code ec, size_t length) { // 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. + // + // Another concession to TCP is that the amount of is contained in the + // first two bytes. This leads to two problems: + // + // a) We don't want those bytes in the return buffer. + // b) They may not both arrive in the first I/O. + // + // So... we need to loop until we have at least two bytes, then store + // the expected amount of data. Then we need to loop until we have + // received all the data before copying it back to the user's buffer. + // And we want to minimise the amount of copying... + data_->origin = ASIO_RECVSOCK; + data_->cumulative = 0; // No data yet received + data_->offset = 0; // First data into start of buffer do { CORO_YIELD data_->socket->asyncReceive(data_->staging.get(), - static_cast(MIN_LENGTH), data_->cumulative, - data_->remote.get(), *this); - data_->cumulative += length; - } while (!data_->socket->receiveComplete(data_->staging.get(), - data_->cumulative)); - - /// Copy the answer into the response buffer. (TODO: If the - /// OutputBuffer object were made to meet the requirements of a - /// MutableBufferSequence, then it could be written to directly by - /// async_receive_from() and this additional copy step would be - /// unnecessary.) - data_->socket->appendNormalizedData(data_->staging.get(), - data_->cumulative, data_->received); + static_cast(MIN_LENGTH), + data_->offset, + data_->remote.get(), *this); + } while (!data_->socket->processReceivedData(data_->staging.get(), length, + data_->cumulative, data_->offset, + data_->expected, data_->received)); // Finished with this socket, so close it. This will not generate an // I/O error, but reset the origin to unknown in case we change this. diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index 766df9934e..d49df460f9 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -24,6 +24,8 @@ #include #include // for some IPC/network system calls +#include +#include #include #include @@ -135,32 +137,25 @@ public: virtual void asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback); - /// \brief Checks if the data received is complete. + /// \brief Process received data packet /// - /// Checks if all the data has been received by checking that the amount - /// of data received is equal to the number in the first two bytes of the - /// message plus two (for the count field itself). + /// See the description of IOAsioSocket::receiveComplete for a complete + /// description of this method. /// - /// \param data Data buffer containing data to date (ignored) - /// \param length Amount of data in the buffer. + /// \param staging Pointer to the start of the staging buffer. + /// \param length Amount of data in the staging buffer. + /// \param cumulative Amount of data received before the staging buffer is + /// processed. + /// \param offset Unused. + /// \param expected unused. + /// \param outbuff Output buffer. Data in the staging buffer is be copied + /// to this output buffer in the call. /// - /// \return true if the receive is complete, false if not. - virtual bool receiveComplete(const void* data, size_t length); - - /// \brief Append Normalized Data - /// - /// When a UDP buffer is received, the entire buffer contains the data. - /// When a TCP buffer is received, the first two bytes of the buffer hold - /// a length count. This method removes those bytes from the buffer. - /// - /// \param inbuf Input buffer. This contains the data received over the - /// network connection. - /// \param length Amount of data in the input buffer. If TCP, this includes - /// the two-byte count field. - /// \param outbuf Pointer to output buffer to which the data will be - /// appended - virtual void appendNormalizedData(const void* inbuf, size_t length, - isc::dns::OutputBufferPtr outbuf); + /// \return Always true + virtual bool processReceivedData(const void* staging, size_t length, + size_t& cumulative, size_t& offset, + size_t& expected, + isc::dns::OutputBufferPtr& outbuff); /// \brief Cancel I/O On Socket virtual void cancel(); @@ -335,32 +330,65 @@ TCPSocket::asyncReceive(void* data, size_t length, size_t offset, // Is the receive complete? template bool -TCPSocket::receiveComplete(const void* data, size_t length) { +TCPSocket::processReceivedData(const void* staging, size_t length, + size_t& cumulative, size_t& offset, + size_t& expected, + isc::dns::OutputBufferPtr& outbuff) +{ + // Point to the data in the staging buffer and note how much there is. + const uint8_t* data = static_cast(staging); + size_t data_length = length; - bool complete = false; + // Is the number is "expected" valid? It won't be unless we have received + // at least two bytes of data in total for this set of receives. + if (cumulative < 2) { - // If we have read at least two bytes, we can work out how much we should be - // reading. - if (length >= 2) { + // "expected" is not valid. Did this read give us enough data to + // work it out? + cumulative += length; + if (cumulative < 2) { - // Convert first two bytes to a count and check that the following data - // is that length. - // TODO: Should we check to see if we have received too much data? - uint16_t expected = readUint16(data); - complete = ((expected + 2) == length); + // Nope, still not valid. This must have been the first packet and + // was only one byte long. Tell the fetch code to read the next + // packet into the staging buffer beyond the data that is already + // there so that the next time we are called we have a complete + // TCP count. + offset = cumulative; + return (false); + } + + // Have enough data to interpret the packet count, so do so now. + expected = readUint16(data); + + // We have two bytes less of data to process. Point to the start of the + // data and adjust the packet size. Note that at this point, + // "cumulative" is the true amount of data in the staging buffer, not + // "length". + data += 2; + data_length = cumulative - 2; + } else { + + // Update total amount of data received. + cumulative += length; } - return (complete); -} + // Regardless of anything else, the next read goes into the start of the + // staging buffer. + offset = 0; -// Copy buffer less leading two bytes to the target buffer. + // Work out how much data we still have to put in the output buffer. (This + // could be zero if we have just interpreted the TCP count and that was + // set to zero.) + if (expected >= outbuff->getLength()) { -template void -TCPSocket::appendNormalizedData(const void* inbuf, size_t length, - isc::dns::OutputBufferPtr outbuf) -{ - const uint8_t* bytebuff = static_cast(inbuf); - outbuf->writeData(bytebuff + 2, length - 2); + // Still need data in the output packet. Copy what we can from the + // staging buffer to the output buffer. + size_t copy_amount = std::min(expected - outbuff->getLength(), data_length); + outbuff->writeData(data, copy_amount); + } + + // We can now say if we have all the data. + return (expected == outbuff->getLength()); } // Cancel I/O on the socket. No-op if the socket is not open. diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index fc845e1317..df9421782e 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include #include #include @@ -48,7 +51,11 @@ 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 response from server to client (longer than 30 bytes)"; -const int SEND_INTERVAL = 500; // Interval in ms between TCP sends +const int SEND_INTERVAL = 250; // Interval in ms between TCP sends + +// The tests are complex, so debug output has been left in (although disabled). +// Set this to true to enable it. +const bool DEBUG = false; /// \brief Test fixture for the asiolink::IOFetch. class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback @@ -70,10 +77,13 @@ public: // response handler methods in this class) receives the question sent by the // fetch object. uint8_t receive_buffer_[512]; ///< Server receive buffer - uint8_t send_buffer_[512]; ///< Server send buffer - uint16_t send_size_; ///< Amount of data to sent + vector send_buffer_; ///< Server send buffer uint16_t send_cumulative_; ///< Data sent so far + // Other data. + string return_data_; ///< Data returned by server + bool debug_; ///< true to enable debug output + /// \brief Constructor IOFetchTest() : service_(), @@ -92,8 +102,9 @@ public: timer_(service_.get_io_service()), receive_buffer_(), send_buffer_(), - send_size_(0), - send_cumulative_(0) + send_cumulative_(0), + return_data_(TEST_DATA), + debug_(DEBUG) { // Construct the data buffer for question we expect to receive. Message msg(Message::RENDER); @@ -119,6 +130,10 @@ public: /// \param length Amount of data received. void udpReceiveHandler(udp::endpoint* remote, udp::socket* socket, error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "udpReceiveHandler(): error = " << ec.value() << + ", length = " << length << endl; + } // The QID in the incoming data is random so set it to 0 for the // data comparison check. (It is set to 0 in the buffer containing @@ -132,7 +147,8 @@ public: static_cast(msgbuf_->getData()))); // Return a message back to the IOFetch object. - socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA), *remote); + socket->send_to(asio::buffer(return_data_.c_str(), return_data_.size()), + *remote); } /// \brief Completion Handler for accepting TCP data @@ -144,6 +160,10 @@ public: /// \param ec Boost error code, value should be zero. void tcpAcceptHandler(tcp::socket* socket, error_code ec = error_code()) { + if (debug_) { + cout << "tcpAcceptHandler(): error = " << ec.value() << endl; + } + // Expect that the accept completed without a problem. EXPECT_EQ(0, ec.value()); @@ -167,6 +187,10 @@ public: void tcpReceiveHandler(tcp::socket* socket, error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "tcpReceiveHandler(): error = " << ec.value() << + ", length = " << length << endl; + } // Expect that the receive completed without a problem. EXPECT_EQ(0, ec.value()); @@ -196,11 +220,12 @@ public: static_cast(msgbuf_->getData()))); // ... and return a message back. This has to be preceded by a two-byte - // count field. Construct the message. - assert(sizeof(send_buffer_) > (sizeof(TEST_DATA) + 2)); - writeUint16(sizeof(TEST_DATA), send_buffer_); - copy(TEST_DATA, TEST_DATA + sizeof(TEST_DATA) - 1, send_buffer_ + 2); - send_size_ = sizeof(TEST_DATA) + 2; + // count field. + send_buffer_.clear(); + send_buffer_.push_back(0); + send_buffer_.push_back(0); + writeUint16(return_data_.size(), &send_buffer_[0]); + copy(return_data_.begin(), return_data_.end(), back_inserter(send_buffer_)); // Send the data. This is done in multiple writes with a delay between // each to check that the reassembly of TCP packets from fragments works. @@ -216,12 +241,16 @@ public: /// /// \param socket Socket over which send should take place void tcpSendData(tcp::socket* socket) { + if (debug_) { + cout << "tcpSendData()" << endl; + } + // Decide what to send based on the cumulative count uint8_t* send_ptr = &send_buffer_[send_cumulative_]; // Pointer to data to send size_t amount = 16; // Amount of data to send if (send_cumulative_ > 30) { - amount = send_size_ - send_cumulative_; + amount = send_buffer_.size() - send_cumulative_; } // ... and send it. The amount sent is also passed as the first argument @@ -252,12 +281,17 @@ public: void tcpSendHandler(size_t expected, tcp::socket* socket, error_code ec = error_code(), size_t length = 0) { + if (debug_) { + cout << "tcpSendHandler(): error = " << ec.value() << + ", length = " << length << endl; + } + EXPECT_EQ(0, ec.value()); // Expect no error EXPECT_EQ(expected, length); // And that amount sent is as expected // Do we need to send more? send_cumulative_ += length; - if (send_cumulative_ < send_size_) { + if (send_cumulative_ < send_buffer_.size()) { // Yes - set up a timer: the callback handler for the timer is // tcpSendData, which will then send the next chunk. We pass the @@ -277,20 +311,22 @@ public: /// /// \param result Result indicated by the callback void operator()(IOFetch::Result result) { + if (debug_) { + cout << "operator()(): result = " << result << endl; + } EXPECT_EQ(expected_, result); // Check correct result returned EXPECT_FALSE(run_); // Check it is run only once run_ = true; // Note success // If the expected result for SUCCESS, then this should have been called - // when one of the "servers" in this class has sent back the TEST_DATA. + // when one of the "servers" in this class has sent back return_data_. // Check the data is as expected/ if (expected_ == IOFetch::SUCCESS) { - EXPECT_EQ(sizeof(TEST_DATA), result_buff_->getLength()); + EXPECT_EQ(return_data_.size(), result_buff_->getLength()); const uint8_t* start = static_cast(result_buff_->getData()); - EXPECT_TRUE(equal(TEST_DATA, (TEST_DATA + sizeof(TEST_DATA) - 1), - start)); + EXPECT_TRUE(equal(return_data_.begin(), return_data_.end(), start)); } // ... and cause the run loop to exit. @@ -360,6 +396,38 @@ public: service_.run(); EXPECT_TRUE(run_); } + + /// \brief Send/Receive Test + /// + /// Send a query to the server then receives a response. + /// + /// \param Test data to return to client + void tcpSendReturnTest(const std::string& return_data) { + return_data_ = return_data; + protocol_ = IOFetch::TCP; + expected_ = IOFetch::SUCCESS; + + // Socket into which the connection will be accepted + tcp::socket socket(service_.get_io_service()); + + // Acceptor object - called when the connection is made, the handler + // will initiate a read on the socket. + tcp::acceptor acceptor(service_.get_io_service(), + tcp::endpoint(tcp::v4(), TEST_PORT)); + acceptor.async_accept(socket, + boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); + + // Post the TCP fetch object to send the query and receive the response. + service_.get_io_service().post(tcp_fetch_); + + // ... and execute all the callbacks. This exits when the fetch + // completes. + service_.run(); + EXPECT_TRUE(run_); // Make sure the callback did execute + + // Tidy up + socket.close(); + } }; // Check the protocol @@ -421,28 +489,27 @@ TEST_F(IOFetchTest, TcpTimeout) { timeoutTest(IOFetch::TCP, tcp_fetch_); } -TEST_F(IOFetchTest, TcpSendReceive) { - protocol_ = IOFetch::TCP; - expected_ = IOFetch::SUCCESS; +// Do a send and return with a small amount of data - // Socket into which the connection will be accepted - tcp::socket socket(service_.get_io_service()); +TEST_F(IOFetchTest, TcpSendReceiveShort) { + tcpSendReturnTest(TEST_DATA); +} - // Acceptor object - called when the connection is made, the handler will - // initiate a read on the socket. - tcp::acceptor acceptor(service_.get_io_service(), - tcp::endpoint(tcp::v4(), TEST_PORT)); - acceptor.async_accept(socket, - boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1)); +// Now with at least 16kB of data. - // Post the TCP fetch object to send the query and receive the response. - service_.get_io_service().post(tcp_fetch_); +TEST_F(IOFetchTest, TcpSendReceiveLong) { + const size_t REQUIRED_SIZE = 16 * 1024; - // ... and execute all the callbacks. This exits when the fetch completes. - service_.run(); - EXPECT_TRUE(run_); // Make sure the callback did execute + // We could initialize the string with a repeat of a single character, but + // we choose to enure that there are different characters as an added test. + string data; + data.reserve(REQUIRED_SIZE); + while (data.size() < REQUIRED_SIZE) { + data += "An abritrary message that is returned to the IOFetch object!"; + } - socket.close(); + // ... and do the test with this data. + tcpSendReturnTest(data); } } // namespace asiolink diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc index e59eafc140..cbaa2093df 100644 --- a/src/lib/asiolink/tests/tcp_socket_unittest.cc +++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc @@ -35,6 +35,8 @@ #include #include +#include + #include #include @@ -45,6 +47,7 @@ using namespace asio; using namespace asio::ip; using namespace asiolink; +using namespace isc::dns; using namespace std; namespace { @@ -78,17 +81,20 @@ public: struct PrivateData { PrivateData() : - error_code_(), length_(0), cumulative_(0), name_(""), - queued_(NONE), called_(NONE) + error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0), + name_(""), queued_(NONE), called_(NONE) {} asio::error_code error_code_; ///< Completion error code - size_t length_; ///< Bytes transfreed in this I/O + size_t length_; ///< Bytes transferred in this I/O size_t cumulative_; ///< Cumulative bytes transferred + size_t expected_; ///< Expected amount of data + size_t offset_; ///< Where to put data in buffer std::string name_; ///< Which of the objects this is - uint8_t data_[MIN_SIZE]; ///< Receive buffer Operation queued_; ///< Queued operation Operation called_; ///< Which callback called + uint8_t data_[MIN_SIZE]; ///< Receive buffer + }; /// \brief Constructor @@ -151,9 +157,17 @@ public: return (ptr_->cumulative_); } - /// \brief Access Data Member - /// - /// \param Reference to the data member + /// \brief Get expected amount of data + size_t& expected() { + return (ptr_->expected_); + } + + /// \brief Get offset intodData + size_t& offset() { + return (ptr_->offset_); + } + + /// \brief Get data member uint8_t* data() { return (ptr_->data_); } @@ -194,9 +208,6 @@ private: void serverRead(tcp::socket& socket, TCPCallback& server_cb) { - // Until we read something, the read is not complete. - bool complete = false; - // As we may need to read multiple times, keep a count of the cumulative // amount of data read and do successive reads into the appropriate part // of the buffer. @@ -205,7 +216,8 @@ serverRead(tcp::socket& socket, TCPCallback& server_cb) { // program and we have sized the buffer to be large enough for the test. server_cb.cumulative() = 0; - while (! complete) { + bool complete = false; + while (!complete) { // Read block of data and update cumulative amount of data received. server_cb.length() = socket.receive( @@ -216,8 +228,8 @@ serverRead(tcp::socket& socket, TCPCallback& server_cb) { // If we have read at least two bytes, we can work out how much we // should be reading. if (server_cb.cumulative() >= 2) { - uint16_t expected = readUint16(server_cb.data()); - if ((expected + 2) == server_cb.cumulative()) { + server_cb.expected() = readUint16(server_cb.data()); + if ((server_cb.expected() + 2) == server_cb.cumulative()) { // Amount of data read from socket equals the size of the // message (as indicated in the first two bytes of the message) @@ -229,101 +241,76 @@ serverRead(tcp::socket& socket, TCPCallback& server_cb) { } } -// Client read complete? -// -// This function is called when it appears that a client callback has been -// executed as the result of a read. It checks to see if all the data has been -// read and, if not, queues another asynchronous read. -// -// "All the data read" means that the client has received a message that is -// preceded by a two-byte count field and that the total amount of data received -// from the remote end is equal to the value in the count field plus two bytes -// for the count field itself. -// -// \param client TCPSocket object representing the client (i.e. the object -// under test). -// \param client_cb TCPCallback object holding information about the client. -// \param client_remote_endpoint Needed for the call to the client's asyncRead() -// method (but otherwise unused). -// -// \return true if the read is complete, false if not. -bool -clientReadComplete(TCPSocket& client, TCPCallback& client_cb, - TCPEndpoint& client_remote_endpoint) -{ - // Assume that all the data has not been read. - bool complete = false; - - // Check that the callback has in fact completed. - EXPECT_EQ(TCPCallback::READ, client_cb.called()); - EXPECT_EQ(0, client_cb.getCode()); - - // Update length of data received. - client_cb.cumulative() += client_cb.length(); - - // If the data is not complete, queue another read. - if (!client.receiveComplete(client_cb.data(), client_cb.cumulative())) { - client_cb.called() = TCPCallback::NONE; - client_cb.queued() = TCPCallback::READ; - client_cb.length() = 0; - client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE , - client_cb.cumulative(), &client_remote_endpoint, - client_cb); - } - - return (complete); -} - - // Receive complete method should return true only if the count in the first // two bytes is equal to the size of the rest if the buffer. -TEST(TCPSocket, receiveComplete) { +TEST(TCPSocket, processReceivedData) { + const uint16_t PACKET_SIZE = 16382; // Amount of "real" data in the buffer + IOService service; // Used to instantiate socket TCPSocket test(service); // Socket under test - uint8_t buffer[32]; // Buffer to check + uint8_t inbuff[PACKET_SIZE + 2]; // Buffer to check + OutputBufferPtr outbuff(new OutputBuffer(16)); + // Where data is put + size_t expected; // Expected amount of data + size_t offset; // Where to put next data + size_t cumulative; // Cumulative data received - // Expect that the value is true whatever number is written in the first - // two bytes of the buffer. - uint16_t count = 0; - for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { - writeUint16(count, buffer); - if (count == (sizeof(buffer) - 2)) { - EXPECT_TRUE(test.receiveComplete(buffer, sizeof(buffer))); - } else { - EXPECT_FALSE(test.receiveComplete(buffer, sizeof(buffer))); - } - } -} - -// Check that the normalized data copy only copies all but the first two bytes -// of the buffer (whatever the count). - -TEST(TCPSocket, appendNormalizedData) { - IOService service; // Used to instantiate socket - TCPSocket test(service); // Socket under test - uint8_t inbuff[32]; // Buffer to check - isc::dns::OutputBufferPtr outbuff(new isc::dns::OutputBuffer(sizeof(inbuff))); - // Where data is written - - // Initialize the input buffer with data. - for (uint8_t i = 0; i < sizeof(inbuff); ++i) { - inbuff[i] = i + 1; // An arbitrary number + // Set some dummy values in the buffer to check + for (size_t i = 0; i < sizeof(inbuff); ++i) { + inbuff[i] = i % 256; } - // Loop to ensure that entire buffer is copied on all count values, no - // matter what. - uint16_t count = 0; - for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { - writeUint16(count, inbuff); - outbuff->clear(); - test.appendNormalizedData(inbuff, sizeof(inbuff), outbuff); + // Check that the method will handle various receive sizes. + writeUint16(PACKET_SIZE, inbuff); - EXPECT_EQ((sizeof(inbuff) - 2), outbuff->getLength()); + cumulative = 0; + offset = 0; + expected = 0; + outbuff->clear(); + bool complete = test.processReceivedData(inbuff, 1, cumulative, offset, + expected, outbuff); + EXPECT_FALSE(complete); + EXPECT_EQ(1, cumulative); + EXPECT_EQ(1, offset); + EXPECT_EQ(0, expected); + EXPECT_EQ(0, outbuff->getLength()); - const uint8_t* outptr = static_cast(outbuff->getData()); - EXPECT_TRUE(equal(&inbuff[2], &inbuff[sizeof(inbuff) - 1], outptr)); - } + // Now pretend that we've received one more byte. + complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected, + outbuff); + EXPECT_FALSE(complete); + EXPECT_EQ(2, cumulative); + EXPECT_EQ(0, offset); + EXPECT_EQ(PACKET_SIZE, expected); + EXPECT_EQ(0, outbuff->getLength()); + + // Add another two bytes. However, this time note that we have to offset + // in the input buffer because it is expected that the next chunk of data + // from the connection will be read into the start of the buffer. + complete = test.processReceivedData(inbuff + cumulative, 2, cumulative, + offset, expected, outbuff); + EXPECT_FALSE(complete); + EXPECT_EQ(4, cumulative); + EXPECT_EQ(0, offset); + EXPECT_EQ(PACKET_SIZE, expected); + EXPECT_EQ(2, outbuff->getLength()); + + const uint8_t* dataptr = static_cast(outbuff->getData()); + EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr)); + + // And add the remaining data. Remember that "inbuff" is "PACKET_SIZE + 2" + // long. + complete = test.processReceivedData(inbuff + cumulative, + PACKET_SIZE + 2 - cumulative, + cumulative, offset, expected, outbuff); + EXPECT_TRUE(complete); + EXPECT_EQ(PACKET_SIZE + 2, cumulative); + EXPECT_EQ(0, offset); + EXPECT_EQ(PACKET_SIZE, expected); + EXPECT_EQ(PACKET_SIZE, outbuff->getLength()); + dataptr = static_cast(outbuff->getData()); + EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr)); } // TODO: Need to add a test to check the cancel() method @@ -340,6 +327,8 @@ TEST(TCPSocket, SequenceTest) { TCPSocket client(service);// Socket under test TCPCallback client_cb("Client"); // Async I/O callback function TCPEndpoint client_remote_endpoint; // Where client receives message from + OutputBufferPtr client_buffer(new OutputBuffer(128)); + // Received data is put here // The server - with which the client communicates. IOAddress server_address(SERVER_ADDRESS); @@ -431,8 +420,11 @@ TEST(TCPSocket, SequenceTest) { client_cb.queued() = TCPCallback::READ; client_cb.length() = 0; client_cb.cumulative() = 0; - client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE , - client_cb.cumulative(), &client_remote_endpoint, + client_cb.expected() = 0; + client_cb.offset() = 0; + + client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE, + client_cb.offset(), &client_remote_endpoint, client_cb); // Run the callbacks. Several options are possible depending on how ASIO @@ -476,16 +468,14 @@ TEST(TCPSocket, SequenceTest) { EXPECT_EQ(TCPCallback::READ, client_cb.called()); EXPECT_EQ(0, client_cb.getCode()); - // Update length of data received. - client_cb.cumulative() += client_cb.length(); - if (client_cb.cumulative() > 2) { - - // Have at least the message length field, check if we have the - // entire message. (If we don't have the length field, the data - // is not complete.) - client_complete = ((readUint16(client_cb.data()) + 2) == - client_cb.cumulative()); - } + // Check if we need to queue another read, copying the data into + // the output buffer as we do so. + client_complete = client.processReceivedData(client_cb.data(), + client_cb.length(), + client_cb.cumulative(), + client_cb.offset(), + client_cb.expected(), + client_buffer); // If the data is not complete, queue another read. if (! client_complete) { @@ -493,19 +483,20 @@ TEST(TCPSocket, SequenceTest) { client_cb.queued() = TCPCallback::READ; client_cb.length() = 0; client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE , - client_cb.cumulative(), &client_remote_endpoint, + client_cb.offset(), &client_remote_endpoint, client_cb); } } } - // Both the send and the receive have comnpleted. Check that the received + // Both the send and the receive have completed. Check that the received // is what was sent. // Check the client state EXPECT_EQ(TCPCallback::READ, client_cb.called()); EXPECT_EQ(0, client_cb.getCode()); EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative()); + EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength()); // ... and check what the server sent. EXPECT_EQ(TCPCallback::WRITE, server_cb.called()); @@ -513,9 +504,9 @@ TEST(TCPSocket, SequenceTest) { EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length()); // ... and that what was sent is what was received. - EXPECT_TRUE(equal(INBOUND_DATA, - (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)), - (client_cb.data() + 2))); + const uint8_t* received = static_cast(client_buffer->getData()); + EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)), + received)); // Close client and server. EXPECT_NO_THROW(client.close()); diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc index 2ee1080e10..4243b419a9 100644 --- a/src/lib/asiolink/tests/udp_socket_unittest.cc +++ b/src/lib/asiolink/tests/udp_socket_unittest.cc @@ -46,6 +46,7 @@ using namespace asio; using namespace asiolink; +using namespace isc::dns; using namespace std; namespace { @@ -168,47 +169,43 @@ private: // Receive complete method should return true regardless of what is in the first // two bytes of a buffer. -TEST(UDPSocket, receiveComplete) { +TEST(UDPSocket, processReceivedData) { IOService service; // Used to instantiate socket UDPSocket test(service); // Socket under test - uint8_t buffer[32]; // Buffer to check + uint8_t inbuff[32]; // Buffer to check + OutputBufferPtr outbuff(new OutputBuffer(16)); + // Where data is put + size_t expected; // Expected amount of data + size_t offset; // Where to put next data + size_t cumulative; // Cumulative data received + + // Set some dummy values in the buffer to check + for (uint8_t i = 0; i < sizeof(inbuff); ++i) { + inbuff[i] = i; + } // Expect that the value is true whatever number is written in the first // two bytes of the buffer. uint16_t count = 0; - for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { - writeUint16(count, buffer); - EXPECT_TRUE(test.receiveComplete(buffer, sizeof(buffer))); - } -} - -// Check that the normalized data copy copies the entire buffer regardless of -// the first two bytes. - -TEST(UDPSocket, appendNormalizedData) { - IOService service; // Used to instantiate socket - UDPSocket test(service); // Socket under test - uint8_t inbuff[32]; // Buffer to check - isc::dns::OutputBufferPtr outbuff(new isc::dns::OutputBuffer(sizeof(inbuff))); - // Where data is written - - // Initialize the input buffer with data. - for (uint8_t i = 0; i < sizeof(inbuff); ++i) { - inbuff[i] = i + 1; // An arbitrary number - } - - // Loop to ensure that entire buffer is copied on all count values, no - // matter what. - uint16_t count = 0; for (uint32_t i = 0; i < (2 << 16); ++i, ++count) { writeUint16(count, inbuff); + + // Set some random values + cumulative = 5; + offset = 10; + expected = 15; outbuff->clear(); - test.appendNormalizedData(inbuff, sizeof(inbuff), outbuff); - EXPECT_EQ(sizeof(inbuff), outbuff->getLength()); + bool completed = test.processReceivedData(inbuff, sizeof(inbuff), + cumulative, offset, expected, + outbuff); + EXPECT_TRUE(completed); + EXPECT_EQ(sizeof(inbuff), cumulative); + EXPECT_EQ(0, offset); + EXPECT_EQ(sizeof(inbuff), expected); - const uint8_t* outptr = static_cast(outbuff->getData()); - EXPECT_TRUE(equal(&inbuff[0], &inbuff[sizeof(inbuff) - 1], outptr)); + const uint8_t* dataptr = static_cast(outbuff->getData()); + EXPECT_TRUE(equal(inbuff, inbuff + sizeof(inbuff) - 1, dataptr)); } } @@ -234,6 +231,10 @@ TEST(UDPSocket, SequenceTest) { 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 + size_t client_offset = 0; // Offset into buffer where data is put + size_t client_expected = 0; // Expected amount of data + OutputBufferPtr client_buffer(new OutputBuffer(16)); + // Where data is put // The server - with which the client communicates. For convenience, we // use the same io_service, and use the endpoint object created for @@ -293,9 +294,9 @@ TEST(UDPSocket, SequenceTest) { server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)), server_remote_endpoint.getASIOEndpoint(), server_cb); - // Expect two callbacks to run - service.get_io_service().poll(); - //service.run_one(); + // Expect two callbacks to run. + service.run_one(); + service.run_one(); EXPECT_TRUE(client_cb.getCalled()); EXPECT_EQ(0, client_cb.getCode()); @@ -312,10 +313,19 @@ TEST(UDPSocket, SequenceTest) { EXPECT_TRUE(server_address == client_remote_endpoint.getAddress()); EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort()); - // Finally, check that the receive received a complete buffer's worth of data. - client_cumulative += client_cb.getLength(); - EXPECT_TRUE(client.receiveComplete(&data[0], client_cumulative)); + // Check that the receive received a complete buffer's worth of data. + EXPECT_TRUE(client.processReceivedData(&data[0], client_cb.getLength(), + client_cumulative, client_offset, + client_expected, client_buffer)); + EXPECT_EQ(client_cb.getLength(), client_cumulative); + EXPECT_EQ(0, client_offset); + EXPECT_EQ(client_cb.getLength(), client_expected); + EXPECT_EQ(client_cb.getLength(), client_buffer->getLength()); + + // ...and check that the data was copied to the output client buffer. + const char* client_char_data = static_cast(client_buffer->getData()); + EXPECT_TRUE(equal(&data[0], &data[client_cb.getLength() - 1], client_char_data)); // Close client and server. EXPECT_NO_THROW(client.close()); diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h index 8399f2c625..35fc7b1841 100644 --- a/src/lib/asiolink/udp_socket.h +++ b/src/lib/asiolink/udp_socket.h @@ -123,37 +123,25 @@ public: virtual void asyncReceive(void* data, size_t length, size_t offset, IOEndpoint* endpoint, C& callback); - /// \brief Checks if the data received is complete. + /// \brief Process received data /// - /// For a UDP socket all the data is received in one I/O, so this is - /// effectively a no-op (although it does update the amount of data - /// received). + /// See the description of IOAsioSocket::receiveComplete for a complete + /// description of this method. /// - /// \param data Data buffer containing data to date (ignored) - /// \param length Amount of data in the buffer. + /// \param staging Pointer to the start of the staging buffer. + /// \param length Amount of data in the staging buffer. + /// \param cumulative Amount of data received before the staging buffer is + /// processed. + /// \param offset Unused. + /// \param expected unused. + /// \param outbuff Output buffer. Data in the staging buffer is be copied + /// to this output buffer in the call. /// /// \return Always true - virtual bool receiveComplete(const void*, size_t) { - return (true); - } - - /// \brief Append Normalized Data - /// - /// When a UDP buffer is received, the entire buffer contains the data. - /// When a TCP buffer is received, the first two bytes of the buffer hold - /// a length count. This method removes those bytes from the buffer. - /// - /// \param inbuf Input buffer. This contains the data received over the - /// network connection. - /// \param length Amount of data in the input buffer. If TCP, this includes - /// the two-byte count field. - /// \param outbuf Pointer to output buffer to which the data will be - /// appended - virtual void appendNormalizedData(const void* inbuf, size_t length, - isc::dns::OutputBufferPtr outbuf) - { - outbuf->writeData(inbuf, length); - } + virtual bool processReceivedData(const void* staging, size_t length, + size_t& cumulative, size_t& offset, + size_t& expected, + isc::dns::OutputBufferPtr& outbuff); /// \brief Cancel I/O On Socket virtual void cancel(); @@ -288,6 +276,27 @@ UDPSocket::asyncReceive(void* data, size_t length, size_t offset, } } +// Receive complete. Just copy the data across to the output buffer and +// update arguments as appropriate. + +template bool +UDPSocket::processReceivedData(const void* staging, size_t length, + size_t& cumulative, size_t& offset, + size_t& expected, + isc::dns::OutputBufferPtr& outbuff) +{ + // Set return values to what we should expect. + cumulative = length; + expected = length; + offset = 0; + + // Copy data across + outbuff->writeData(staging, length); + + // ... and mark that we have everything. + return (true); +} + // Cancel I/O on the socket. No-op if the socket is not open. template void From 3407184a20c367a0de02b41befb4db16971c9589 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Fri, 4 Mar 2011 16:39:10 +0100 Subject: [PATCH 050/113] [trac657] Remove nop operation Not directly related to the ticket, I just discovered a call that does nothing (it is called with null pointer and the function checks the parameter is not null, if it is, it just does nothing). --- src/bin/auth/main.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 275ae7dd8b..04730093cb 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -176,13 +176,8 @@ main(int argc, char* argv[]) { auth_server->setXfrinSession(xfrin_session); auth_server->setStatisticsSession(statistics_session); - // Configure the server. configureAuthServer() is expected to install - // all initial configurations, but as a short term workaround we - // handle the traditional "database_file" setup by directly calling - // updateConfig(). auth_server->setConfigSession(config_session); configureAuthServer(*auth_server, config_session->getFullConfig()); - auth_server->updateConfig(ElementPtr()); cout << "[b10-auth] Server started." << endl; io_service.run(); From b2d210a1ed486dc8c1083deb6f1b0bf2802e8d57 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Fri, 4 Mar 2011 17:28:56 +0100 Subject: [PATCH 051/113] [trac657] Abort on fatal errors Like when the socket is closed. Trying to accept more connections on such socket lead to busy loop. --- src/lib/asiolink/tcp_server.cc | 16 ++++++++++++++-- src/lib/asiolink/udp_server.cc | 11 +++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc index e77828c029..8f3fa0d8d1 100644 --- a/src/lib/asiolink/tcp_server.cc +++ b/src/lib/asiolink/tcp_server.cc @@ -17,6 +17,7 @@ #include #include #include // for some IPC/network system calls +#include #include @@ -83,11 +84,22 @@ TCPServer::operator()(error_code ec, size_t length) { /// Create a socket to listen for connections socket_.reset(new tcp::socket(acceptor_->get_io_service())); - /// Wait for new connections. In the event of error, + /// Wait for new connections. In the event of non-fatal error, /// try again do { CORO_YIELD acceptor_->async_accept(*socket_, *this); - } while (!ec); + // Abort on fatal errors + // TODO: Log error? + if (ec) { + if (ec.category() != error::system_category) { + return; + } + if (ec.value() != EWOULDBLOCK && ec.value() != EAGAIN && + ec.value() != ECONNABORTED && ec.value() != EINTR) { + return; + } + } + } while (ec); /// Fork the coroutine by creating a copy of this one and /// scheduling it on the ASIO service queue. The parent diff --git a/src/lib/asiolink/udp_server.cc b/src/lib/asiolink/udp_server.cc index 841bdc6d9b..7f58b0094c 100644 --- a/src/lib/asiolink/udp_server.cc +++ b/src/lib/asiolink/udp_server.cc @@ -15,6 +15,7 @@ #include #include #include // for some IPC/network system calls +#include #include @@ -195,6 +196,16 @@ UDPServer::operator()(error_code ec, size_t length) { CORO_YIELD data_->socket_->async_receive_from( buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_, *this); + // Abort on fatal errors + if (ec) { + if (ec.category() != error::system_category) { + return; + } + if (ec.value() != EWOULDBLOCK && ec.value() != EAGAIN && + ec.value() != EINTR) { + return; + } + } } while (ec || length == 0); data_->bytes_ = length; From c4f4d32eed0f4c26e4968336179a1e749e925aa7 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 10:29:33 -0800 Subject: [PATCH 052/113] [trac606] fixed a typo in README: we use make systest instead of 'test'. (make test is for unittests in BIND 10) --- tests/system/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/README b/tests/system/README index 7da8e90765..ea3ca1fd46 100644 --- a/tests/system/README +++ b/tests/system/README @@ -56,7 +56,7 @@ The tests can be run individually like this: sh run.sh glue etc. -To run all the tests, just type "make test" either on this directory +To run all the tests, just type "make systest" either on this directory or on the top source directory. Note: currently these tests cannot be run when built under a separate build directory. Everything must be run within the original source tree. From 860a2965d04f80fd0e90d3c5100332a8bb2ae9d8 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 10:30:11 -0800 Subject: [PATCH 053/113] [trac606] minor grammar fix --- tests/system/README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/README b/tests/system/README index ea3ca1fd46..3ace845844 100644 --- a/tests/system/README +++ b/tests/system/README @@ -19,7 +19,7 @@ There are multiple test suites, each in a separate subdirectory and involving a different DNS setup. They are: glue/ Glue handling tests -(the following test are planned to be added soon) +(the following tests are planned to be added soon) dnssec/ DNSSEC tests masterfile/ Master file parser xfer/ Zone transfer tests From f3bd834c8e9334004994c33d0798ed6b61a7a99b Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 11:02:34 -0800 Subject: [PATCH 054/113] [trac606] make sure distclean works for a separate builddir, even if systest itself cannot be run for such build environment. also added an explicit check about the assumption in conf.sh with an error message explaining what's wrong. --- Makefile.am | 2 +- tests/system/Makefile.am | 4 ++-- tests/system/conf.sh.in | 7 +++++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Makefile.am b/Makefile.am index e2203dba4d..e31a1a5078 100644 --- a/Makefile.am +++ b/Makefile.am @@ -80,7 +80,7 @@ cppcheck: # system tests systest: cd tests/system; \ - sh runall.sh + sh $(abs_srcdir)/tests/system/runall.sh #### include external sources in the distributed tarball: EXTRA_DIST = ext/asio/README diff --git a/tests/system/Makefile.am b/tests/system/Makefile.am index 44e09e080b..050ee2f249 100644 --- a/tests/system/Makefile.am +++ b/tests/system/Makefile.am @@ -1,5 +1,5 @@ systest: - sh runall.sh + sh $(srcdir)/runall.sh distclean-local: - sh cleanall.sh + sh $(srcdir)/cleanall.sh diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in index 5c6a44d240..874731ee2e 100755 --- a/tests/system/conf.sh.in +++ b/tests/system/conf.sh.in @@ -20,6 +20,13 @@ # other shell scripts. # +# Prerequisite check +if [ @srcdir@ != @builddir@ ]; then + echo "Currently systest doesn't work for a separate build tree." + echo "Rebuild BIND 10 on the source tree and run the tests." + exit 1 +fi + # Find the top of the source tree. TOP=@abs_top_srcdir@ From 4596c143eadfb9d67c5395bb6fa367470976d0cb Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 11:19:33 -0800 Subject: [PATCH 055/113] [trac606] added an explicit check to see if BIND9_TOP is set. --- tests/system/conf.sh.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in index 874731ee2e..69ebe69106 100755 --- a/tests/system/conf.sh.in +++ b/tests/system/conf.sh.in @@ -27,6 +27,13 @@ if [ @srcdir@ != @builddir@ ]; then exit 1 fi +if [ -z $BIND9_TOP ]; then + echo "systest assumes there's a compiled tree of BIND 9 which can be" + echo "accessed via the BIND9_TOP environment variable." + echo "Please make sure this assumption is met." + exit 1 +fi + # Find the top of the source tree. TOP=@abs_top_srcdir@ From e0d5584bdb3ca05b078693787c35d8f8f9228040 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 11:46:08 -0800 Subject: [PATCH 056/113] [trac606] added some note about code quality of digcomp.pl based on review comment. --- tests/system/digcomp.pl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/system/digcomp.pl b/tests/system/digcomp.pl index 506e62b32a..f5cd7fe9fb 100755 --- a/tests/system/digcomp.pl +++ b/tests/system/digcomp.pl @@ -19,6 +19,9 @@ # Ignore "unimportant" differences, like ordering of NS lines, TTL's, # etc... +# This is a bare copy of the script used in BIND 9's system test. It was +# old and may need cleanups, or we may want to rewrite it in python. + $file1 = $ARGV[0]; $file2 = $ARGV[1]; From 242cb68113bd4d2c0a3ea3b61cc56e143a42b38e Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 15:32:43 -0800 Subject: [PATCH 057/113] [master] a trivial cleanup: remove unnecessary cast. the orignal code was not good in that - it used C-style cast - the cast wasn't necessary in the first place quite trivial, no behavior change, compile ok, so committing directly. --- src/lib/asiolink/tcp_server.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc index e77828c029..4389677087 100644 --- a/src/lib/asiolink/tcp_server.cc +++ b/src/lib/asiolink/tcp_server.cc @@ -110,7 +110,7 @@ TCPServer::operator()(error_code ec, size_t length) { /// Now read the message itself. (This is done in a different scope /// to allow inline variable declarations.) CORO_YIELD { - InputBuffer dnsbuffer((const void *) data_.get(), length); + InputBuffer dnsbuffer(data_.get(), length); uint16_t msglen = dnsbuffer.readUint16(); async_read(*socket_, asio::buffer(data_.get(), msglen), *this); } From b9d341ecd382b44d72888899e3559d7d41baa203 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 15:35:59 -0800 Subject: [PATCH 058/113] [trac657] Revert "[trac657] Remove nop operation" This reverts commit 3407184a20c367a0de02b41befb4db16971c9589. This is not nop, and is necessary for the default sqlite3 data source in the "from source" mode. --- src/bin/auth/main.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc index 04730093cb..275ae7dd8b 100644 --- a/src/bin/auth/main.cc +++ b/src/bin/auth/main.cc @@ -176,8 +176,13 @@ main(int argc, char* argv[]) { auth_server->setXfrinSession(xfrin_session); auth_server->setStatisticsSession(statistics_session); + // Configure the server. configureAuthServer() is expected to install + // all initial configurations, but as a short term workaround we + // handle the traditional "database_file" setup by directly calling + // updateConfig(). auth_server->setConfigSession(config_session); configureAuthServer(*auth_server, config_session->getFullConfig()); + auth_server->updateConfig(ElementPtr()); cout << "[b10-auth] Server started." << endl; io_service.run(); From dfbeea4eb3f414dcb66d2447e59216c23dade515 Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Sat, 5 Mar 2011 14:44:01 +0800 Subject: [PATCH 059/113] [master] Add changelog. --- ChangeLog | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ChangeLog b/ChangeLog index 4ffd7575e3..7d140eceee 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ + 187. [bug] zhang likun + Fix the assert error in class isc::cache::RRsetCache by adding the + check for empty pointer and test case for it. + (Trac #638, git 54e61304131965c4a1d88c9151f8697dcbb3ce12) + 186. [bug] jelte b10-resolver could stop with an assertion failure on certain kinds of messages (there was a problem in error message creation). This From 1c8557996bdd428c9a3bf0935c30bf42fb167f35 Mon Sep 17 00:00:00 2001 From: JINMEI Tatuya Date: Fri, 4 Mar 2011 23:31:16 -0800 Subject: [PATCH 060/113] [master] minor editorial fixes: typo in comments, missing braces (style matter) skipping review. --- src/lib/asiolink/tcp_server.cc | 7 ++++--- src/lib/asiolink/udp_server.cc | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc index 4389677087..df19b000a5 100644 --- a/src/lib/asiolink/tcp_server.cc +++ b/src/lib/asiolink/tcp_server.cc @@ -65,7 +65,7 @@ TCPServer::TCPServer(io_service& io_service, void TCPServer::operator()(error_code ec, size_t length) { - /// Because the coroutine reeentry block is implemented as + /// Because the coroutine reentry block is implemented as /// a switch statement, inline variable declarations are not /// permitted. Certain variables used below can be declared here. @@ -196,9 +196,10 @@ TCPServer::asyncLookup() { } void TCPServer::stop() { - //server should not be stopped twice - if (stopped_by_hand_) + // server should not be stopped twice + if (stopped_by_hand_) { return; + } stopped_by_hand_ = true; acceptor_->close(); diff --git a/src/lib/asiolink/udp_server.cc b/src/lib/asiolink/udp_server.cc index 841bdc6d9b..98a47c48b6 100644 --- a/src/lib/asiolink/udp_server.cc +++ b/src/lib/asiolink/udp_server.cc @@ -169,7 +169,7 @@ UDPServer::UDPServer(io_service& io_service, const ip::address& addr, /// pattern; see internal/coroutine.h for details. void UDPServer::operator()(error_code ec, size_t length) { - /// Because the coroutine reeentry block is implemented as + /// Because the coroutine reentry block is implemented as /// a switch statement, inline variable declarations are not /// permitted. Certain variables used below can be declared here. From 19197b5bc9f2955bd6a8ca48a2d04472ed696e81 Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Mon, 7 Mar 2011 13:56:16 +0800 Subject: [PATCH 061/113] [master] 1. Add changelog. 2. Add cache test data file EXTRA_DISTOF --- ChangeLog | 6 ++++++ src/lib/cache/tests/Makefile.am | 2 ++ 2 files changed, 8 insertions(+) diff --git a/ChangeLog b/ChangeLog index 7d140eceee..690595a300 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ + 188. [bug] zhang likun + Make the rrset trust level ranking algorithm used by + isc::cache::MessageEntry::getRRsetTrustLevel() follow RFC2181 + section 5.4.1. + (Trac #595 git) + 187. [bug] zhang likun Fix the assert error in class isc::cache::RRsetCache by adding the check for empty pointer and test case for it. diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am index b93c9a7d7a..6f05f7463f 100644 --- a/src/lib/cache/tests/Makefile.am +++ b/src/lib/cache/tests/Makefile.am @@ -64,3 +64,5 @@ EXTRA_DIST += testdata/message_fromWire3 EXTRA_DIST += testdata/message_fromWire4 EXTRA_DIST += testdata/message_fromWire5 EXTRA_DIST += testdata/message_fromWire6 +EXTRA_DIST += testdata/message_fromWire7 +EXTRA_DIST += testdata/message_fromWire8 From ed6b07b60ae503e97e284eff351386ba24377981 Mon Sep 17 00:00:00 2001 From: zhanglikun Date: Mon, 7 Mar 2011 13:57:24 +0800 Subject: [PATCH 062/113] [master] Changelog --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 690595a300..582bba0c6f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,7 +2,7 @@ Make the rrset trust level ranking algorithm used by isc::cache::MessageEntry::getRRsetTrustLevel() follow RFC2181 section 5.4.1. - (Trac #595 git) + (Trac #595 git 19197b5bc9f2955bd6a8ca48a2d04472ed696e81) 187. [bug] zhang likun Fix the assert error in class isc::cache::RRsetCache by adding the From 0b42ead35810e82a97561d71309283950ca9d5a3 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 7 Mar 2011 11:30:58 +0100 Subject: [PATCH 063/113] [trac499] fixed some trivial typos --- src/lib/asiolink/io_asio_socket.h | 2 +- src/lib/asiolink/tcp_socket.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h index 0fadca2148..ac793a6af7 100644 --- a/src/lib/asiolink/io_asio_socket.h +++ b/src/lib/asiolink/io_asio_socket.h @@ -121,7 +121,7 @@ public: /// 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. + /// limited purposes. /// /// This method never throws an exception. /// diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h index d49df460f9..fcbf3b72b0 100644 --- a/src/lib/asiolink/tcp_socket.h +++ b/src/lib/asiolink/tcp_socket.h @@ -175,7 +175,7 @@ private: // TODO: Remove temporary buffer // The current implementation copies the buffer passed to asyncSend() into // a temporary buffer and precedes it with a two-byte count field. As - // ASIO should really be just about sendiong and receiving data, the TCP + // ASIO should really be just about sending and receiving data, the TCP // code should not do this. If the protocol using this requires a two-byte // count, it should add it before calling this code. (This may be best // achieved by altering isc::dns::buffer to have pairs of methods: @@ -302,7 +302,7 @@ TCPSocket::asyncReceive(void* data, size_t length, size_t offset, assert(endpoint->getProtocol() == IPPROTO_TCP); TCPEndpoint* tcp_endpoint = static_cast(endpoint); - // Write the endpoint details from the comminications link. Ideally + // Write the endpoint details from the communications link. Ideally // we should make IOEndpoint assignable, but this runs in to all sorts // of problems concerning the management of the underlying Boost // endpoint (e.g. if it is not self-managed, is the copied one From 058f40e75eb59aea1d3e18ffcdfdacc8386aafa3 Mon Sep 17 00:00:00 2001 From: Jelte Jansen Date: Mon, 7 Mar 2011 14:03:15 +0100 Subject: [PATCH 064/113] [trac583] addressed review comments Fixed some style issues, made the singleton instance a static object, and added a bit of documentation --- src/lib/asiolink/io_fetch.cc | 2 +- src/lib/asiolink/qid_gen.cc | 26 +++++-------- src/lib/asiolink/qid_gen.h | 43 ++++++++++++++-------- src/lib/asiolink/tests/qid_gen_unittest.cc | 18 ++++----- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 08a5f8f521..a68c4c5e10 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -67,7 +67,7 @@ IOFetch::operator()(error_code ec, size_t length) { { Message msg(Message::RENDER); - msg.setQid(QidGenerator::getInstance()->generateQid()); + msg.setQid(QidGenerator::getInstance().generateQid()); msg.setOpcode(Opcode::QUERY()); msg.setRcode(Rcode::NOERROR()); msg.setHeaderFlag(Message::HEADERFLAG_RD); diff --git a/src/lib/asiolink/qid_gen.cc b/src/lib/asiolink/qid_gen.cc index c570ee14dd..1361b95b92 100644 --- a/src/lib/asiolink/qid_gen.cc +++ b/src/lib/asiolink/qid_gen.cc @@ -23,40 +23,34 @@ #include namespace { - asiolink::QidGenerator* qid_generator_instance = NULL; + asiolink::QidGenerator qid_generator_instance; } namespace asiolink { -QidGenerator* + +QidGenerator& QidGenerator::getInstance() { - if (!qid_generator_instance) { - qid_generator_instance = new QidGenerator(); - qid_generator_instance->seed(); - } - - return qid_generator_instance; + return (qid_generator_instance); } -void -QidGenerator::cleanInstance() { - delete qid_generator_instance; - qid_generator_instance = NULL; +QidGenerator::QidGenerator() : dist_(0, 65535), + vgen_(generator_, dist_) +{ + seed(); } -QidGenerator::QidGenerator() : dist(0, 65535), vgen(generator, dist) {} - void QidGenerator::seed() { struct timeval tv; gettimeofday(&tv, 0); long int seed; seed = (tv.tv_sec * 1000000) + tv.tv_usec; - generator.seed(seed); + generator_.seed(seed); } isc::dns::qid_t QidGenerator::generateQid() { - return vgen(); + return (vgen_()); } } // namespace asiolink diff --git a/src/lib/asiolink/qid_gen.h b/src/lib/asiolink/qid_gen.h index 89fd506554..ba3c23193c 100644 --- a/src/lib/asiolink/qid_gen.h +++ b/src/lib/asiolink/qid_gen.h @@ -18,8 +18,8 @@ // (and other parts where we need randomness, perhaps another thing // for a general libutil?) -#ifndef __QID_GEN_ -#define __QID_GEN_ +#ifndef __QID_GEN_H +#define __QID_GEN_H #include #include @@ -40,15 +40,17 @@ class QidGenerator { public: /// \brief Returns the singleton instance of the QidGenerator /// - /// Initializes a new instance if there is none. - static QidGenerator* getInstance(); + /// Returns a reference to the singleton instance of the generator + static QidGenerator& getInstance(); - /// \brief Cleans up the singleton instance. + /// \brief Default constructor /// - /// This is added so that we can run memory checkers and - /// not get complaints about leaking data. If getInstance() - /// is called after cleanInstance, a new one would be created. - static void cleanInstance(); + /// It is recommended that getInstance is used rather than creating + /// separate instances of this class. + /// + /// The constructor automatically seeds the generator with the + /// current time. + QidGenerator(); /// Generate a Qid /// @@ -57,18 +59,27 @@ public: /// \brief Seeds the QidGenerator (based on the current time) /// - /// This is automatically called when getInstance() is called - /// the first time. + /// This is automatically called by the constructor void seed(); private: - QidGenerator(); - boost::mt19937 generator; - boost::uniform_int<> dist; - boost::variate_generator > vgen; + // "Mersenne Twister: A 623-dimensionally equidistributed + // uniform pseudo-random number generator", Makoto Matsumoto and + // Takuji Nishimura, ACM Transactions on Modeling and Computer + // Simulation: Special Issue on Uniform Random Number Generation, + // Vol. 8, No. 1, January 1998, pp. 3-30. + // + // mt19937 is an implementation of one of the pseudo random + // generators described in this paper. + boost::mt19937 generator_; + + // For qid's we want a uniform distribution + boost::uniform_int<> dist_; + + boost::variate_generator > vgen_; }; } // namespace asiolink -#endif // __QID_GEN_ +#endif // __QID_GEN_H diff --git a/src/lib/asiolink/tests/qid_gen_unittest.cc b/src/lib/asiolink/tests/qid_gen_unittest.cc index aa315828f7..3ad8a03c51 100644 --- a/src/lib/asiolink/tests/qid_gen_unittest.cc +++ b/src/lib/asiolink/tests/qid_gen_unittest.cc @@ -39,14 +39,10 @@ // Check that getInstance returns a singleton TEST(QidGenerator, singleton) { - asiolink::QidGenerator* g1 = asiolink::QidGenerator::getInstance(); - asiolink::QidGenerator* g2 = asiolink::QidGenerator::getInstance(); + asiolink::QidGenerator& g1 = asiolink::QidGenerator::getInstance(); + asiolink::QidGenerator& g2 = asiolink::QidGenerator::getInstance(); - EXPECT_TRUE(g1 == g2); - - asiolink::QidGenerator::cleanInstance(); - // Is there any way to make sure a newly allocated one gets - // a new address? + EXPECT_TRUE(&g1 == &g2); } TEST(QidGenerator, generate) { @@ -55,9 +51,9 @@ TEST(QidGenerator, generate) { // test (http://xkcd.com/221/), and check if three consecutive // generates are not all the same. isc::dns::qid_t one, two, three; - asiolink::QidGenerator* gen = asiolink::QidGenerator::getInstance(); - one = gen->generateQid(); - two = gen->generateQid(); - three = gen->generateQid(); + asiolink::QidGenerator& gen = asiolink::QidGenerator::getInstance(); + one = gen.generateQid(); + two = gen.generateQid(); + three = gen.generateQid(); ASSERT_FALSE((one == two) && (one == three)); } From 117fa08fc7f8b7462190b75f99d7604c49f3b7c6 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 7 Mar 2011 14:45:05 +0000 Subject: [PATCH 065/113] [trac499] Miscellaneous enhancements * Increasing staging buffer size for better performance. * Removed some overhead when creating the IOFetchData structure. * Extend unit tests. --- src/lib/asiolink/io_fetch.cc | 21 ++-- src/lib/asiolink/io_fetch.h | 2 +- src/lib/asiolink/tests/io_fetch_unittest.cc | 109 +++++++++++++++----- 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc index 8b7efabbb9..bc85892909 100644 --- a/src/lib/asiolink/io_fetch.cc +++ b/src/lib/asiolink/io_fetch.cc @@ -19,8 +19,7 @@ #include #include -#include -#include +#include #include #include @@ -67,13 +66,12 @@ struct IOFetchData { // actually instantiated depends on whether the fetch is over UDP or TCP, // which is not known until construction of the IOFetch. Use of a shared // pointer here is merely to ensure deletion when the data object is deleted. - boost::shared_ptr > socket; + boost::scoped_ptr > socket; ///< Socket to use for I/O - boost::shared_ptr remote; ///< Where the fetch was sent + boost::scoped_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 received; ///< Received data put here - boost::shared_array staging; ///< Temporary array for received data IOFetch::Callback* callback; ///< Called on I/O Completion asio::deadline_timer timer; ///< Timer to measure timeouts IOFetch::Protocol protocol; ///< Protocol being used @@ -89,6 +87,8 @@ struct IOFetchData { // This means that we must make sure that all possible "origins" take the // same arguments in their message in the same order. isc::log::MessageID origin; ///< Origin of last asynchronous I/O + uint8_t staging[IOFetch::STAGING_LENGTH]; + ///< Temporary array for received data /// \brief Constructor /// @@ -126,7 +126,7 @@ struct IOFetchData { question(query), msgbuf(new isc::dns::OutputBuffer(512)), received(buff), - staging(new char[IOFetch::MIN_LENGTH]), + callback(cb), timer(service.get_io_service()), protocol(proto), @@ -135,7 +135,8 @@ struct IOFetchData { offset(0), stopped(false), timeout(wait), - origin(ASIO_UNKORIGIN) + origin(ASIO_UNKORIGIN), + staging() {} }; @@ -235,11 +236,11 @@ IOFetch::operator()(asio::error_code ec, size_t length) { data_->cumulative = 0; // No data yet received data_->offset = 0; // First data into start of buffer do { - CORO_YIELD data_->socket->asyncReceive(data_->staging.get(), - static_cast(MIN_LENGTH), + CORO_YIELD data_->socket->asyncReceive(data_->staging, + static_cast(STAGING_LENGTH), data_->offset, data_->remote.get(), *this); - } while (!data_->socket->processReceivedData(data_->staging.get(), length, + } while (!data_->socket->processReceivedData(data_->staging, length, data_->cumulative, data_->offset, data_->expected, data_->received)); diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h index ab7e9add75..0723777e11 100644 --- a/src/lib/asiolink/io_fetch.h +++ b/src/lib/asiolink/io_fetch.h @@ -78,7 +78,7 @@ public: /// \brief Integer Constants enum { - MIN_LENGTH = 4096 ///< Minimum size of receive buffer + STAGING_LENGTH = 8192 ///< Size of staging buffer }; /// \brief I/O Fetch Callback diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc index df9421782e..4282c80aca 100644 --- a/src/lib/asiolink/tests/io_fetch_unittest.cc +++ b/src/lib/asiolink/tests/io_fetch_unittest.cc @@ -49,9 +49,8 @@ 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 response from server to client (longer than 30 bytes)"; -const int SEND_INTERVAL = 250; // Interval in ms between TCP sends +const int SEND_INTERVAL = 250; // Interval in ms between TCP sends +const size_t MAX_SIZE = 64 * 1024; // Should be able to take 64kB // The tests are complex, so debug output has been left in (although disabled). // Set this to true to enable it. @@ -76,12 +75,13 @@ public: // The next member is the buffer in which the "server" (implemented by the // response handler methods in this class) receives the question sent by the // fetch object. - uint8_t receive_buffer_[512]; ///< Server receive buffer + uint8_t receive_buffer_[MAX_SIZE]; ///< Server receive buffer vector send_buffer_; ///< Server send buffer uint16_t send_cumulative_; ///< Data sent so far // Other data. string return_data_; ///< Data returned by server + string test_data_; ///< Large string - here for convenience bool debug_; ///< true to enable debug output /// \brief Constructor @@ -103,7 +103,8 @@ public: receive_buffer_(), send_buffer_(), send_cumulative_(0), - return_data_(TEST_DATA), + return_data_(""), + test_data_(""), debug_(DEBUG) { // Construct the data buffer for question we expect to receive. @@ -115,6 +116,20 @@ public: msg.addQuestion(question_); MessageRenderer renderer(*msgbuf_); msg.toWire(renderer); + + // Initialize the test data to be returned: tests will return a + // substring of this data. (It's convenient to have this as a member of + // the class.) + // + // We could initialize the data with a single character, but as an added + // check we'll make ssre that it has some structure. + + test_data_.clear(); + test_data_.reserve(MAX_SIZE); + while (test_data_.size() < MAX_SIZE) { + test_data_ += "A message to be returned to the client that has " + "some sort of structure."; + } } /// \brief UDP Response handler (the "remote UDP DNS server") @@ -237,7 +252,8 @@ public: /// /// Send the TCP data back to the IOFetch object. The data is sent in /// three chunks - two of 16 bytes and the remainder, with a 250ms gap - /// between each. + /// between each. (Amounts of data smaller than one 32 bytes are sent in + /// one or two packets.) /// /// \param socket Socket over which send should take place void tcpSendData(tcp::socket* socket) { @@ -245,11 +261,20 @@ public: cout << "tcpSendData()" << endl; } - // Decide what to send based on the cumulative count + // Decide what to send based on the cumulative count. At most we'll do + // two chunks of 16 bytes (with a 250ms gap between) and then the + // remainder. uint8_t* send_ptr = &send_buffer_[send_cumulative_]; // Pointer to data to send size_t amount = 16; // Amount of data to send - if (send_cumulative_ > 30) { + if (send_cumulative_ < (2 * amount)) { + + // First or second time through, send at most 16 bytes + amount = min(amount, (send_buffer_.size() - send_cumulative_)); + + } else { + + // Third time through, send the remainder. amount = send_buffer_.size() - send_cumulative_; } @@ -403,6 +428,9 @@ public: /// /// \param Test data to return to client void tcpSendReturnTest(const std::string& return_data) { + if (debug_) { + cout << "tcpSendReturnTest(): data size = " << return_data.size() << endl; + } return_data_ = return_data; protocol_ = IOFetch::TCP; expected_ = IOFetch::SUCCESS; @@ -489,27 +517,62 @@ TEST_F(IOFetchTest, TcpTimeout) { timeoutTest(IOFetch::TCP, tcp_fetch_); } -// Do a send and return with a small amount of data +// Test with values at or near 0, then at or near the chunk size (16 and 32 +// bytes, the sizes of the first two packets) then up to 65535. These are done +// in separate tests because in practice a new IOFetch is created for each +// query/response exchange and we don't want to confuse matters in the test +// by running the test with an IOFetch that has already done one exchange. -TEST_F(IOFetchTest, TcpSendReceiveShort) { - tcpSendReturnTest(TEST_DATA); +TEST_F(IOFetchTest, TcpSendReceive0) { + tcpSendReturnTest(test_data_.substr(0, 0)); } -// Now with at least 16kB of data. +TEST_F(IOFetchTest, TcpSendReceive1) { + tcpSendReturnTest(test_data_.substr(0, 1)); +} -TEST_F(IOFetchTest, TcpSendReceiveLong) { - const size_t REQUIRED_SIZE = 16 * 1024; +TEST_F(IOFetchTest, TcpSendReceive15) { + tcpSendReturnTest(test_data_.substr(0, 15)); +} - // We could initialize the string with a repeat of a single character, but - // we choose to enure that there are different characters as an added test. - string data; - data.reserve(REQUIRED_SIZE); - while (data.size() < REQUIRED_SIZE) { - data += "An abritrary message that is returned to the IOFetch object!"; - } +TEST_F(IOFetchTest, TcpSendReceive16) { + tcpSendReturnTest(test_data_.substr(0, 16)); +} - // ... and do the test with this data. - tcpSendReturnTest(data); +TEST_F(IOFetchTest, TcpSendReceive17) { + tcpSendReturnTest(test_data_.substr(0, 17)); +} + +TEST_F(IOFetchTest, TcpSendReceive31) { + tcpSendReturnTest(test_data_.substr(0, 31)); +} + +TEST_F(IOFetchTest, TcpSendReceive32) { + tcpSendReturnTest(test_data_.substr(0, 32)); +} + +TEST_F(IOFetchTest, TcpSendReceive33) { + tcpSendReturnTest(test_data_.substr(0, 33)); +} + +TEST_F(IOFetchTest, TcpSendReceive4096) { + tcpSendReturnTest(test_data_.substr(0, 4096)); +} + +TEST_F(IOFetchTest, TcpSendReceive8192) { + tcpSendReturnTest(test_data_.substr(0, 8192)); +} + +TEST_F(IOFetchTest, TcpSendReceive16384) { + tcpSendReturnTest(test_data_.substr(0, 16384)); +} + +TEST_F(IOFetchTest, TcpSendReceive32768) { + tcpSendReturnTest(test_data_.substr(0, 32768)); +} + +TEST_F(IOFetchTest, TcpSendReceive65535) { + tcpSendReturnTest(test_data_.substr(0, 65535)); } } // namespace asiolink From 5ff855e14dca375c24404eebc4573b77dba915d3 Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 7 Mar 2011 16:06:26 +0000 Subject: [PATCH 066/113] [trac622] Update documentation Remove (most) references to log4cxx in the documentation. Ensure that the documentation and the "experimental" files mentioned in it are included in the distribution. --- src/lib/log/Makefile.am | 4 ++ src/lib/log/{documentation.txt => README} | 61 +++-------------------- 2 files changed, 11 insertions(+), 54 deletions(-) rename src/lib/log/{documentation.txt => README} (87%) diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am index 5bcafa8ebe..60b3f5bb85 100644 --- a/src/lib/log/Makefile.am +++ b/src/lib/log/Makefile.am @@ -23,6 +23,10 @@ liblog_la_SOURCES += message_types.h liblog_la_SOURCES += root_logger_name.cc root_logger_name.h liblog_la_SOURCES += strutil.h strutil.cc +EXTRA_DIST = README +EXTRA_DIST += logger_impl_log4cxx.cc logger_impl_log4cxx.h +EXTRA_DIST += xdebuglevel.cc xdebuglevel.h + # Note: the ordering matters: -Wno-... must follow -Wextra (defined in # B10_CXXFLAGS) liblog_la_CXXFLAGS = $(AM_CXXFLAGS) diff --git a/src/lib/log/documentation.txt b/src/lib/log/README similarity index 87% rename from src/lib/log/documentation.txt rename to src/lib/log/README index 0501587af7..a6f5a99e95 100644 --- a/src/lib/log/documentation.txt +++ b/src/lib/log/README @@ -233,8 +233,8 @@ To use the current version of the logging: isc::log::Logger logger("myname", true); - The argument is ignored for underlying implementations other than log4cxx. - See below for the use of this argument. + (The argument is required to support a possible future implementation of + logging. Currently it has no effect.) 3. The main program unit should include a call to isc::log::runTimeInit() (defined in logger_support.h) to set the logging severity, debug log level, @@ -379,56 +379,9 @@ Outstanding Issues log4cxx Issues ============== +Some experimental code to utilise log4cxx as an underlying implementation +is present in the source code directory, although it is not currently used. +The files are: -Second Argument in Logger Constructor -------------------------------------- -As noted above, when using log4cxx as the underlying implementation, the -argument to the logger's constructor should be set true if declaring the -logger within a method and set false (or omitted) if declaring the logger -external to an execution unit. - -This is due to an apparent bug in the underlying log4cxx, where the deletion -of a statically-declared object at program termination can cause a segment -fault. (The destruction of internal memory structures can sometimes happen -out of order.) By default the Logger class creates the structures in -its constructor but does not delete them in the destruction. The default -behavious works because instead of reclaiming memory at program run-down, -the operating system reclaims it when the process is deleted. - -Setting the second argument "true" causes the Logger's destructor to delete -the log4cxx structures. This does not cause a problem if the program is -not terminating. So use the second form when declaring an automatic instance -of isc::log::Logger on the stack. - -Building with log4cxx ---------------------- -Owing to issues with versions of log4cxx on different systems, log4cxx was -temporarily disabled. To use log4cxx on your system: - -* Uncomment the log4cxx lines in configure.ac -* In src/lib/log, replace the logger_impl.{cc,h} files with their log4cxx - equivalents, i.e. - - cp logger_impl_log4cxx.h logger_impl.h - cp logger_impl_log4cxx.cc logger_impl.cc - -* In src/lib/log/Makefile.am, uncomment the lines: - - # AM_CPPFLAGS += $(LOG4CXX_INCLUDES) - - # liblog_la_SOURCES += xdebuglevel.cc xdebuglevel.h - - # liblog_la_LDFLAGS = $(LOG4CXX_LDFLAGS) - -* In src/lib/log/test, re-enable testing of the log4cxx implementation - class, i.e. - - cp logger_impl_log4cxx_unittest.cc logger_impl_unittest.cc - - ... and uncomment the following lines in Makefile.am: - - # run_unittests_SOURCES += logger_impl_unittest.cc - - # run_unittests_SOURCES += xdebuglevel_unittest.cc - -Then rebuild the system from scratch. + logger_impl_log4cxx.{cc,h} + xdebuglevel.{cc,h} From 5e2e3fa2759475313d69d3f9cf1cd339f3db8bc7 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Mon, 7 Mar 2011 11:39:18 -0600 Subject: [PATCH 067/113] [master] regenerated manual pages for trac575; update date for a manual --- src/bin/auth/b10-auth.8 | 50 +++------------------------------------ src/bin/auth/b10-auth.xml | 2 +- src/bin/bind10/bind10.8 | 37 ++++------------------------- 3 files changed, 8 insertions(+), 81 deletions(-) diff --git a/src/bin/auth/b10-auth.8 b/src/bin/auth/b10-auth.8 index bae8e4a0b5..dcbe6d8a0f 100644 --- a/src/bin/auth/b10-auth.8 +++ b/src/bin/auth/b10-auth.8 @@ -2,12 +2,12 @@ .\" Title: b10-auth .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: January 19, 2011 +.\" Date: February 22, 2011 .\" Manual: BIND10 .\" Source: BIND10 .\" Language: English .\" -.TH "B10\-AUTH" "8" "January 19, 2011" "BIND10" "BIND10" +.TH "B10\-AUTH" "8" "February 22, 2011" "BIND10" "BIND10" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -22,7 +22,7 @@ b10-auth \- Authoritative DNS server .SH "SYNOPSIS" .HP \w'\fBb10\-auth\fR\ 'u -\fBb10\-auth\fR [\fB\-4\fR] [\fB\-6\fR] [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR] +\fBb10\-auth\fR [\fB\-n\fR] [\fB\-u\ \fR\fB\fIusername\fR\fR] [\fB\-v\fR] .SH "DESCRIPTION" .PP The @@ -42,55 +42,11 @@ It receives its configurations from .PP The arguments are as follows: .PP -\fB\-4\fR -.RS 4 -Enables IPv4 only mode\&. This switch may not be used with -\fB\-6\fR -nor -\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&. -.RE -.PP -\fB\-6\fR -.RS 4 -Enables IPv6 only mode\&. This switch may not be used with -\fB\-4\fR -nor -\fB\-a\fR\&. By default, it listens on both IPv4 and IPv6 (if capable)\&. -.RE -.PP -\fB\-a \fR\fB\fIaddress\fR\fR -.RS 4 -The IPv4 or IPv6 address to listen on\&. This switch may not be used with -\fB\-4\fR -nor -\fB\-6\fR\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.) -.RE -.PP \fB\-n\fR .RS 4 Do not cache answers in memory\&. The default is to use the cache for faster responses\&. The cache keeps the most recent 30,000 answers (positive and negative) in memory for 30 seconds (instead of querying the data source, such as SQLite3 database, each time)\&. .RE .PP -\fB\-p \fR\fB\fInumber\fR\fR -.RS 4 -The port number it listens on\&. The default is 5300\&. -.if n \{\ -.sp -.\} -.RS 4 -.it 1 an-trap -.nr an-no-space-flag 1 -.nr an-break-flag 1 -.br -.ps +1 -\fBNote\fR -.ps -1 -.br -The Y1 prototype runs on all interfaces and on this nonstandard port\&. -.sp .5v -.RE -.RE -.PP \fB\-u \fR\fB\fIusername\fR\fR .RS 4 The user name of the diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml index c9e935a4b3..ce80689481 100644 --- a/src/bin/auth/b10-auth.xml +++ b/src/bin/auth/b10-auth.xml @@ -20,7 +20,7 @@ - January 19, 2011 + February 22, 2011 diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8 index f625abef3d..a75136bb35 100644 --- a/src/bin/bind10/bind10.8 +++ b/src/bin/bind10/bind10.8 @@ -2,12 +2,12 @@ .\" Title: bind10 .\" Author: [see the "AUTHORS" section] .\" Generator: DocBook XSL Stylesheets v1.75.2 -.\" Date: July 29, 2010 +.\" Date: February 22, 2011 .\" Manual: BIND10 .\" Source: BIND10 .\" Language: English .\" -.TH "BIND10" "8" "July 29, 2010" "BIND10" "BIND10" +.TH "BIND10" "8" "February 22, 2011" "BIND10" "BIND10" .\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- @@ -22,7 +22,7 @@ bind10 \- BIND 10 boss process .SH "SYNOPSIS" .HP \w'\fBbind10\fR\ 'u -\fBbind10\fR [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-address\ \fR\fB\fIaddress\fR\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-port\ \fR\fB\fInumber\fR\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-verbose\fR] +\fBbind10\fR [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-verbose\fR] .SH "DESCRIPTION" .PP The @@ -32,13 +32,6 @@ daemon starts up other BIND 10 required daemons\&. It handles restarting of exit .PP The arguments are as follows: .PP -\fB\-a\fR \fIaddress\fR, \fB\-\-address\fR \fIaddress\fR -.RS 4 -The IPv4 or IPv6 address for the -\fBb10-auth\fR(8) -daemon to listen on\&. The default is to listen on all addresses\&. (This is a short term workaround\&. This argument may change\&.) -.RE -.PP \fB\-m\fR \fIfile\fR, \fB\-\-msgq\-socket\-file\fR \fIfile\fR .RS 4 The UNIX domain socket file for the @@ -54,28 +47,6 @@ Disables the hot\-spot caching used by the daemon\&. .RE .PP -\fB\-p\fR \fInumber\fR, \fB\-\-port\fR \fInumber\fR -.RS 4 -The port number for the -\fBb10-auth\fR(8) -daemon to listen on\&. The default is 5300\&. -.if n \{\ -.sp -.\} -.RS 4 -.it 1 an-trap -.nr an-no-space-flag 1 -.nr an-break-flag 1 -.br -.ps +1 -\fBNote\fR -.ps -1 -.br -This prototype release uses a non\-default port for domain service\&. -.sp .5v -.RE -.RE -.PP \fB\-u\fR \fIuser\fR, \fB\-\-user\fR \fIname\fR .RS 4 The username for @@ -125,5 +96,5 @@ The daemon was initially designed by Shane Kerr of ISC\&. .SH "COPYRIGHT" .br -Copyright \(co 2010 Internet Systems Consortium, Inc. ("ISC") +Copyright \(co 2011 Internet Systems Consortium, Inc. ("ISC") .br From eb6441aca464980d00e3ff827cbf4195c5a7afc5 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Mon, 7 Mar 2011 11:46:05 -0600 Subject: [PATCH 068/113] [master] Do not install the message compiler. (Ticket 634) --- src/lib/log/compiler/Makefile.am | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/log/compiler/Makefile.am b/src/lib/log/compiler/Makefile.am index 2475036e75..9343793f7c 100644 --- a/src/lib/log/compiler/Makefile.am +++ b/src/lib/log/compiler/Makefile.am @@ -10,11 +10,9 @@ if USE_STATIC_LINK AM_LDFLAGS = -static endif -pkglibexecdir = $(libexecdir)/@PACKAGE@ - CLEANFILES = *.gcno *.gcda -pkglibexec_PROGRAMS = message +noinst_PROGRAMS = message message_SOURCES = message.cc message_LDADD = $(top_builddir)/src/lib/log/liblog.la From 2495b96e3a3adeaaca091d7bf2533c1a58971ee5 Mon Sep 17 00:00:00 2001 From: "Jeremy C. Reed" Date: Mon, 7 Mar 2011 11:48:34 -0600 Subject: [PATCH 069/113] [master] changelog entry for 634 --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 582bba0c6f..b7d113a29b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ + 189. [bug] jreed + Do not install the log message compiler. + (Trac #634, git eb6441aca464980d00e3ff827cbf4195c5a7afc5) + 188. [bug] zhang likun Make the rrset trust level ranking algorithm used by isc::cache::MessageEntry::getRRsetTrustLevel() follow RFC2181 From bdc63200a7e2adcde2b0ce44aaeb8477fb059d17 Mon Sep 17 00:00:00 2001 From: Michal 'vorner' Vaner Date: Mon, 7 Mar 2011 18:55:08 +0100 Subject: [PATCH 070/113] [trac606] Fix error message --- tests/system/runall.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/system/runall.sh b/tests/system/runall.sh index b623f769bd..1cb9275f67 100755 --- a/tests/system/runall.sh +++ b/tests/system/runall.sh @@ -35,7 +35,7 @@ I: I:NOTE: Many of the tests were skipped because they require that I: the IP addresses 10.53.0.1 through 10.53.0.7 are configured I: as alias addresses on the loopback interface. Please run -I: "bin/tests/system/ifconfig.sh up" as root to configure them +I: "tests/system/ifconfig.sh up" as root to configure them I: and rerun the tests. EOF exit 0; From b23ef1cf58b0754e23e550362e7a979d5f8624ef Mon Sep 17 00:00:00 2001 From: Stephen Morris Date: Mon, 7 Mar 2011 18:09:31 +0000 Subject: [PATCH 071/113] [trac622] Additional changes to documentation --- src/lib/log/README | 65 +++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 38 deletions(-) diff --git a/src/lib/log/README b/src/lib/log/README index a6f5a99e95..2fc6aec2c4 100644 --- a/src/lib/log/README +++ b/src/lib/log/README @@ -59,7 +59,7 @@ The steps in using the system are: 2. Run it through the message compiler to produce the .h and .cc files. It is intended that this step be included in the build process. However, - for not run the compiler (found in the "compiler" subdirectory) manually. + for now run the compiler (found in the "compiler" subdirectory) manually. The only argument is the name of the message file: it will produce as output two files, having the same name as the input file but with file types of ".h" and ".cc". @@ -74,7 +74,7 @@ The steps in using the system are: 4. Declare loggers in your code and use them to log messages. This is described in more detail below. -5. To set the debug level and run-time message file, call runTimeInit (declared +5. To set the debug level and run-time message file, call initLogger (declared in logger_support.h) in the main program unit. This is a temporary solution for Year 2, and will be replaced at a later date, the information coming from the configuration database. @@ -106,7 +106,7 @@ UNKNOWN unknown message Points to note: * Leading and trailing space are trimmed from the line. Although the above - exampl,e has every line starting at column 1, the lines could be indented + example has every line starting at column 1, the lines could be indented if desired. * Blank lines are ignored. @@ -120,10 +120,8 @@ Points to note: * $PREFIX, which has one argument: the string used to prefix symbols. If absent, there is no prefix to the symbols. (Prefixes are explained below.) * $NAMESPACE, which has one argument: the namespace in which the symbols are - created. (Specifying the argument as a double colon - i.e. "$NAMESPACE - ::" puts the symbol definitions in the unnamed namespace. And not - including a $NAMESPACE directive will result in the symbols note being - put in any namespace. + created. In the absence of a $NAMESPACE directive, symbols will be put + in the global namespace. * Lines starting + indicate an explanation for the preceding message. These are intended to be processed by a separate program and used to generate @@ -151,13 +149,11 @@ The message compiler processes the message file to produce two files: the form: namespace { - isc::log::MessageID PREFIX_IDENTIFIER = "IDENTIFIER"; + extern const isc::log::MessageID PREFIX_IDENTIFIER; : } -The symbols define the keys in the global message dictionary. At present -they are defined as std::strings, but a future implementation could redefine -them as numeric values. +The symbols define the keys in the global message dictionary. The namespace enclosing the symbols is set by the $NAMESPACE directive. @@ -167,14 +163,18 @@ ABC with "MSG_" to give the symbol MSG_ABC. Similarly "$PREFIX E" would prefix it with "E" to give the symbol EABC. If no $PREFIX is given, no prefix appears (so the symbol in this example would be ABC). -The header file also includes a couple of lines to ensure that the message -text is included in the final program image. +2) A C++ source file (called .cc) that holds the definitions +of the global symbols and code to insert the symbols and messages into the map. +Symbols are defined to be equal to strings holding the identifier, e.g. -2) A C++ source file (called .cc) that holds the code to -insert the symbols and messages into the map. + extern const isc::log::MessageID MSG_DUPLNS = "DUPLNS"; -This file declares an array of identifiers/messages in the form: +(The implementation allows symbols to be compared. However, use of strings +should not be assumed - a future implementation may change this.) + +iIn addition, the file declares an array of identifiers/messages and an object +to add them to the global dictionary: namespace { const char* values[] = { @@ -183,21 +183,10 @@ This file declares an array of identifiers/messages in the form: : NULL }; + + const isc::log::MessageInitializer initializer(values); } -(A more complex structure to group identifiers and their messages could be -imposed, but as the array is generated by code and will be read by code, -it is not needed.) - -It then declares an object that will add information to the global dictionary: - - MessageInitializer _