summaryrefslogtreecommitdiffstats
path: root/bin/tests/system/tcp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt12
-rw-r--r--bin/tests/system/tcp/ans6/ans.py157
-rw-r--r--bin/tests/system/tcp/clean.sh22
-rw-r--r--bin/tests/system/tcp/ns1/named.conf.in40
-rw-r--r--bin/tests/system/tcp/ns1/root.db24
-rw-r--r--bin/tests/system/tcp/ns2/example.db28
-rw-r--r--bin/tests/system/tcp/ns2/named.conf.in47
-rw-r--r--bin/tests/system/tcp/ns3/named.conf.in42
-rw-r--r--bin/tests/system/tcp/ns4/named.conf.in44
-rw-r--r--bin/tests/system/tcp/ns5/named.conf.in45
-rw-r--r--bin/tests/system/tcp/ns7/named.conf.in42
-rw-r--r--bin/tests/system/tcp/ns7/named.dropedns1
-rw-r--r--bin/tests/system/tcp/ns7/root.db24
-rw-r--r--bin/tests/system/tcp/setup.sh23
-rw-r--r--bin/tests/system/tcp/tests.sh203
-rw-r--r--bin/tests/system/tcp/tests_sh_tcp.py14
-rw-r--r--bin/tests/system/tcp/tests_tcp.py116
17 files changed, 884 insertions, 0 deletions
diff --git a/bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt b/bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt
new file mode 100644
index 0000000..7520c3a
--- /dev/null
+++ b/bin/tests/system/tcp/1996-alloc_dnsbuf-crash-test.pkt
@@ -0,0 +1,12 @@
+# Transaction ID
+0001
+# Standard query
+0000
+# Questions: 1, Additional: 1
+0001 0000 0000 0000
+# QNAME: www.isc.org
+03 697363 03 6F7267 00
+# Type: AXFR
+00fc
+# Class: IN
+0001
diff --git a/bin/tests/system/tcp/ans6/ans.py b/bin/tests/system/tcp/ans6/ans.py
new file mode 100644
index 0000000..4595ddc
--- /dev/null
+++ b/bin/tests/system/tcp/ans6/ans.py
@@ -0,0 +1,157 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+############################################################################
+#
+# This tool allows an arbitrary number of TCP connections to be made to the
+# specified service and to keep them open until told otherwise. It is
+# controlled by writing text commands to a TCP socket (default port: 5309).
+#
+# Currently supported commands:
+#
+# - open <COUNT> <HOST> <PORT>
+#
+# Opens <COUNT> TCP connections to <HOST>:<PORT> and keeps them open.
+# <HOST> must be an IP address (IPv4 or IPv6).
+#
+# - close <COUNT>
+#
+# Close the oldest <COUNT> previously established connections.
+#
+############################################################################
+
+from __future__ import print_function
+
+import datetime
+import errno
+import os
+import select
+import signal
+import socket
+import sys
+import time
+
+
+# Timeout for establishing all connections requested by a single 'open' command.
+OPEN_TIMEOUT = 2
+VERSION_QUERY = b"\x00\x1e\xaf\xb8\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07version\x04bind\x00\x00\x10\x00\x03"
+
+
+def log(msg):
+ print(datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S.%f ") + msg)
+
+
+def open_connections(active_conns, count, host, port):
+ queued = []
+ errors = []
+
+ try:
+ socket.inet_aton(host)
+ family = socket.AF_INET
+ except socket.error:
+ family = socket.AF_INET6
+
+ log("Opening %d connections..." % count)
+
+ for _ in range(count):
+ sock = socket.socket(family, socket.SOCK_STREAM)
+ sock.setblocking(0)
+ err = sock.connect_ex((host, port))
+ if err not in (0, errno.EINPROGRESS):
+ log("%s on connect for socket %s" % (errno.errorcode[err], sock))
+ errors.append(sock)
+ else:
+ queued.append(sock)
+
+ start = time.time()
+ while queued:
+ now = time.time()
+ time_left = OPEN_TIMEOUT - (now - start)
+ if time_left <= 0:
+ break
+ _, wsocks, _ = select.select([], queued, [], time_left)
+ for sock in wsocks:
+ queued.remove(sock)
+ err = sock.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
+ if err:
+ log("%s for socket %s" % (errno.errorcode[err], sock))
+ errors.append(sock)
+ else:
+ sock.send(VERSION_QUERY)
+ active_conns.append(sock)
+
+ if errors:
+ log("result=FAIL: %d connection(s) failed" % len(errors))
+ elif queued:
+ log("result=FAIL: Timed out, aborting %d pending connections" % len(queued))
+ for sock in queued:
+ sock.close()
+ else:
+ log("result=OK: Successfully opened %d connections" % count)
+
+
+def close_connections(active_conns, count):
+ log("Closing %s connections..." % "all" if count == 0 else str(count))
+ if count == 0:
+ count = len(active_conns)
+ for _ in range(count):
+ sock = active_conns.pop(0)
+ sock.close()
+ log("result=OK: Successfully closed %d connections" % count)
+
+
+def sigterm(*_):
+ log("SIGTERM received, shutting down")
+ os.remove("ans.pid")
+ sys.exit(0)
+
+
+def main():
+ active_conns = []
+
+ signal.signal(signal.SIGTERM, sigterm)
+
+ with open("ans.pid", "w") as pidfile:
+ print(os.getpid(), file=pidfile)
+
+ listenip = "10.53.0.6"
+ try:
+ port = int(os.environ["CONTROLPORT"])
+ except KeyError:
+ port = 5309
+
+ log("Listening on %s:%d" % (listenip, port))
+
+ ctlsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ ctlsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ ctlsock.bind((listenip, port))
+ ctlsock.listen(1)
+
+ while True:
+ (clientsock, _) = ctlsock.accept()
+ log("Accepted control connection from %s" % clientsock)
+ cmdline = clientsock.recv(512).decode("ascii").strip()
+ if cmdline:
+ log("Received command: %s" % cmdline)
+ cmd = cmdline.split()
+ if cmd[0] == "open":
+ count, host, port = cmd[1:]
+ open_connections(active_conns, int(count), host, int(port))
+ elif cmd[0] == "close":
+ (count,) = cmd[1:]
+ close_connections(active_conns, int(count))
+ else:
+ log("result=FAIL: Unknown command")
+ clientsock.close()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/bin/tests/system/tcp/clean.sh b/bin/tests/system/tcp/clean.sh
new file mode 100644
index 0000000..1ea5b60
--- /dev/null
+++ b/bin/tests/system/tcp/clean.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+rm -f ./*/named.memstats
+rm -f ./*/named.run
+rm -f ./*/named.conf
+rm -f ./*/named.stats*
+rm -f ans6/ans.run*
+rm -f dig.out*
+rm -f rndc.out*
+rm -f ns*/named.lock
+rm -f ns*/managed-keys.bind*
diff --git a/bin/tests/system/tcp/ns1/named.conf.in b/bin/tests/system/tcp/ns1/named.conf.in
new file mode 100644
index 0000000..010e754
--- /dev/null
+++ b/bin/tests/system/tcp/ns1/named.conf.in
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.1;
+ notify-source 10.53.0.1;
+ transfer-source 10.53.0.1;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.1; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+ notify yes;
+ statistics-file "named.stats";
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
diff --git a/bin/tests/system/tcp/ns1/root.db b/bin/tests/system/tcp/ns1/root.db
new file mode 100644
index 0000000..17780d1
--- /dev/null
+++ b/bin/tests/system/tcp/ns1/root.db
@@ -0,0 +1,24 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+. IN SOA gson.nominum.com. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+. NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.1
+
+example. NS ns2.example.
+ns2.example. A 10.53.0.2
diff --git a/bin/tests/system/tcp/ns2/example.db b/bin/tests/system/tcp/ns2/example.db
new file mode 100644
index 0000000..4d60ce3
--- /dev/null
+++ b/bin/tests/system/tcp/ns2/example.db
@@ -0,0 +1,28 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$ORIGIN .
+$TTL 300 ; 5 minutes
+example IN SOA mname1. . (
+ 1 ; serial
+ 20 ; refresh (20 seconds)
+ 20 ; retry (20 seconds)
+ 1814400 ; expire (3 weeks)
+ 3600 ; minimum (1 hour)
+ )
+example. NS ns2.example.
+ns2.example. A 10.53.0.2
+
+$ORIGIN example.
+a A 10.0.0.1
+ MX 10 mail.example.
+
+mail A 10.0.0.2
diff --git a/bin/tests/system/tcp/ns2/named.conf.in b/bin/tests/system/tcp/ns2/named.conf.in
new file mode 100644
index 0000000..0a97093
--- /dev/null
+++ b/bin/tests/system/tcp/ns2/named.conf.in
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.2;
+ notify-source 10.53.0.2;
+ transfer-source 10.53.0.2;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.2; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ notify yes;
+ statistics-file "named.stats";
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.2 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
+
+zone "example" {
+ type primary;
+ file "example.db";
+ allow-update { any; };
+};
diff --git a/bin/tests/system/tcp/ns3/named.conf.in b/bin/tests/system/tcp/ns3/named.conf.in
new file mode 100644
index 0000000..8516d72
--- /dev/null
+++ b/bin/tests/system/tcp/ns3/named.conf.in
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.3;
+ notify-source 10.53.0.3;
+ transfer-source 10.53.0.3;
+ port @PORT@;
+ directory ".";
+ pid-file "named.pid";
+ listen-on { 10.53.0.3; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ notify yes;
+};
+
+server 10.53.0.1 { tcp-only yes; };
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.3 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
diff --git a/bin/tests/system/tcp/ns4/named.conf.in b/bin/tests/system/tcp/ns4/named.conf.in
new file mode 100644
index 0000000..a7758cc
--- /dev/null
+++ b/bin/tests/system/tcp/ns4/named.conf.in
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.4;
+ notify-source 10.53.0.4;
+ transfer-source 10.53.0.4;
+ port @PORT@;
+ directory ".";
+ pid-file "named.pid";
+ listen-on { 10.53.0.4; };
+ listen-on-v6 { none; };
+ recursion yes;
+ dnssec-validation no;
+ notify yes;
+ forwarders { 10.53.0.2; };
+ forward only;
+};
+
+server 10.53.0.2 { tcp-only yes; };
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.4 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
diff --git a/bin/tests/system/tcp/ns5/named.conf.in b/bin/tests/system/tcp/ns5/named.conf.in
new file mode 100644
index 0000000..bd754f7
--- /dev/null
+++ b/bin/tests/system/tcp/ns5/named.conf.in
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+// NS5
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.5 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+
+options {
+ query-source address 10.53.0.5;
+ notify-source 10.53.0.5;
+ transfer-source 10.53.0.5;
+ port @PORT@;
+ directory ".";
+ pid-file "named.pid";
+ listen-on { 10.53.0.5; };
+ listen-on-v6 { none; };
+ tcp-listen-queue 32;
+ recursion yes;
+ notify yes;
+ tcp-clients 17;
+ dnssec-validation no;
+};
+
+zone "." {
+ type hint;
+ file "../../common/root.hint";
+};
diff --git a/bin/tests/system/tcp/ns7/named.conf.in b/bin/tests/system/tcp/ns7/named.conf.in
new file mode 100644
index 0000000..5441519
--- /dev/null
+++ b/bin/tests/system/tcp/ns7/named.conf.in
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+options {
+ query-source address 10.53.0.7;
+ notify-source 10.53.0.7;
+ transfer-source 10.53.0.7;
+ port @PORT@;
+ pid-file "named.pid";
+ listen-on { 10.53.0.7; };
+ listen-on-v6 { none; };
+ recursion no;
+ dnssec-validation no;
+ notify yes;
+ statistics-file "named.stats";
+ tcp-clients 1;
+ keep-response-order { any; };
+};
+
+key rndc_key {
+ secret "1234abcd8765";
+ algorithm @DEFAULT_HMAC@;
+};
+
+controls {
+ inet 10.53.0.7 port @CONTROLPORT@ allow { any; } keys { rndc_key; };
+};
+
+zone "." {
+ type primary;
+ file "root.db";
+};
diff --git a/bin/tests/system/tcp/ns7/named.dropedns b/bin/tests/system/tcp/ns7/named.dropedns
new file mode 100644
index 0000000..37dd9cf
--- /dev/null
+++ b/bin/tests/system/tcp/ns7/named.dropedns
@@ -0,0 +1 @@
+dropedns
diff --git a/bin/tests/system/tcp/ns7/root.db b/bin/tests/system/tcp/ns7/root.db
new file mode 100644
index 0000000..bb31741
--- /dev/null
+++ b/bin/tests/system/tcp/ns7/root.db
@@ -0,0 +1,24 @@
+; Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+;
+; SPDX-License-Identifier: MPL-2.0
+;
+; This Source Code Form is subject to the terms of the Mozilla Public
+; License, v. 2.0. If a copy of the MPL was not distributed with this
+; file, you can obtain one at https://mozilla.org/MPL/2.0/.
+;
+; See the COPYRIGHT file distributed with this work for additional
+; information regarding copyright ownership.
+
+$TTL 300
+. IN SOA gson.nominum.com. a.root.servers.nil. (
+ 2000042100 ; serial
+ 600 ; refresh
+ 600 ; retry
+ 1200 ; expire
+ 600 ; minimum
+ )
+. NS a.root-servers.nil.
+a.root-servers.nil. A 10.53.0.7
+
+example. NS ns2.example.
+ns2.example. A 10.53.0.2
diff --git a/bin/tests/system/tcp/setup.sh b/bin/tests/system/tcp/setup.sh
new file mode 100644
index 0000000..475f399
--- /dev/null
+++ b/bin/tests/system/tcp/setup.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+. ../conf.sh
+
+$SHELL clean.sh
+
+copy_setports ns1/named.conf.in ns1/named.conf
+copy_setports ns2/named.conf.in ns2/named.conf
+copy_setports ns3/named.conf.in ns3/named.conf
+copy_setports ns4/named.conf.in ns4/named.conf
+copy_setports ns5/named.conf.in ns5/named.conf
+copy_setports ns7/named.conf.in ns7/named.conf
diff --git a/bin/tests/system/tcp/tests.sh b/bin/tests/system/tcp/tests.sh
new file mode 100644
index 0000000..12ca679
--- /dev/null
+++ b/bin/tests/system/tcp/tests.sh
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+set -e
+
+# shellcheck source=../conf.sh
+. ../conf.sh
+
+dig_with_opts() {
+ "${DIG}" -p "${PORT}" "$@"
+}
+
+rndccmd() {
+ "${RNDC}" -p "${CONTROLPORT}" -c ../common/rndc.conf -s "$@"
+}
+
+status=0
+n=0
+
+n=$((n + 1))
+echo_i "initializing TCP statistics ($n)"
+ret=0
+rndccmd 10.53.0.1 stats || ret=1
+rndccmd 10.53.0.2 stats || ret=1
+mv ns1/named.stats ns1/named.stats.test$n
+mv ns2/named.stats ns2/named.stats.test$n
+ntcp10="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')"
+ntcp20="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')"
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking TCP request statistics (resolver) ($n)"
+ret=0
+dig_with_opts @10.53.0.3 txt.example. > dig.out.test$n
+sleep 1
+rndccmd 10.53.0.1 stats || ret=1
+rndccmd 10.53.0.2 stats || ret=1
+mv ns1/named.stats ns1/named.stats.test$n
+mv ns2/named.stats ns2/named.stats.test$n
+ntcp11="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')"
+ntcp21="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')"
+if [ "$ntcp10" -ge "$ntcp11" ]; then ret=1; fi
+if [ "$ntcp20" -ne "$ntcp21" ]; then ret=1; fi
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+n=$((n + 1))
+echo_i "checking TCP request statistics (forwarder) ($n)"
+ret=0
+dig_with_opts @10.53.0.4 txt.example. > dig.out.test$n
+sleep 1
+rndccmd 10.53.0.1 stats || ret=1
+rndccmd 10.53.0.2 stats || ret=1
+mv ns1/named.stats ns1/named.stats.test$n
+mv ns2/named.stats ns2/named.stats.test$n
+ntcp12="$(grep "TCP requests received" ns1/named.stats.test$n | tail -1 | awk '{print $1}')"
+ntcp22="$(grep "TCP requests received" ns2/named.stats.test$n | tail -1 | awk '{print $1}')"
+if [ "$ntcp11" -ne "$ntcp12" ]; then ret=1; fi
+if [ "$ntcp21" -ge "$ntcp22" ];then ret=1; fi
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+# -------- TCP high-water tests ----------
+refresh_tcp_stats() {
+ rndccmd 10.53.0.5 status > rndc.out.$n || ret=1
+ TCP_CUR="$(sed -n "s/^tcp clients: \([0-9][0-9]*\).*/\1/p" rndc.out.$n)"
+ TCP_LIMIT="$(sed -n "s/^tcp clients: .*\/\([0-9][0-9]*\)/\1/p" rndc.out.$n)"
+ TCP_HIGH="$(sed -n "s/^TCP high-water: \([0-9][0-9]*\)/\1/p" rndc.out.$n)"
+}
+
+# Send a command to the tool script listening on 10.53.0.6.
+send_command() {
+ nextpart ans6/ans.run > /dev/null
+ echo "$*" | send 10.53.0.6 "${CONTROLPORT}"
+ wait_for_log_peek 10 "result=" ans6/ans.run || ret=1
+ if ! nextpartpeek ans6/ans.run | grep -qF "result=OK"; then
+ return 1
+ fi
+}
+
+# Instructs ans6 to open $1 TCP connections to 10.53.0.5.
+open_connections() {
+ send_command "open" "${1}" 10.53.0.5 "${PORT}" || return 1
+}
+
+# Instructs ans6 to close $1 TCP connections to 10.53.0.5.
+close_connections() {
+ send_command "close" "${1}" || return 1
+}
+
+# Check TCP connections are working normally before opening
+# multiple connections
+n=$((n + 1))
+echo_i "checking TCP query repsonse ($n)"
+ret=0
+dig_with_opts +tcp @10.53.0.5 txt.example > dig.out.test$n
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+# Check TCP statistics after server startup before using them as a baseline for
+# subsequent checks.
+n=$((n + 1))
+echo_i "TCP high-water: check initial statistics ($n)"
+ret=0
+refresh_tcp_stats
+assert_int_equal "${TCP_CUR}" 0 "current TCP clients count" || ret=1
+# We compare initial tcp-highwater value with 1 because as part of the
+# system test startup, the script start.pl executes dig to check if target
+# named is running, and that increments tcp-quota by one.
+assert_int_equal "${TCP_HIGH}" 1 "tcp-highwater count" || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+# Ensure the TCP high-water statistic gets updated after some TCP connections
+# are established.
+n=$((n + 1))
+echo_i "TCP high-water: check value after some TCP connections are established ($n)"
+ret=0
+OLD_TCP_CUR="${TCP_CUR}"
+TCP_ADDED=9
+open_connections "${TCP_ADDED}" || ret=1
+check_stats_added() {
+ refresh_tcp_stats
+ assert_int_equal "${TCP_CUR}" $((OLD_TCP_CUR + TCP_ADDED)) "current TCP clients count" || return 1
+ assert_int_equal "${TCP_HIGH}" $((OLD_TCP_CUR + TCP_ADDED)) "TCP high-water value" || return 1
+}
+retry 2 check_stats_added || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+# Ensure the TCP high-water statistic remains unchanged after some TCP
+# connections are closed.
+n=$((n + 1))
+echo_i "TCP high-water: check value after some TCP connections are closed ($n)"
+ret=0
+OLD_TCP_CUR="${TCP_CUR}"
+OLD_TCP_HIGH="${TCP_HIGH}"
+TCP_REMOVED=5
+close_connections "${TCP_REMOVED}" || ret=1
+check_stats_removed() {
+ refresh_tcp_stats
+ assert_int_equal "${TCP_CUR}" $((OLD_TCP_CUR - TCP_REMOVED)) "current TCP clients count" || return 1
+ assert_int_equal "${TCP_HIGH}" "${OLD_TCP_HIGH}" "TCP high-water value" || return 1
+}
+retry 2 check_stats_removed || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+# Ensure the TCP high-water statistic never exceeds the configured TCP clients
+# limit.
+n=$((n + 1))
+echo_i "TCP high-water: ensure tcp-clients is an upper bound ($n)"
+ret=0
+open_connections $((TCP_LIMIT + 1)) || ret=1
+check_stats_limit() {
+ refresh_tcp_stats
+ assert_int_equal "${TCP_CUR}" "${TCP_LIMIT}" "current TCP clients count" || return 1
+ assert_int_equal "${TCP_HIGH}" "${TCP_LIMIT}" "TCP high-water value" || return 1
+}
+retry 2 check_stats_limit || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+# Check TCP connections are working normally before opening
+# multiple connections
+n=$((n + 1))
+echo_i "checking TCP response recovery ($n)"
+ret=0
+# "0" closes all connections
+close_connections 0 || ret=1
+dig_with_opts +tcp @10.53.0.5 txt.example > dig.out.test$n || ret=1
+grep "status: NXDOMAIN" dig.out.test$n > /dev/null || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+####################################################
+# NOTE: The next test resets the debug level to 1. #
+####################################################
+
+n=$((n + 1))
+echo_i "checking that BIND 9 doesn't crash on long TCP messages ($n)"
+ret=0
+# Avoid logging useless information.
+rndccmd 10.53.0.1 trace 1 || ret=1
+{ $PERL ../packet.pl -a "10.53.0.1" -p "${PORT}" -t tcp -r 300000 1996-alloc_dnsbuf-crash-test.pkt || ret=1 ; } | cat_i
+dig_with_opts +tcp @10.53.0.1 txt.example > dig.out.test$n || ret=1
+if [ $ret != 0 ]; then echo_i "failed"; fi
+status=$((status + ret))
+
+echo_i "exit status: $status"
+[ $status -eq 0 ] || exit 1
diff --git a/bin/tests/system/tcp/tests_sh_tcp.py b/bin/tests/system/tcp/tests_sh_tcp.py
new file mode 100644
index 0000000..b1d797c
--- /dev/null
+++ b/bin/tests/system/tcp/tests_sh_tcp.py
@@ -0,0 +1,14 @@
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+
+def test_tcp(run_tests_sh):
+ run_tests_sh()
diff --git a/bin/tests/system/tcp/tests_tcp.py b/bin/tests/system/tcp/tests_tcp.py
new file mode 100644
index 0000000..532b47b
--- /dev/null
+++ b/bin/tests/system/tcp/tests_tcp.py
@@ -0,0 +1,116 @@
+#!/usr/bin/python3
+
+# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+#
+# SPDX-License-Identifier: MPL-2.0
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at https://mozilla.org/MPL/2.0/.
+#
+# See the COPYRIGHT file distributed with this work for additional
+# information regarding copyright ownership.
+
+# pylint: disable=unused-variable
+
+import socket
+import struct
+import time
+
+import pytest
+
+pytest.importorskip("dns", minversion="2.0.0")
+import dns.message
+import dns.query
+
+
+TIMEOUT = 10
+
+
+def create_msg(qname, qtype, edns=-1):
+ msg = dns.message.make_query(qname, qtype, use_edns=edns)
+ return msg
+
+
+def timeout():
+ return time.time() + TIMEOUT
+
+
+def create_socket(host, port):
+ sock = socket.create_connection((host, port), timeout=10)
+ sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, True)
+ return sock
+
+
+def test_tcp_garbage(named_port):
+ with create_socket("10.53.0.7", named_port) as sock:
+ msg = create_msg("a.example.", "A")
+ (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+ (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+ wire = msg.to_wire()
+ assert len(wire) > 0
+
+ # Send DNS message shorter than DNS message header (12),
+ # this should cause the connection to be terminated
+ sock.send(struct.pack("!H", 11))
+ sock.send(struct.pack("!s", b"0123456789a"))
+
+ with pytest.raises(EOFError):
+ try:
+ (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+ (response, rtime) = dns.query.receive_tcp(sock, timeout())
+ except ConnectionError as e:
+ raise EOFError from e
+
+
+def test_tcp_garbage_response(named_port):
+ with create_socket("10.53.0.7", named_port) as sock:
+ msg = create_msg("a.example.", "A")
+ (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+ (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+ wire = msg.to_wire()
+ assert len(wire) > 0
+
+ # Send DNS response instead of DNS query, this should cause
+ # the connection to be terminated
+
+ rmsg = dns.message.make_response(msg)
+ (sbytes, stime) = dns.query.send_tcp(sock, rmsg, timeout())
+
+ with pytest.raises(EOFError):
+ try:
+ (response, rtime) = dns.query.receive_tcp(sock, timeout())
+ except ConnectionError as e:
+ raise EOFError from e
+
+
+# Regression test for CVE-2022-0396
+def test_close_wait(named_port):
+ with create_socket("10.53.0.7", named_port) as sock:
+ msg = create_msg("a.example.", "A")
+ (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+ (response, rtime) = dns.query.receive_tcp(sock, timeout())
+
+ msg = dns.message.make_query("a.example.", "A", use_edns=0, payload=1232)
+ (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+
+ # Shutdown the socket, but ignore the other side closing the socket
+ # first because we sent DNS message with EDNS0
+ try:
+ sock.shutdown(socket.SHUT_RDWR)
+ except ConnectionError:
+ pass
+ except OSError:
+ pass
+
+ # BIND allows one TCP client, the part above sends DNS messaage with EDNS0
+ # after the first query. BIND should react adequately because of
+ # ns7/named.dropedns and close the socket, making room for the next
+ # request. If it gets stuck in CLOSE_WAIT state, there is no connection
+ # available for the query below and it will time out.
+ with create_socket("10.53.0.7", named_port) as sock:
+ msg = create_msg("a.example.", "A")
+ (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout())
+ (response, rtime) = dns.query.receive_tcp(sock, timeout())