diff --git a/.pylintrc b/.pylintrc index 750e50e37c..178a06a801 100644 --- a/.pylintrc +++ b/.pylintrc @@ -5,3 +5,4 @@ disable= C0116, # missing-function-docstring R0801, # duplicate-code C0103, # invalid-name + C0415,# import-outside-toplevel diff --git a/bin/tests/system/Makefile.am b/bin/tests/system/Makefile.am index a407716c04..d5e18c3f3f 100644 --- a/bin/tests/system/Makefile.am +++ b/bin/tests/system/Makefile.am @@ -201,7 +201,7 @@ if HAVE_PYTHON TESTS += kasp tcp pipelined if HAVE_PYMOD_DNS -TESTS += qmin cookie +TESTS += qmin cookie timeouts if HAVE_PERLMOD_NET_DNS TESTS += dnssec diff --git a/bin/tests/system/timeouts/clean.sh b/bin/tests/system/timeouts/clean.sh new file mode 100644 index 0000000000..b177285085 --- /dev/null +++ b/bin/tests/system/timeouts/clean.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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 ./ns*/managed-keys.bind* +rm -f ./ns*/named.conf +rm -f ./ns*/named.lock +rm -f ./ns*/named.memstats +rm -f ./ns*/named.run* +rm -f ./ns*/named.stats +rm -rf ./.cache ./__pycache__ +rm -f ./ns*/large.db diff --git a/bin/tests/system/timeouts/conftest.py b/bin/tests/system/timeouts/conftest.py new file mode 100644 index 0000000000..368fe9c407 --- /dev/null +++ b/bin/tests/system/timeouts/conftest.py @@ -0,0 +1,59 @@ +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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. +############################################################################ + +import os +import pytest + + +def pytest_configure(config): + config.addinivalue_line( + "markers", "dnspython: mark tests that need dnspython to function" + ) + config.addinivalue_line( + "markers", "dnspython2: mark tests that need dnspython >= 2.0.0" + ) + + +def pytest_collection_modifyitems(config, items): + # pylint: disable=unused-argument,unused-import,too-many-branches + # pylint: disable=import-outside-toplevel + + # Test for dnspython module + skip_dnspython = pytest.mark.skip( + reason="need dnspython module to run") + try: + import dns.query # noqa: F401 + except ModuleNotFoundError: + for item in items: + if "dnspython" in item.keywords: + item.add_marker(skip_dnspython) + + # Test for dnspython >= 2.0.0 module + skip_dnspython2 = pytest.mark.skip( + reason="need dnspython >= 2.0.0 module to run") + try: + from dns.query import send_tcp # noqa: F401 + except ImportError: + for item in items: + if "dnspython2" in item.keywords: + item.add_marker(skip_dnspython2) + + +@pytest.fixture +def port(request): + # pylint: disable=unused-argument + env_port = os.getenv("PORT") + if port is None: + env_port = 5300 + else: + env_port = int(env_port) + + return env_port diff --git a/bin/tests/system/timeouts/ns1/example.db b/bin/tests/system/timeouts/ns1/example.db new file mode 100644 index 0000000000..310a31b5ca --- /dev/null +++ b/bin/tests/system/timeouts/ns1/example.db @@ -0,0 +1,23 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; 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 http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ SOA mname1. . ( + 2000062101 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns1 +ns1 A 10.53.0.1 +@ A 10.53.0.1 +a A 10.53.0.1 +b A 10.53.0.1 +$INCLUDE large.db diff --git a/bin/tests/system/timeouts/ns1/named.conf.in b/bin/tests/system/timeouts/ns1/named.conf.in new file mode 100644 index 0000000000..7923524985 --- /dev/null +++ b/bin/tests/system/timeouts/ns1/named.conf.in @@ -0,0 +1,41 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * 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 http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +include "../../common/rndc.key"; + +controls { + inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; +}; + +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; + notify no; + tcp-initial-timeout 20; + tcp-idle-timeout 50; +}; + +zone "." { + type primary; + file "root.db"; +}; + +zone "example." { + type primary; + file "example.db"; + check-integrity no; +}; diff --git a/bin/tests/system/timeouts/ns1/root.db b/bin/tests/system/timeouts/ns1/root.db new file mode 100644 index 0000000000..2c481ea7f2 --- /dev/null +++ b/bin/tests/system/timeouts/ns1/root.db @@ -0,0 +1,22 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; 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 http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 +. IN SOA gson.isc.org. 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 ns1.example. +ns1.example. A 10.53.0.1 diff --git a/bin/tests/system/timeouts/prereq.sh b/bin/tests/system/timeouts/prereq.sh new file mode 100644 index 0000000000..9f4e7cd560 --- /dev/null +++ b/bin/tests/system/timeouts/prereq.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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 + +if test -n "$PYTHON" +then + if $PYTHON -c "from dns.query import send_tcp" 2> /dev/null + then + : + else + echo_i "This test requires the dnspython >= 2.0.0 module." >&2 + exit 1 + fi +else + echo_i "This test requires Python and the dnspython module." >&2 + exit 1 +fi + +exit 0 diff --git a/bin/tests/system/timeouts/setup.sh b/bin/tests/system/timeouts/setup.sh new file mode 100644 index 0000000000..2af68a27f4 --- /dev/null +++ b/bin/tests/system/timeouts/setup.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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 + +copy_setports ns1/named.conf.in ns1/named.conf + +# +# Generate a large enough zone, so the transfer takes longer than +# tcp-initial-timeout interval +# +$PYTHON -c " +for a in range(150000): + print('%s IN NS a' % (a)) + print('%s IN NS b' % (a))" > ns1/large.db diff --git a/bin/tests/system/timeouts/tests-tcp.py b/bin/tests/system/timeouts/tests-tcp.py new file mode 100644 index 0000000000..03f69da434 --- /dev/null +++ b/bin/tests/system/timeouts/tests-tcp.py @@ -0,0 +1,161 @@ +#!/usr/bin/python3 +############################################################################ +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# 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 time + +import pytest + +TIMEOUT = 10 + + +def create_msg(qname, qtype): + import dns.message + msg = dns.message.make_query(qname, qtype, want_dnssec=True, + use_edns=0, payload=4096) + + return msg + + +def timeout(): + return time.time() + TIMEOUT + + +@pytest.mark.dnspython +@pytest.mark.dnspython2 +def test_initial_timeout(port): + # + # The initial timeout is 2.5 seconds, so this should timeout + # + import dns.query + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect(("10.53.0.1", port)) + + time.sleep(3) + + msg = create_msg("example.", "A") + + with pytest.raises(EOFError): + try: + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + except ConnectionResetError as e: + raise EOFError from e + + +@pytest.mark.dnspython +@pytest.mark.dnspython2 +def test_idle_timeout(port): + # + # The idle timeout is 5 second, so sending the second message must fail + # + import dns.rcode + + msg = create_msg("example.", "A") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect(("10.53.0.1", port)) + + time.sleep(1) + + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + + time.sleep(3) + + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + + time.sleep(6) + + with pytest.raises(EOFError): + try: + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + except ConnectionResetError as e: + raise EOFError from e + + +@pytest.mark.dnspython +@pytest.mark.dnspython2 +def test_pipelining_timeout(port): + # + # The pipelining should only timeout after the last message is received + # + import dns.query + + msg = create_msg("example.", "A") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect(("10.53.0.1", port)) + + time.sleep(1) + + # Send and receive 25 DNS queries + for n in range(25): + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + for n in range(25): + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + + time.sleep(3) + + # Send and receive 25 DNS queries + for n in range(25): + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + for n in range(25): + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + + time.sleep(6) + + with pytest.raises(EOFError): + try: + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + (response, rtime) = dns.query.receive_tcp(sock, timeout()) + except ConnectionResetError as e: + raise EOFError from e + + +@pytest.mark.dnspython +@pytest.mark.dnspython2 +def test_long_axfr(port): + # + # The timers should not fire during AXFR, thus the connection should not + # close abruptly + # + import dns.query + import dns.rdataclass + import dns.rdatatype + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.connect(("10.53.0.1", port)) + + name = dns.name.from_text("example.") + msg = create_msg("example.", "AXFR") + (sbytes, stime) = dns.query.send_tcp(sock, msg, timeout()) + + # Receive the initial DNS message with SOA + (response, rtime) = dns.query.receive_tcp(sock, timeout(), + one_rr_per_rrset=True) + soa = response.get_rrset(dns.message.ANSWER, name, + dns.rdataclass.IN, dns.rdatatype.SOA) + assert soa is not None + + # Pull DNS message from wire until the second SOA is received + while True: + (response, rtime) = dns.query.receive_tcp(sock, timeout(), + one_rr_per_rrset=True) + soa = response.get_rrset(dns.message.ANSWER, name, + dns.rdataclass.IN, dns.rdatatype.SOA) + if soa is not None: + break + assert soa is not None diff --git a/util/copyrights b/util/copyrights index 06feaf151b..aef7cbfb8c 100644 --- a/util/copyrights +++ b/util/copyrights @@ -892,6 +892,11 @@ ./bin/tests/system/testsock.pl PERL 2000,2001,2004,2007,2010,2011,2012,2013,2016,2018,2019,2020,2021 ./bin/tests/system/testsock6.pl PERL 2010,2012,2014,2016,2018,2019,2020,2021 ./bin/tests/system/testsummary.sh SH 2018,2019,2020,2021 +./bin/tests/system/timeouts/clean.sh SH 2021 +./bin/tests/system/timeouts/conftest.py PYTHON 2021 +./bin/tests/system/timeouts/prereq.sh SH 2021 +./bin/tests/system/timeouts/setup.sh SH 2021 +./bin/tests/system/timeouts/tests-tcp.py PYTHON-BIN 2021 ./bin/tests/system/tkey/clean.sh SH 2001,2004,2007,2011,2012,2013,2014,2015,2016,2018,2019,2020,2021 ./bin/tests/system/tkey/keycreate.c C 2001,2004,2005,2007,2009,2011,2012,2014,2015,2016,2017,2018,2019,2020,2021 ./bin/tests/system/tkey/keydelete.c C 2001,2004,2005,2007,2009,2010,2011,2014,2015,2016,2017,2018,2019,2020,2021