xref: /netbsd-src/external/mpl/bind/dist/bin/tests/system/timeouts/tests_tcp_timeouts.py (revision 9689912e6b171cbda866ec33f15ae94a04e2c02d)
1#!/usr/bin/python3
2
3# Copyright (C) Internet Systems Consortium, Inc. ("ISC")
4#
5# SPDX-License-Identifier: MPL-2.0
6#
7# This Source Code Form is subject to the terms of the Mozilla Public
8# License, v. 2.0.  If a copy of the MPL was not distributed with this
9# file, you can obtain one at https://mozilla.org/MPL/2.0/.
10#
11# See the COPYRIGHT file distributed with this work for additional
12# information regarding copyright ownership.
13
14# pylint: disable=unused-variable
15
16import socket
17import time
18
19import pytest
20
21pytest.importorskip("dns", minversion="2.0.0")
22import dns.edns
23import dns.message
24import dns.name
25import dns.query
26import dns.rdataclass
27import dns.rdatatype
28
29import isctest.mark  # pylint: disable=import-error
30
31pytestmark = pytest.mark.extra_artifacts(
32    [
33        "ns1/large.db",
34    ]
35)
36
37TIMEOUT = 10
38
39
40def create_msg(qname, qtype):
41    msg = dns.message.make_query(
42        qname, qtype, want_dnssec=True, use_edns=0, payload=4096
43    )
44    return msg
45
46
47def timeout():
48    return time.time() + TIMEOUT
49
50
51def test_initial_timeout(named_port):
52    #
53    # The initial timeout is 2.5 seconds, so this should timeout
54    #
55    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
56        sock.connect(("10.53.0.1", named_port))
57
58        time.sleep(3)
59
60        msg = create_msg("example.", "A")
61
62        with pytest.raises(EOFError):
63            try:
64                dns.query.send_tcp(sock, msg, timeout())
65                dns.query.receive_tcp(sock, timeout())
66            except ConnectionError as e:
67                raise EOFError from e
68
69
70def test_idle_timeout(named_port):
71    #
72    # The idle timeout is 5 seconds, so the third message should fail
73    #
74    msg = create_msg("example.", "A")
75    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
76        sock.connect(("10.53.0.1", named_port))
77
78        time.sleep(1)
79
80        dns.query.send_tcp(sock, msg, timeout())
81        dns.query.receive_tcp(sock, timeout())
82
83        time.sleep(2)
84
85        dns.query.send_tcp(sock, msg, timeout())
86        dns.query.receive_tcp(sock, timeout())
87
88        time.sleep(6)
89
90        with pytest.raises(EOFError):
91            try:
92                dns.query.send_tcp(sock, msg, timeout())
93                dns.query.receive_tcp(sock, timeout())
94            except ConnectionError as e:
95                raise EOFError from e
96
97
98def test_keepalive_timeout(named_port):
99    #
100    # Keepalive is 7 seconds, so the third message should succeed.
101    #
102    msg = create_msg("example.", "A")
103    kopt = dns.edns.GenericOption(11, b"\x00")
104    msg.use_edns(edns=True, payload=4096, options=[kopt])
105
106    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
107        sock.connect(("10.53.0.1", named_port))
108
109        time.sleep(1)
110
111        dns.query.send_tcp(sock, msg, timeout())
112        dns.query.receive_tcp(sock, timeout())
113
114        time.sleep(2)
115
116        dns.query.send_tcp(sock, msg, timeout())
117        dns.query.receive_tcp(sock, timeout())
118
119        time.sleep(6)
120
121        dns.query.send_tcp(sock, msg, timeout())
122        dns.query.receive_tcp(sock, timeout())
123
124
125def test_pipelining_timeout(named_port):
126    #
127    # The pipelining should only timeout after the last message is received
128    #
129    msg = create_msg("example.", "A")
130    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
131        sock.connect(("10.53.0.1", named_port))
132
133        time.sleep(1)
134
135        # Send and receive 25 DNS queries
136        for _ in range(25):
137            dns.query.send_tcp(sock, msg, timeout())
138        for _ in range(25):
139            dns.query.receive_tcp(sock, timeout())
140
141        time.sleep(3)
142
143        # Send and receive 25 DNS queries
144        for _ in range(25):
145            dns.query.send_tcp(sock, msg, timeout())
146        for _ in range(25):
147            dns.query.receive_tcp(sock, timeout())
148
149        time.sleep(6)
150
151        with pytest.raises(EOFError):
152            try:
153                dns.query.send_tcp(sock, msg, timeout())
154                dns.query.receive_tcp(sock, timeout())
155            except ConnectionError as e:
156                raise EOFError from e
157
158
159def test_long_axfr(named_port):
160    #
161    # The timers should not fire during AXFR, thus the connection should not
162    # close abruptly
163    #
164    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
165        sock.connect(("10.53.0.1", named_port))
166
167        name = dns.name.from_text("example.")
168        msg = create_msg("example.", "AXFR")
169        dns.query.send_tcp(sock, msg, timeout())
170
171        # Receive the initial DNS message with SOA
172        (response, _) = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True)
173        soa = response.get_rrset(
174            dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
175        )
176        assert soa is not None
177
178        # Pull DNS message from wire until the second SOA is received
179        while True:
180            (response, _) = dns.query.receive_tcp(
181                sock, timeout(), one_rr_per_rrset=True
182            )
183            soa = response.get_rrset(
184                dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
185            )
186            if soa is not None:
187                break
188        assert soa is not None
189
190
191@isctest.mark.flaky(max_runs=3)
192def test_send_timeout(named_port):
193    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
194        sock.connect(("10.53.0.1", named_port))
195
196        # Send and receive single large RDATA over TCP
197        msg = create_msg("large.example.", "TXT")
198        dns.query.send_tcp(sock, msg, timeout())
199        dns.query.receive_tcp(sock, timeout())
200
201        # Send and receive 28 large (~32k) DNS queries that should
202        # fill the default maximum 208k TCP send buffer
203        for _ in range(28):
204            dns.query.send_tcp(sock, msg, timeout())
205
206        # configure idle interval is 5 seconds, sleep 6 to make sure we are
207        # above the interval
208        time.sleep(6)
209
210        with pytest.raises(EOFError):
211            try:
212                dns.query.send_tcp(sock, msg, timeout())
213                dns.query.receive_tcp(sock, timeout())
214            except ConnectionError as e:
215                raise EOFError from e
216
217
218@isctest.mark.long_test
219def test_max_transfer_idle_out(named_port):
220    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
221        sock.connect(("10.53.0.1", named_port))
222
223        name = dns.name.from_text("example.")
224        msg = create_msg("example.", "AXFR")
225        dns.query.send_tcp(sock, msg, timeout())
226
227        # Receive the initial DNS message with SOA
228        (response, _) = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True)
229        soa = response.get_rrset(
230            dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
231        )
232        assert soa is not None
233
234        time.sleep(61)  # max-transfer-idle-out is 1 minute
235
236        with pytest.raises(ConnectionResetError):
237            # Process queued TCP messages
238            while True:
239                (response, _) = dns.query.receive_tcp(
240                    sock, timeout(), one_rr_per_rrset=True
241                )
242                soa = response.get_rrset(
243                    dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
244                )
245                if soa is not None:
246                    break
247            assert soa is None
248
249
250@isctest.mark.long_test
251def test_max_transfer_time_out(named_port):
252    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
253        sock.connect(("10.53.0.1", named_port))
254
255        name = dns.name.from_text("example.")
256        msg = create_msg("example.", "AXFR")
257        dns.query.send_tcp(sock, msg, timeout())
258
259        # Receive the initial DNS message with SOA
260        (response, _) = dns.query.receive_tcp(sock, timeout(), one_rr_per_rrset=True)
261        soa = response.get_rrset(
262            dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
263        )
264        assert soa is not None
265
266        # The loop should timeout at the 5 minutes (max-transfer-time-out)
267        with pytest.raises(EOFError):
268            while True:
269                time.sleep(1)
270                (response, _) = dns.query.receive_tcp(
271                    sock, timeout(), one_rr_per_rrset=True
272                )
273                soa = response.get_rrset(
274                    dns.message.ANSWER, name, dns.rdataclass.IN, dns.rdatatype.SOA
275                )
276                if soa is not None:
277                    break
278        assert soa is None
279