diff --git a/ChangeLog b/ChangeLog
index 517954853d..3bbc21f4cb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,7 +1,84 @@
- 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.
+ 197. [bug] zhang likun
+ Remove expired message and rrset entries when looking up them
+ in cache, touch or remove the rrset entry in cache properly
+ when doing lookup or update.
+ (Trac #661, git 9efbe64fe3ff22bb5fba46de409ae058f199c8a7)
+
+ 196. [bug] jinmei
+ b10-auth, src/lib/datasrc: the backend of the in-memory data
+ source could not handle the root name. As a result b10-auth could
+ not work as a root server when using the in-memory data source.
+ (Trac #683, git 420ec42bd913fb83da37b26b75faae49c7957c46)
+
+ 195. [func] stephen
+ Resolver will now re-try a query over TCP if a response to a UDP
+ query has the TC bit set.
+ (Trac #499, git 4c05048ba059b79efeab53498737abe94d37ee07)
+
+ 194. [bug] vorner
+ Solved a 100% CPU usage problem after switching addresses in b10-auth
+ (and possibly, but unconfirmed, in b10-resolver). It was caused by
+ repeated reads/accepts on closed socket (the bug was in the code for a
+ long time, recent changes made it show).
+ (Trac #657, git e0863720a874d75923ea66adcfbf5b2948efb10a)
+
+ 193. [func]* jreed
+ Listen on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses
+ for b10-auth. This returns to previous behavior prior to
+ change #184. Document the listen_on configuration in manual.
+ (Trac #649, git 65a77d8fde64d464c75917a1ab9b6b3f02640ca6)
+
+ 192. [func]* jreed
+ Listen on standard domain port 53 for b10-auth and
+ b10-resolver.
+ (Trac #617, #618, git 137a6934a14cf0c5b5c065e910b8b364beb0973f)
+
+ 191. [func] jinmei
+ Imported system test framework of BIND 9. It can be run by
+ 'make systest' at the top source directory. Notes: currently it
+ doesn't work when built in a separate tree. It also requires
+ perl, an inherited dependency from the original framework.
+ Also, mainly for the purpose of tests, a new option "--pid-file"
+ was added to BoB, with which the boss process will dump its PID
+ to the specified file.
+ (Trac #606, git 6ac000df85625f5921e8895a1aafff5e4be3ba9c)
+
+ 190. [func] jelte
+ Resolver now sets random qids on outgoing queries using
+ the boost::mt19937 prng.
+ (Trac #583, git 5222b51a047d8f2352bc9f92fd022baf1681ed81)
+
+ 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
+ section 5.4.1.
+ (Trac #595 git 19197b5bc9f2955bd6a8ca48a2d04472ed696e81)
+
+ 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
+ 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).
+ (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
+ at runtime, in the Auth/listen_on list, not through command line
+ arguments.
(Trac #575, #576, git f06ce638877acf6f8e1994962bf2dbfbab029edf)
183. [bug] jerry
diff --git a/Makefile.am b/Makefile.am
index 9a28f200f0..e31a1a5078 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 $(abs_srcdir)/tests/system/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/README b/README
index 5bc31546a8..b10d12ee65 100644
--- a/README
+++ b/README
@@ -164,8 +164,6 @@ source tree:
(Which will use the modules and configurations also from the source
tree.)
-The server will listen on port 5300 for DNS requests.
-
CONFIGURATION
Commands can be given through the bindctl tool.
diff --git a/configure.ac b/configure.ac
index d5795c41a3..b93fdc7098 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)
@@ -684,6 +690,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
@@ -713,9 +721,10 @@ 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
+ src/bin/bindctl/bindctl_main.py
src/bin/bindctl/tests/bindctl_test
src/bin/loadzone/run_loadzone.sh
src/bin/loadzone/tests/correct/correct_test.sh
@@ -740,6 +749,10 @@ 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
+ tests/system/glue/setup.sh
+ tests/system/glue/nsx1/b10-config.db
+ tests/system/bindctl/nsx1/b10-config.db.template
], [
chmod +x src/bin/cmdctl/run_b10-cmdctl.sh
chmod +x src/bin/xfrin/run_b10-xfrin.sh
@@ -764,6 +777,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
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 0935c810c3..bceb40cb02 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -336,14 +336,6 @@ var/
-
-
- The development prototype of the b10-auth server listens on
- 0.0.0.0 (all interfaces) port 5300. (This is not the standard
- domain service port.)
-
-
-
To quickly get started with BIND 10, follow these steps.
@@ -397,7 +389,7 @@ var/
Test it; for example:
- $ dig @127.0.0.1 -p 5300 -c CH -t TXT authors.bind
+ $ dig @127.0.0.1 -c CH -t TXT authors.bind
@@ -1044,11 +1036,6 @@ TODO
process.
-
- This development prototype release listens on all interfaces
- and the non-standard port 5300.
-
-
Server Configurations
diff --git a/src/bin/auth/auth.spec.pre.in b/src/bin/auth/auth.spec.pre.in
index e95dabd7a0..d88ffb5e3e 100644
--- a/src/bin/auth/auth.spec.pre.in
+++ b/src/bin/auth/auth.spec.pre.in
@@ -63,12 +63,12 @@
"item_optional": false,
"item_default": [
{
- "address": "::1",
- "port": 5300
+ "address": "::",
+ "port": 53
},
{
- "address": "127.0.0.1",
- "port": 5300
+ "address": "0.0.0.0",
+ "port": 53
}
],
"list_item_spec": {
@@ -87,7 +87,7 @@
"item_name": "port",
"item_type": "integer",
"item_optional": false,
- "item_default": 5300
+ "item_default": 53
}
]
}
diff --git a/src/bin/auth/b10-auth.8 b/src/bin/auth/b10-auth.8
index bae8e4a0b5..0356683b11 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: March 8, 2011
.\" Manual: BIND10
.\" Source: BIND10
.\" Language: English
.\"
-.TH "B10\-AUTH" "8" "January 19, 2011" "BIND10" "BIND10"
+.TH "B10\-AUTH" "8" "March 8, 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
@@ -114,6 +70,18 @@ defines the path to the SQLite3 zone file when using the sqlite datasource\&. Th
/usr/local/var/bind10\-devel/zone\&.sqlite3\&.
.PP
+\fIlisten_on\fR
+is a list of addresses and ports for
+\fBb10\-auth\fR
+to listen on\&. The list items are the
+\fIaddress\fR
+string and
+\fIport\fR
+number\&. By default,
+\fBb10\-auth\fR
+listens on port 53 on the IPv6 (::) and IPv4 (0\&.0\&.0\&.0) wildcard addresses\&.
+.PP
+
\fIdatasources\fR
configures data sources\&. The list items include:
\fItype\fR
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index c9e935a4b3..2b533947d1 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -20,7 +20,7 @@
- January 19, 2011
+ March 8, 2011
@@ -131,6 +131,15 @@
/usr/local/var/bind10-devel/zone.sqlite3.
+
+ listen_on is a list of addresses and ports for
+ b10-auth to listen on.
+ The list items are the address string
+ and port number.
+ By default, b10-auth listens on port 53
+ on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses.
+
+
datasources configures data sources.
The list items include:
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/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/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/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
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
index 346287ef35..9cdf6f0411 100755
--- a/src/bin/bind10/bind10.py.in
+++ b/src/bin/bind10/bind10.py.in
@@ -831,18 +831,54 @@ def parse_args(args=sys.argv[1:], Parser=OptionParser):
default=None)
parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
default=None, help="Port of command control")
+ 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(args)
+
if options.cmdctl_port is not None:
try:
isc.net.parse.port_parse(options.cmdctl_port)
except ValueError as e:
parser.error(e)
+
if args:
parser.print_help()
sys.exit(1)
return options
+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
+ 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(). 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
@@ -907,6 +943,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
@@ -960,6 +997,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 90%
rename from src/bin/bind10/tests/bind10_test.py
rename to src/bin/bind10/tests/bind10_test.py.in
index e85158194f..a53df1a526 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, parse_args
+from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file
# XXX: environment tests are currently disabled, due to the preprocessor
# setup that we have now complicating the environment
@@ -49,7 +49,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)
@@ -464,5 +464,52 @@ class TestParseArgs(unittest.TestCase):
options = parse_args(['--cmdctl-port=1234'], TestOptParser)
self.assertEqual(1234, options.cmdctl_port)
+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_unlink_pid_file_notexist(self):
+ dummy_data = 'dummy_data\n'
+ open(self.pid_file, "w").write(dummy_data)
+ unlink_pid_file("no_such_pid_file")
+ # the file specified for unlink_pid_file doesn't exist,
+ # and 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()
diff --git a/src/bin/bindctl/Makefile.am b/src/bin/bindctl/Makefile.am
index e95af785aa..2f412ece88 100644
--- a/src/bin/bindctl/Makefile.am
+++ b/src/bin/bindctl/Makefile.am
@@ -5,12 +5,13 @@ man_MANS = bindctl.1
EXTRA_DIST = $(man_MANS) bindctl.xml
-python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py mycollections.py
+python_PYTHON = __init__.py bindcmd.py cmdparse.py exception.py moduleinfo.py \
+ mycollections.py
pythondir = $(pyexecdir)/bindctl
bindctldir = $(pkgdatadir)
-CLEANFILES = bindctl
+CLEANFILES = bindctl bindctl_main.pyc
if ENABLE_MAN
@@ -19,8 +20,8 @@ bindctl.1: bindctl.xml
endif
-bindctl: bindctl-source.py
+bindctl: bindctl_main.py
$(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
-e "s|@@SYSCONFDIR@@|@sysconfdir@|" \
- -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bindctl-source.py >$@
+ -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" bindctl_main.py >$@
chmod a+x $@
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 683dda9e4c..83dab25e75 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -87,7 +87,8 @@ class ValidatedHTTPSConnection(http.client.HTTPSConnection):
class BindCmdInterpreter(Cmd):
"""simple bindctl example."""
- def __init__(self, server_port = 'localhost:8080', pem_file = None):
+ def __init__(self, server_port='localhost:8080', pem_file=None,
+ csv_file_dir=None):
Cmd.__init__(self)
self.location = ""
self.prompt_end = '> '
@@ -103,7 +104,12 @@ class BindCmdInterpreter(Cmd):
ca_certs=pem_file)
self.session_id = self._get_session_id()
self.config_data = None
-
+ if csv_file_dir is not None:
+ self.csv_file_dir = csv_file_dir
+ else:
+ self.csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir + \
+ os.sep + '.bind10' + os.sep
+
def _get_session_id(self):
'''Generate one session id for the connection. '''
rand = os.urandom(16)
@@ -175,9 +181,7 @@ class BindCmdInterpreter(Cmd):
time, username and password saved in 'default_user.csv' will be
used first.
'''
- csv_file_dir = pwd.getpwnam(getpass.getuser()).pw_dir
- csv_file_dir += os.sep + '.bind10' + os.sep
- users = self._get_saved_user_info(csv_file_dir, CSV_FILE_NAME)
+ users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
for row in users:
param = {'username': row[0], 'password' : row[1]}
try:
@@ -211,7 +215,8 @@ class BindCmdInterpreter(Cmd):
raise FailToLogin()
if response.status == http.client.OK:
- self._save_user_info(username, passwd, csv_file_dir, CSV_FILE_NAME)
+ self._save_user_info(username, passwd, self.csv_file_dir,
+ CSV_FILE_NAME)
return True
def _update_commands(self):
diff --git a/src/bin/bindctl/bindctl.xml b/src/bin/bindctl/bindctl.xml
index 98d65f95da..eff1de2c18 100644
--- a/src/bin/bindctl/bindctl.xml
+++ b/src/bin/bindctl/bindctl.xml
@@ -51,6 +51,7 @@
+
@@ -109,6 +110,22 @@
+
+
+ file
+
+
+
+
+ The directory name in which the user/password CSV file
+ is stored (see AUTHENTICATION).
+ By default this option doesn't have any value,
+ in which case the ".bind10" directory under the user's
+ home directory will be used.
+
+
+
+
,
@@ -148,8 +165,10 @@
The tool will authenticate using a username and password.
On the first successful login, it will save the details to
- ~/.bind10/default_user.csv
+ a comma-separated-value (CSV) file
which will be used for later uses of bindctl.
+ The file name is default_user.csv
+ located under the directory specified by the --csv-file-dir option.
diff --git a/src/bin/bindctl/bindctl-source.py.in b/src/bin/bindctl/bindctl_main.py.in
old mode 100644
new mode 100755
similarity index 86%
rename from src/bin/bindctl/bindctl-source.py.in
rename to src/bin/bindctl/bindctl_main.py.in
index 080c3bca83..01307e9798
--- a/src/bin/bindctl/bindctl-source.py.in
+++ b/src/bin/bindctl/bindctl_main.py.in
@@ -111,25 +111,28 @@ def check_addr(option, opt_str, value, parser):
parser.values.addr = value
def set_bindctl_options(parser):
- parser.add_option('-p', '--port', dest = 'port', type = 'int',
- action = 'callback', callback=check_port,
- default = '8080', help = 'port for cmdctl of bind10')
+ parser.add_option('-p', '--port', dest='port', type='int',
+ action='callback', callback=check_port,
+ default='8080', help='port for cmdctl of bind10')
- parser.add_option('-a', '--address', dest = 'addr', type = 'string',
- action = 'callback', callback=check_addr,
- default = '127.0.0.1', help = 'IP address for cmdctl of bind10')
+ parser.add_option('-a', '--address', dest='addr', type='string',
+ action='callback', callback=check_addr,
+ default='127.0.0.1', help='IP address for cmdctl of bind10')
- parser.add_option('-c', '--certificate-chain', dest = 'cert_chain',
- type = 'string', action = 'store',
- help = 'PEM formatted server certificate validation chain file')
+ parser.add_option('-c', '--certificate-chain', dest='cert_chain',
+ type='string', action='store',
+ help='PEM formatted server certificate validation chain file')
+
+ parser.add_option('--csv-file-dir', dest='csv_file_dir', type='string',
+ default=None, action='store',
+ help='Directory to store the password CSV file')
if __name__ == '__main__':
parser = OptionParser(version = VERSION)
set_bindctl_options(parser)
(options, args) = parser.parse_args()
server_addr = options.addr + ':' + str(options.port)
- tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain)
+ tool = BindCmdInterpreter(server_addr, pem_file=options.cert_chain,
+ csv_file_dir=options.csv_file_dir)
prepare_config_commands(tool)
tool.run()
-
-
diff --git a/src/bin/bindctl/tests/Makefile.am b/src/bin/bindctl/tests/Makefile.am
index 8a7a6237b9..d2bb90fe71 100644
--- a/src/bin/bindctl/tests/Makefile.am
+++ b/src/bin/bindctl/tests/Makefile.am
@@ -11,6 +11,6 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_srcdir)/src/bin \
+ env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bindctl:$(abs_top_srcdir)/src/bin \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index 490dd7a09c..dd492c11f5 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -17,8 +17,12 @@
import unittest
import isc.cc.data
import os
+import pwd
+import getpass
+from optparse import OptionParser
from isc.config.config_data import ConfigData, MultiConfigData
from isc.config.module_spec import ModuleSpec
+from bindctl_main import set_bindctl_options
from bindctl import cmdparse
from bindctl import bindcmd
from bindctl.moduleinfo import *
@@ -332,13 +336,6 @@ class TestConfigCommands(unittest.TestCase):
cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=[1]")
self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
-
-
-
-class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
- def __init__(self):
- pass
-
class TestBindCmdInterpreter(unittest.TestCase):
def _create_invalid_csv_file(self, csvfilename):
@@ -349,9 +346,22 @@ class TestBindCmdInterpreter(unittest.TestCase):
writer.writerow(['name2'])
csvfile.close()
+ def test_csv_file_dir(self):
+ # Checking default value
+ if "HOME" in os.environ:
+ home_dir = os.environ["HOME"]
+ else:
+ home_dir = pwd.getpwnam(getpass.getuser()).pw_dir
+ self.assertEqual(home_dir + os.sep + '.bind10' + os.sep,
+ bindcmd.BindCmdInterpreter().csv_file_dir)
+
+ new_csv_dir = '/something/different/'
+ custom_cmd = bindcmd.BindCmdInterpreter(csv_file_dir=new_csv_dir)
+ self.assertEqual(new_csv_dir, custom_cmd.csv_file_dir)
+
def test_get_saved_user_info(self):
- cmd = FakeBindCmdInterpreter()
- users = cmd._get_saved_user_info('/notexist', 'cvs_file.cvs')
+ cmd = bindcmd.BindCmdInterpreter()
+ users = cmd._get_saved_user_info('/notexist', 'csv_file.csv')
self.assertEqual([], users)
csvfilename = 'csv_file.csv'
@@ -360,6 +370,40 @@ class TestBindCmdInterpreter(unittest.TestCase):
self.assertEqual([], users)
os.remove(csvfilename)
+
+class TestCommandLineOptions(unittest.TestCase):
+ class FakeParserError(Exception):
+ """An exception thrown from FakeOptionParser on parser error.
+ """
+ pass
+
+ class FakeOptionParser(OptionParser):
+ """This fake class emulates the OptionParser class with customized
+ error handling for the convenient of tests.
+ """
+ def __init__(self):
+ OptionParser.__init__(self)
+
+ def error(self, msg):
+ raise TestCommandLineOptions.FakeParserError
+
+ def setUp(self):
+ self.parser = self.FakeOptionParser()
+ set_bindctl_options(self.parser)
+
+ def test_csv_file_dir(self):
+ # by default the option is "undefined"
+ (options, _) = self.parser.parse_args([])
+ self.assertEqual(None, options.csv_file_dir)
+
+ # specify the option, valid case.
+ (options, _) = self.parser.parse_args(['--csv-file-dir', 'some_dir'])
+ self.assertEqual('some_dir', options.csv_file_dir)
+
+ # missing option arg; should trigger parser error.
+ self.assertRaises(self.FakeParserError, self.parser.parse_args,
+ ['--csv-file-dir'])
+
if __name__== "__main__":
unittest.main()
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index 272a17e7e3..41a6790c71 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -27,10 +27,18 @@ from optparse import OptionParser
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_LOCALSTATEDIR. Otherwise
+# we use the ones installed on the system.
+# 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:
- DATA_PATH = os.environ["B10_FROM_SOURCE"]
+ 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:
PREFIX = "@prefix@"
DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
diff --git a/src/bin/resolver/b10-resolver.8 b/src/bin/resolver/b10-resolver.8
index 3125e32061..849092c007 100644
--- a/src/bin/resolver/b10-resolver.8
+++ b/src/bin/resolver/b10-resolver.8
@@ -74,7 +74,7 @@ to listen on\&. The list items are the
\fIaddress\fR
string and
\fIport\fR
-number\&. The defaults are address ::1 port 5300 and address 127\&.0\&.0\&.1 port 5300\&.
+number\&. The defaults are address ::1 port 53 and address 127\&.0\&.0\&.1 port 53\&.
.PP
\fIretries\fR
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
index 0d395a71dc..bdf4f8ad25 100644
--- a/src/bin/resolver/b10-resolver.xml
+++ b/src/bin/resolver/b10-resolver.xml
@@ -141,8 +141,9 @@ once that is merged you can for instance do 'config add Resolver/forward_address
b10-resolver to listen on.
The list items are the address string
and port number.
- The defaults are address ::1 port 5300 and
- address 127.0.0.1 port 5300.
+ The defaults are address ::1 port 53 and
+ address 127.0.0.1 port 53.
+
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index 03f9ab7cb5..d987c74ee9 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -56,7 +56,6 @@ using namespace asiolink;
namespace {
-// Default port current 5300 for testing purposes
static const string PROGRAM = "Resolver";
IOService io_service;
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index 4996b710b3..84df9d2c07 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/resolver.spec.pre.in b/src/bin/resolver/resolver.spec.pre.in
index bc598b0263..9df1e752d3 100644
--- a/src/bin/resolver/resolver.spec.pre.in
+++ b/src/bin/resolver/resolver.spec.pre.in
@@ -86,11 +86,11 @@
"item_default": [
{
"address": "::1",
- "port": 5300
+ "port": 53
},
{
"address": "127.0.0.1",
- "port": 5300
+ "port": 53
}
],
"list_item_spec": {
@@ -109,7 +109,7 @@
"item_name": "port",
"item_type": "integer",
"item_optional": false,
- "item_default": 5300
+ "item_default": 53
}
]
}
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);
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@"
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
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index b3968f0661..b28b7847dd 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -13,28 +13,34 @@ 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
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 += qid_gen.cc qid_gen.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)
@@ -46,7 +52,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..94c71b5f46
--- /dev/null
+++ b/src/lib/asiolink/asiodef.cc
@@ -0,0 +1,37 @@
+// File created from asiodef.msg on Mon Feb 28 17:15:30 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(%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(%d)",
+ 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..ba77817974
--- /dev/null
+++ b/src/lib/asiolink/asiodef.h
@@ -0,0 +1,21 @@
+// File created from asiodef.msg on Mon Feb 28 17:15:30 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..2fcadd1f54
--- /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(%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(%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(%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 %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 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(%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
++ 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(%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/asiolink.h b/src/lib/asiolink/asiolink.h
index 03951ae9df..251413ec04 100644
--- a/src/lib/asiolink/asiolink.h
+++ b/src/lib/asiolink/asiolink.h
@@ -85,7 +85,3 @@
/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
#endif // __ASIOLINK_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/asiolink_utilities.h b/src/lib/asiolink/asiolink_utilities.h
new file mode 100644
index 0000000000..659e6a0908
--- /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/dns_server.h b/src/lib/asiolink/dns_server.h
index 352ea8e57c..f15f808e6e 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(),
@@ -87,7 +87,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/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/internal/iofetch.h b/src/lib/asiolink/internal/iofetch.h
deleted file mode 100644
index d066c923c0..0000000000
--- a/src/lib/asiolink/internal/iofetch.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef __IOFETCH_H
-#define __IOFETCH_H 1
-
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-#include
-
-#include
-#include
-
-// This file contains TCP/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/TCP coroutine for upstream fetches
-//
-//class IOFetch : public coroutine, public UdpFetch, public TcpFetch {
-class IOFetch : public coroutine {
-public:
- // TODO Maybe this should be more generic than just for IOFetch?
- ///
- /// \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 IOFetch.
- class Callback {
- public:
- virtual ~Callback() {}
-
- /// This will be called when the IOFetch 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.
- ///
- IOFetch(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,
- int protocol = IPPROTO_UDP);
- 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 IOFetchProtocol;
- //boost::shared_ptr data_;
- //struct UdpData;
- //struct TcpData;
- boost::shared_ptr data_;
- boost::shared_ptr tcp_data_;
-};
-class UdpFetch : public IOFetch {
- public:
- struct UdpData;
- explicit UdpFetch(asio::io_service& io_service,
- const isc::dns::Question& q,
- const IOAddress& addr,
- uint16_t port,
- isc::dns::OutputBufferPtr buffer,
- IOFetch::Callback *callback,
- int timeout);
-};
-class TcpFetch : public IOFetch {
- public:
- struct TcpData;
- explicit TcpFetch(io_service& io_service, const Question& q,
- const IOAddress& addr, uint16_t port,
- OutputBufferPtr buffer, Callback *callback, int timeout);
-};
-
-}
-
-
-#endif // __IOFETCH_H
-
-// Local Variables:
-// mode: c++
-// End:
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..d7598276bc 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -121,7 +121,3 @@ private:
} // asiolink
#endif // __IO_ADDRESS_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
index eae9b32509..ac793a6af7 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
@@ -41,7 +43,24 @@ public:
IOError(file, line, what) {}
};
+/// \brief Error setting socket options
+///
+/// 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) {}
+};
+/// \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;
@@ -91,24 +110,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.
@@ -118,36 +136,50 @@ 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 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
- /// 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
/// 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 "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 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).
///
/// \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.
- ///
- /// \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;
+ /// 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
///
@@ -160,44 +192,85 @@ 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
///
- /// 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.
///
/// \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. 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 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.
+ /// \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 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.
+ /// 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 (although it does update the amount received).
+ /// 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 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.
+ /// 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(void* data, size_t length,
- size_t& cumulative) = 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;
@@ -244,6 +317,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
@@ -273,21 +353,31 @@ 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&) { }
+ virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) {
+ }
+
/// \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 Cancel I/O On AsioSocket
///
/// Must be supplied as it is abstract in the base class.
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_endpoint.h b/src/lib/asiolink/io_endpoint.h
index 62b9e47942..2ec4083583 100644
--- a/src/lib/asiolink/io_endpoint.h
+++ b/src/lib/asiolink/io_endpoint.h
@@ -116,7 +116,3 @@ public:
} // asiolink
#endif // __IO_ENDPOINT_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc
index d1f722cf86..3ff44c048b 100644
--- a/src/lib/asiolink/io_fetch.cc
+++ b/src/lib/asiolink/io_fetch.cc
@@ -19,15 +19,30 @@
#include
#include
+#include
+#include
#include
#include
#include
#include
-#include
+#include
+
+#include
#include
+#include
+
+#include
+#include
+#include
+#include
#include
+#include
+#include
+#include
+#include
+#include
using namespace asio;
using namespace isc::dns;
@@ -36,23 +51,125 @@ using namespace std;
namespace asiolink {
+/// Use the ASIO logger
+
+isc::log::Logger logger("asiolink");
+
+/// \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::scoped_ptr > socket;
+ ///< Socket to use for I/O
+ 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
+ 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
+ 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
+
+ // 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
+ uint8_t staging[IOFetch::STAGING_LENGTH];
+ ///< Temporary array for received data
+
+ /// \brief Constructor
+ ///
+ /// Just fills in the data members of the IOFetchData structure
+ ///
+ /// \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.
+ /// \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 proto, IOService& service,
+ const isc::dns::Question& query, const IOAddress& address,
+ uint16_t port, isc::dns::OutputBufferPtr& buff, IOFetch::Callback* cb,
+ int wait)
+ :
+ socket((proto == IOFetch::UDP) ?
+ static_cast*>(
+ new UDPSocket(service)) :
+ static_cast*>(
+ new TCPSocket(service))
+ ),
+ remote((proto == IOFetch::UDP) ?
+ static_cast(new UDPEndpoint(address, port)) :
+ static_cast(new TCPEndpoint(address, port))
+ ),
+ question(query),
+ msgbuf(new isc::dns::OutputBuffer(512)),
+ received(buff),
+
+ callback(cb),
+ timer(service.get_io_service()),
+ protocol(proto),
+ cumulative(0),
+ expected(0),
+ offset(0),
+ stopped(false),
+ timeout(wait),
+ origin(ASIO_UNKORIGIN),
+ staging()
+ {}
+};
+
/// 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)
+ 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))
{
}
+// 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.
void
-IOFetch::operator()(error_code ec, size_t length) {
- if (ec || data_->stopped) {
+IOFetch::operator()(asio::error_code ec, size_t length) {
+
+ if (data_->stopped) {
+ return;
+ } else if (ec) {
+ logIOFailure(ec);
return;
}
@@ -63,26 +180,17 @@ IOFetch::operator()(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.setQid(QidGenerator::getInstance().generateQid());
msg.setOpcode(Opcode::QUERY());
msg.setRcode(Rcode::NOERROR());
msg.setHeaderFlag(Message::HEADERFLAG_RD);
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;
-
- dlog("Sending " + msg.toText() + " to " +
- data_->remote->getAddress().toText());
}
-
- // If we timeout, we stop, which will shutdown everything and
- // cancel all other attempts to run inside the coroutine
+ // If 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));
@@ -91,13 +199,17 @@ 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)) {
- CORO_YIELD;
+ // is synchronous (i.e. UDP operation) we bypass the yield.
+ data_->origin = ASIO_OPENSOCK;
+ if (data_->socket->isOpenSynchronous()) {
+ data_->socket->open(data_->remote.get(), *this);
+ } else {
+ CORO_YIELD data_->socket->open(data_->remote.get(), *this);
}
- // Begin an asynchronous send, and then yield. When the send completes
- // send completes, we will resume immediately after this point.
+ // 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);
@@ -108,24 +220,33 @@ 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.
+ //
+ // 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_->data.get(),
- static_cast(MAX_LENGTH), data_->cumulative,
- data_->remote.get(), *this);
- } while (!data_->socket->receiveComplete(data_->data.get(), length,
- data_->cumulative));
+ CORO_YIELD data_->socket->asyncReceive(data_->staging,
+ static_cast(STAGING_LENGTH),
+ data_->offset,
+ data_->remote.get(), *this);
+ } while (!data_->socket->processReceivedData(data_->staging, length,
+ data_->cumulative, data_->offset,
+ data_->expected, data_->received));
- // 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.)
- data_->buffer->writeData(data_->data.get(), length);
-
- // Finished with this socket, so close it.
+ // 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
@@ -137,9 +258,8 @@ IOFetch::operator()(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) {
@@ -156,20 +276,46 @@ 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
+ // below before calling Logger::debug 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(20, ASIO_RECVTMO,
+ data_->remote->getAddress().toText().c_str(),
+ static_cast(data_->remote->getPort()));
+ }
+ break;
+
+ case SUCCESS:
+ if (logger.isDebugEnabled(50)) {
+ logger.debug(30, ASIO_FETCHCOMP,
+ data_->remote->getAddress().toText().c_str(),
+ static_cast(data_->remote->getPort()));
+ }
break;
case STOPPED:
- dlog("Query 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(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(),
+ static_cast(data_->remote->getPort()));
}
// Stop requested, cancel and I/O's on the socket and shut it down,
@@ -183,11 +329,27 @@ IOFetch::stop(Result result) {
if (data_->callback) {
(*(data_->callback))(result);
}
-
- // Mark that stop() has now been called.
-
}
}
+// Log an error - called on I/O failure
+
+void IOFetch::logIOFailure(asio::error_code ec) {
+
+ // 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"};
+ 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/io_fetch.h b/src/lib/asiolink/io_fetch.h
index 8158c6c05c..0723777e11 100644
--- a/src/lib/asiolink/io_fetch.h
+++ b/src/lib/asiolink/io_fetch.h
@@ -17,31 +17,23 @@
#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
///
@@ -51,6 +43,23 @@ 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
+ /// 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
///
@@ -59,9 +68,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
@@ -69,7 +78,7 @@ public:
/// \brief Integer Constants
enum {
- MAX_LENGTH = 4096 ///< Maximum size of receive buffer
+ STAGING_LENGTH = 8192 ///< Size of staging buffer
};
/// \brief I/O Fetch Callback
@@ -95,91 +104,21 @@ public:
virtual ~Callback()
{}
- /// \brief Callback method called when the fetch completes
+ /// \brief Callback method
///
- /// \brief result Result of the fetch
+ /// This is the method called when the fetch 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
-
- /// \brief Constructor
- ///
- /// Just fills in the data members of the IOFetchData structure
- ///
- /// \param protocol either IPPROTO_UDP or IPPROTO_TCP
- /// \param service I/O Service object to handle the asynchronous
- /// operations.
- /// \param query DNS question to send to the upstream server.
- /// \param address IP address of upstream server
- /// \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(int protocol, IOService& service,
- const isc::dns::Question& query, const IOAddress& address,
- uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
- int wait)
- :
- socket((protocol == IPPROTO_UDP) ?
- static_cast*>(
- new UDPSocket(service)) :
- static_cast*>(
- new TCPSocket(service))
- ),
- remote((protocol == IPPROTO_UDP) ?
- static_cast(new UDPEndpoint(address, port)) :
- static_cast(new TCPEndpoint(address, port))
- ),
- question(query),
- msgbuf(new isc::dns::OutputBuffer(512)),
- buffer(buff),
- data(new char[IOFetch::MAX_LENGTH]),
- callback(cb),
- cumulative(0),
- stopped(false),
- timer(service.get_io_service()),
- timeout(wait)
- {}
- };
-
/// \brief Constructor.
///
/// Creates the object that will handle the upstream fetch.
///
/// 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.
@@ -193,11 +132,16 @@ 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 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
@@ -205,8 +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 = asio::error_code(),
- size_t length = 0);
+ void operator()(asio::error_code ec = asio::error_code(), size_t length = 0);
/// \brief Terminate query
///
@@ -217,6 +160,16 @@ 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);
+
+ // 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/io_message.h b/src/lib/asiolink/io_message.h
index 532f4492d9..e857bd9f1c 100644
--- a/src/lib/asiolink/io_message.h
+++ b/src/lib/asiolink/io_message.h
@@ -98,7 +98,3 @@ private:
} // asiolink
#endif // __IO_MESSAGE_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/qid_gen.cc b/src/lib/asiolink/qid_gen.cc
new file mode 100644
index 0000000000..4063b39511
--- /dev/null
+++ b/src/lib/asiolink/qid_gen.cc
@@ -0,0 +1,54 @@
+// 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;
+}
+
+namespace asiolink {
+
+QidGenerator&
+QidGenerator::getInstance() {
+ return (qid_generator_instance);
+}
+
+QidGenerator::QidGenerator() : dist_(0, 65535),
+ vgen_(generator_, dist_)
+{
+ seed();
+}
+
+void
+QidGenerator::seed() {
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ generator_.seed((tv.tv_sec * 1000000) + tv.tv_usec);
+}
+
+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..a5caa17d83
--- /dev/null
+++ b/src/lib/asiolink/qid_gen.h
@@ -0,0 +1,85 @@
+// 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_H
+#define __QID_GEN_H
+
+#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
+ ///
+ /// Returns a reference to the singleton instance of the generator
+ static QidGenerator& getInstance();
+
+ /// \brief Default constructor
+ ///
+ /// 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
+ ///
+ /// \return A random Qid
+ isc::dns::qid_t generateQid();
+
+ /// \brief Seeds the QidGenerator (based on the current time)
+ ///
+ /// This is automatically called by the constructor
+ void seed();
+
+private:
+ // "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_H
diff --git a/src/lib/asiolink/recursive_query.cc b/src/lib/asiolink/recursive_query.cc
index 0bdf24e68a..406b176621 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(IPPROTO_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(IPPROTO_UDP, io_, question_,
+ IOFetch query(protocol, io_, question_,
zone_servers_.at(serverIndex).first,
zone_servers_.at(serverIndex).second, buffer_, this,
query_timeout_);
@@ -203,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;
@@ -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);
+ 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.
+ // 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/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_server.cc b/src/lib/asiolink/tcp_server.cc
index e77828c029..3e0cdb420c 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
@@ -65,7 +66,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.
@@ -83,11 +84,21 @@ 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) {
+ using namespace asio::error;
+ if (ec.value() != would_block && ec.value() != try_again &&
+ ec.value() != connection_aborted &&
+ ec.value() != interrupted) {
+ return;
+ }
+ }
+ } while (ec);
/// Fork the coroutine by creating a copy of this one and
/// scheduling it on the ASIO service queue. The parent
@@ -110,7 +121,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);
}
@@ -196,9 +207,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/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index 5a85aaa633..fcbf3b72b0 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -24,11 +24,18 @@
#include
#include // for some IPC/network system calls
-#include
+#include
+#include
#include
+#include
+#include
+
#include
+#include
+
+#include
#include
#include
#include
@@ -36,6 +43,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.
///
@@ -48,18 +64,18 @@ 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 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);
@@ -67,68 +83,79 @@ 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 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 callback Unused.
- ///
- /// \return false to indicate that the "operation" completed synchronously.
- virtual bool open(const IOEndpoint* endpoint, C&);
+ /// \param endpoint Endpoint to which the socket will connect.
+ /// \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
- /// \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);
+ const IOEndpoint* endpoint, C& callback);
/// \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.
+ /// \brief Process received data packet
///
- /// 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).
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
///
- /// \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 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 another receive is
- /// needed.
- virtual bool receiveComplete(void*, size_t length, size_t& cumulative) {
- cumulative = length;
- return (true);
- }
+ /// \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();
@@ -144,13 +171,28 @@ 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 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:
+ // 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
};
// 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 +213,14 @@ 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.
-
if (!isopen_) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(asio::ip::tcp::v4());
@@ -190,35 +230,55 @@ 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
+ 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,
@@ -226,26 +286,40 @@ TCPSocket::asyncSend(const void* data, size_t length,
}
}
-// 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);
- // TODO: Complete TCPSocket::asyncReceive()
+ // 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
+ // 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();
+
+ // 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,
@@ -253,7 +327,72 @@ TCPSocket::asyncReceive(void* data, size_t length, size_t,
}
}
+// Is the receive complete?
+
+template bool
+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;
+
+ // 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) {
+
+ // "expected" is not valid. Did this read give us enough data to
+ // work it out?
+ cumulative += length;
+ if (cumulative < 2) {
+
+ // 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;
+ }
+
+ // Regardless of anything else, the next read goes into the start of the
+ // staging buffer.
+ offset = 0;
+
+ // 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()) {
+
+ // 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.
+
template void
TCPSocket::cancel() {
if (isopen_) {
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index b4f0a87739..3a229f302a 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
@@ -25,19 +26,24 @@ 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
run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += qid_gen_unittest.cc
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)
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/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
index 534850a6bf..6101473ec1 100644
--- a/src/lib/asiolink/tests/io_endpoint_unittest.cc
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -22,9 +22,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());
@@ -62,7 +62,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/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc
index 9b74ee0a1b..901df45d2f 100644
--- a/src/lib/asiolink/tests/io_fetch_unittest.cc
+++ b/src/lib/asiolink/tests/io_fetch_unittest.cc
@@ -12,13 +12,17 @@
// 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
@@ -30,19 +34,27 @@
#include
#include
+#include
+#include
+#include
#include
#include
using namespace asio;
using namespace isc::dns;
-using asio::ip::udp;
+using namespace asio::ip;
+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);
-// FIXME Shouldn't we send something that is real message?
-const char TEST_DATA[] = "TEST DATA";
+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.
+const bool DEBUG = false;
/// \brief Test fixture for the asiolink::IOFetch.
class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
@@ -52,13 +64,26 @@ 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
+ size_t cumulative_; ///< Cumulative data received by "server".
+ deadline_timer timer_; ///< Timer to measure timeouts
- // The next member is the buffer iin which the "server" (implemented by the
- // response handler method) receives the question sent by the fetch object.
- std::vector server_buff_; ///< 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 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
+ size_t tcp_send_size_; ///< Max size of TCP send
/// \brief Constructor
IOFetchTest() :
@@ -66,126 +91,518 @@ public:
expected_(IOFetch::NOTSET),
run_(false),
question_(Name("example.net"), RRClass::IN(), RRType::A()),
- buff_(new OutputBuffer(512)),
- udp_fetch_(IPPROTO_UDP, service_, question_, IOAddress(TEST_HOST),
- TEST_PORT, buff_, this, 100),
- server_buff_(512)
- // 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) {
-
+ result_buff_(new OutputBuffer(512)),
+ msgbuf_(new OutputBuffer(512)),
+ 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, (16 * SEND_INTERVAL)),
+ // Timeout interval chosen to ensure no timeout
+ protocol_(IOFetch::TCP), // for initialization - will be changed
+ cumulative_(0),
+ timer_(service_.get_io_service()),
+ receive_buffer_(),
+ send_buffer_(),
+ send_cumulative_(0),
+ return_data_(""),
+ test_data_(""),
+ debug_(DEBUG),
+ tcp_send_size_(0)
+ {
// Construct the data buffer for question we expect to receive.
- OutputBuffer msgbuf(512);
Message msg(Message::RENDER);
msg.setQid(0);
msg.setOpcode(Opcode::QUERY());
msg.setRcode(Rcode::NOERROR());
msg.setHeaderFlag(Message::HEADERFLAG_RD);
msg.addQuestion(question_);
- MessageRenderer renderer(msgbuf);
+ 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")
+ ///
+ /// 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) {
+ 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 was set to 0 when the buffer containing
- // the expected data was constructed above.)
- server_buff_[0] = 0;
- server_buff_[1] = 0;
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ receive_buffer_[0] = receive_buffer_[1] = 0;
- // Check that lengths are identical.
- EXPECT_EQ(msgbuf.getLength(), length);
- EXPECT_TRUE(memcmp(msgbuf.getData(), &server_buff_[0], 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(equal(receive_buffer_, (receive_buffer_ + length - 1),
+ static_cast(msgbuf_->getData())));
- // ... and return a message back.
- socket->send_to(asio::buffer(TEST_DATA, sizeof TEST_DATA), *remote);
+ // Return a message back to the IOFetch object.
+ socket->send_to(asio::buffer(return_data_.c_str(), return_data_.size()),
+ *remote);
+ if (debug_) {
+ cout << "udpReceiveHandler(): returned " << return_data_.size() <<
+ " bytes to the client" << endl;
+ }
+ }
+
+ /// \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())
+ {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Work out the maximum size of data we can send over it when we
+ // respond, then subtract 1kB or so for safety.
+ tcp::socket::send_buffer_size send_size;
+ socket->get_option(send_size);
+ if (send_size.value() < (2 * 1024)) {
+ FAIL() << "TCP send size is less than 2kB";
+ } else {
+ tcp_send_size_ = send_size.value() - 1024;
+ if (debug_) {
+ cout << "tcpacceptHandler(): will use send size = " << tcp_send_size_ << endl;
+ }
+ }
+
+ // Initiate a read on the socket.
+ cumulative_ = 0;
+ socket->async_receive(asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
+ 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 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
+ /// 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)
+ {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // If we haven't received all the data, issue another read.
+ cumulative_ += length;
+ bool complete = false;
+ if (cumulative_ > 2) {
+ uint16_t dns_length = readUint16(receive_buffer_);
+ complete = ((dns_length + 2) == cumulative_);
+ }
+
+ if (!complete) {
+ socket->async_receive(asio::buffer((receive_buffer_ + cumulative_),
+ (sizeof(receive_buffer_) - 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 third and fourth
+ // bytes.
+ EXPECT_EQ(msgbuf_->getLength() + 2, cumulative_);
+ 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.
+ 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.
+ 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. (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) {
+ if (debug_) {
+ cout << "tcpSendData()" << endl;
+ }
+
+ // 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_ < (2 * amount)) {
+
+ // First or second time through, send at most 16 bytes
+ amount = min(amount, (send_buffer_.size() - send_cumulative_));
+
+ } else {
+
+ // For all subsequent times, send the remainder, maximised to
+ // whatever we have chosen for the maximum send size.
+ amount = min(tcp_send_size_,
+ (send_buffer_.size() - send_cumulative_));
+ }
+ if (debug_) {
+ cout << "tcpSendData(): sending " << amount << " bytes" << endl;
+ }
+
+
+ // ... 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,
+ amount, socket, _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.)
+ ///
+ /// 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, 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_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
+ // 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
+ ///
+ /// 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) {
+ 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 return_data_.
+ // Check the data is as expected/
+ if (expected_ == IOFetch::SUCCESS) {
+ EXPECT_EQ(return_data_.size(), result_buff_->getLength());
+
+ const uint8_t* start = static_cast(result_buff_->getData());
+ EXPECT_TRUE(equal(return_data_.begin(), return_data_.end(), start));
+ }
+
+ // ... 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_);
+ }
+
+ /// \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) {
+ if (debug_) {
+ cout << "tcpSendReturnTest(): data size = " << return_data.size() << endl;
+ }
+ 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
+TEST_F(IOFetchTest, Protocol) {
+ EXPECT_EQ(IOFetch::UDP, udp_fetch_.getProtocol());
+ EXPECT_EQ(IOFetch::TCP, tcp_fetch_.getProtocol());
+}
-/// 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;
+ // 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));
+ return_data_ = "Message returned to the client";
udp::endpoint remote;
- socket.async_receive_from(asio::buffer(server_buff_),
+ socket.async_receive_from(asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
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_);
+ if (debug_) {
+ cout << "udpSendReceive: async_receive_from posted, waiting for callback" <<
+ endl;
+ }
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 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, TcpSendReceive0) {
+ tcpSendReturnTest(test_data_.substr(0, 0));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive1) {
+ tcpSendReturnTest(test_data_.substr(0, 1));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive15) {
+ tcpSendReturnTest(test_data_.substr(0, 15));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive16) {
+ tcpSendReturnTest(test_data_.substr(0, 16));
+}
+
+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
diff --git a/src/lib/asiolink/tests/io_service_unittest.cc b/src/lib/asiolink/tests/io_service_unittest.cc
index 28924d4b9b..779d03e88e 100644
--- a/src/lib/asiolink/tests/io_service_unittest.cc
+++ b/src/lib/asiolink/tests/io_service_unittest.cc
@@ -28,7 +28,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/qid_gen_unittest.cc b/src/lib/asiolink/tests/qid_gen_unittest.cc
new file mode 100644
index 0000000000..3ad8a03c51
--- /dev/null
+++ b/src/lib/asiolink/tests/qid_gen_unittest.cc
@@ -0,0 +1,59 @@
+// 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);
+}
+
+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));
+}
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..ce51bbf88e
--- /dev/null
+++ b/src/lib/asiolink/tests/recursive_query_unittest_2.cc
@@ -0,0 +1,642 @@
+// 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
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace asio;
+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
+/// www.example.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 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,
+/// regardless of the glue returned in referrals.
+
+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"; ///< 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
+class RecursiveQueryTest2 : public virtual ::testing::Test
+{
+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
+ };
+
+ // Common stuff
+ bool debug_; ///< Set true for debug print
+ IOService service_; ///< Service to run everything
+ DNSService dns_service_; ///< Resolver is part of "server"
+ 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
+
+ // 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
+ 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_remote_; ///< Endpoint for UDP receives
+ size_t udp_length_; ///< Expected length value
+ 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_, NULL, NULL, NULL),
+ question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
+ last_(NONE),
+ expected_(NONE),
+ question_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_cumulative_(0),
+ tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS), TEST_PORT),
+ tcp_length_(0),
+ tcp_receive_buffer_(),
+ tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_socket_(service_.get_io_service()),
+ udp_remote_(),
+ udp_length_(0),
+ udp_receive_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.
+ ///
+ /// \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) {
+ 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 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."));
+ msg.addRRset(Message::SECTION_AUTHORITY, org_ns);
+
+ 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_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);
+ }
+
+ /// \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) {
+ 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 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."));
+ 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(createRdata(RRType::A(), RRClass::IN(), "192.0.2.11"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1);
+
+ 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);
+ }
+
+ /// \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) {
+ 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::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.)
+ setReferralExampleOrg(msg);
+ }
+
+ /// \brief UDP Receive 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.
+ /// \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);
+ 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 question we received is what was expected.
+ 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.
+ 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);
+ if (debug_) {
+ cout << "udpReceiveHandler(): setting TC bit" << endl;
+ }
+ 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
+ udp_send_buffer_->clear();
+ MessageRenderer renderer(*udp_send_buffer_);
+ msg.toWire(renderer);
+
+ // 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));
+ }
+
+ /// \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.
+ ///
+ /// \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() <<
+ ", length = " << length << endl;
+ }
+
+ // Check send was OK
+ EXPECT_EQ(0, ec.value());
+ EXPECT_EQ(udp_length_, length);
+
+ // 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_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Accepting TCP Data
+ ///
+ /// 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
+ /// \param ec Boost error code, value should be zero.
+ 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());
+
+ // 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(&RecursiveQueryTest2::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 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
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ 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());
+
+ // 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) {
+ 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(&RecursiveQueryTest2::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 question we received is what was expected. Note that we
+ // have to ignore the two-byte header in order to parse the message.
+ 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
+ // QID in the returned message with what was in the received message.
+ Message msg(Message::RENDER);
+ 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;
+ 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
+ // 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);
+ 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
+ ///
+ /// 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_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_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, 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
+///
+/// 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), run_(false), status_(false), debug_(DEBUG_PRINT)
+ {}
+
+ /// \brief Destructor
+ virtual ~ResolverCallback()
+ {}
+
+ /// \brief Resolver Callback Success
+ ///
+ /// Called if the resolver detects that the call has succeeded.
+ ///
+ /// \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 resolution has failed.
+ virtual void failure() {
+ 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. 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(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(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);
+
+ // Set up callback for the tor eceive notification that the query has
+ // completed.
+ ResolverInterface::CallbackPtr
+ resolver_callback(new ResolverCallback(service_));
+
+ // Kick off the resolution process. We expect the first question to go to
+ // "root".
+ expected_ = UDP_ROOT;
+ query.resolve(question_, resolver_callback);
+ service_.run();
+
+ // 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
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());
}
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..f0a45eeaa4
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -0,0 +1,515 @@
+// 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