Commit 06913d3d authored by Ondřej Surý's avatar Ondřej Surý
Browse files

Merge branch '2573-dont-timeout-when-sending-data' into 'main'

Resolve "Fix TCPDNS and TLSDNS timers"

Closes #2583 and #2573

See merge request !4807
parents 2edba877 98f74954
Pipeline #66724 passed with stages
in 1 minute and 16 seconds
......@@ -5,3 +5,4 @@ disable=
C0116, # missing-function-docstring
R0801, # duplicate-code
C0103, # invalid-name
C0415,# import-outside-toplevel
5602. [bug] Fix the TCPDNS and TLSDNS timers, so TCP initial
and idle timers work correctly. [GL #2573]
5601. [bug] Dynamic zones with dnssec-policy could not be thawed
because KASP zones were always considered dynamic;
previously, dynamic KASP zones did not check whether
......
......@@ -169,7 +169,18 @@
#define EXCLBUFFERS 4096
#endif /* TUNE_LARGE */
#define MAX_TCP_TIMEOUT 65535
/* RFC7828 defines timeout as 16-bit value specified in units of 100
* milliseconds, so the maximum and minimum advertised and keepalive
* timeouts are capped by the data type (it's ~109 minutes)
*/
#define MIN_INITIAL_TIMEOUT UINT32_C(2500) /* 2.5 seconds */
#define MAX_INITIAL_TIMEOUT UINT32_C(120000) /* 2 minutes */
#define MIN_IDLE_TIMEOUT UINT32_C(100) /* 0.1 seconds */
#define MAX_IDLE_TIMEOUT UINT32_C(120000) /* 2 minutes */
#define MIN_KEEPALIVE_TIMEOUT UINT32_C(100) /* 0.1 seconds */
#define MAX_KEEPALIVE_TIMEOUT UINT32_C(UINT16_MAX * 100)
#define MIN_ADVERTISED_TIMEOUT UINT32_C(0) /* No minimum */
#define MAX_ADVERTISED_TIMEOUT UINT32_C(UINT16_MAX * 100)
/*%
* Check an operation for failure. Assumes that the function
......@@ -8499,7 +8510,7 @@ load_configuration(const char *filename, named_server_t *server,
unsigned int maxsocks;
uint32_t softquota = 0;
uint32_t max;
unsigned int initial, idle, keepalive, advertised;
uint64_t initial, idle, keepalive, advertised;
dns_aclenv_t *env =
ns_interfacemgr_getaclenv(named_g_server->interfacemgr);
......@@ -8766,62 +8777,67 @@ load_configuration(const char *filename, named_server_t *server,
obj = NULL;
result = named_config_get(maps, "tcp-initial-timeout", &obj);
INSIST(result == ISC_R_SUCCESS);
initial = cfg_obj_asuint32(obj);
if (initial > 1200) {
initial = cfg_obj_asuint32(obj) * 100;
if (initial > MAX_INITIAL_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-initial-timeout value is out of range: "
"lowering to 1200");
initial = 1200;
} else if (initial < 25) {
"lowering to %" PRIu32,
MAX_INITIAL_TIMEOUT / 100);
initial = MAX_INITIAL_TIMEOUT;
} else if (initial < MIN_INITIAL_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-initial-timeout value is out of range: "
"raising to 25");
initial = 25;
"raising to %" PRIu32,
MIN_INITIAL_TIMEOUT / 100);
initial = MIN_INITIAL_TIMEOUT;
}
obj = NULL;
result = named_config_get(maps, "tcp-idle-timeout", &obj);
INSIST(result == ISC_R_SUCCESS);
idle = cfg_obj_asuint32(obj);
if (idle > 1200) {
idle = cfg_obj_asuint32(obj) * 100;
if (idle > MAX_IDLE_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-idle-timeout value is out of range: "
"lowering to 1200");
idle = 1200;
} else if (idle < 1) {
"lowering to %" PRIu32,
MAX_IDLE_TIMEOUT / 100);
idle = MAX_IDLE_TIMEOUT;
} else if (idle < MIN_IDLE_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-idle-timeout value is out of range: "
"raising to 1");
idle = 1;
"raising to %" PRIu32,
MIN_IDLE_TIMEOUT / 100);
idle = MIN_IDLE_TIMEOUT;
}
obj = NULL;
result = named_config_get(maps, "tcp-keepalive-timeout", &obj);
INSIST(result == ISC_R_SUCCESS);
keepalive = cfg_obj_asuint32(obj);
if (keepalive > MAX_TCP_TIMEOUT) {
keepalive = cfg_obj_asuint32(obj) * 100;
if (keepalive > MAX_KEEPALIVE_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-keepalive-timeout value is out of range: "
"lowering to %u",
MAX_TCP_TIMEOUT);
keepalive = MAX_TCP_TIMEOUT;
} else if (keepalive < 1) {
"lowering to %" PRIu32,
MAX_KEEPALIVE_TIMEOUT / 100);
keepalive = MAX_KEEPALIVE_TIMEOUT;
} else if (keepalive < MIN_KEEPALIVE_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-keepalive-timeout value is out of range: "
"raising to 1");
keepalive = 1;
"raising to %" PRIu32,
MIN_KEEPALIVE_TIMEOUT / 100);
keepalive = MIN_KEEPALIVE_TIMEOUT;
}
obj = NULL;
result = named_config_get(maps, "tcp-advertised-timeout", &obj);
INSIST(result == ISC_R_SUCCESS);
advertised = cfg_obj_asuint32(obj);
if (advertised > MAX_TCP_TIMEOUT) {
advertised = cfg_obj_asuint32(obj) * 100;
if (advertised > MAX_ADVERTISED_TIMEOUT) {
cfg_obj_log(obj, named_g_lctx, ISC_LOG_WARNING,
"tcp-advertized-timeout value is out of range: "
"lowering to %u",
MAX_TCP_TIMEOUT);
advertised = MAX_TCP_TIMEOUT;
"lowering to %" PRIu32,
MAX_ADVERTISED_TIMEOUT / 100);
advertised = MAX_ADVERTISED_TIMEOUT;
}
isc_nm_settimeouts(named_g_nm, initial, idle, keepalive, advertised);
......@@ -16246,7 +16262,7 @@ isc_result_t
named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
char *ptr;
isc_result_t result = ISC_R_SUCCESS;
unsigned int initial, idle, keepalive, advertised;
uint32_t initial, idle, keepalive, advertised;
char msg[128];
/* Skip the command name. */
......@@ -16262,10 +16278,11 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
ptr = next_token(lex, NULL);
if (ptr != NULL) {
CHECK(isc_parse_uint32(&initial, ptr, 10));
if (initial > 1200) {
initial *= 100;
if (initial > MAX_INITIAL_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
if (initial < 25) {
if (initial < MIN_INITIAL_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
......@@ -16274,10 +16291,11 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
return (ISC_R_UNEXPECTEDEND);
}
CHECK(isc_parse_uint32(&idle, ptr, 10));
if (idle > 1200) {
idle *= 100;
if (idle > MAX_IDLE_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
if (idle < 1) {
if (idle < MIN_IDLE_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
......@@ -16286,10 +16304,11 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
return (ISC_R_UNEXPECTEDEND);
}
CHECK(isc_parse_uint32(&keepalive, ptr, 10));
if (keepalive > MAX_TCP_TIMEOUT) {
keepalive *= 100;
if (keepalive > MAX_KEEPALIVE_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
if (keepalive < 1) {
if (keepalive < MIN_KEEPALIVE_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
......@@ -16298,7 +16317,8 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
return (ISC_R_UNEXPECTEDEND);
}
CHECK(isc_parse_uint32(&advertised, ptr, 10));
if (advertised > MAX_TCP_TIMEOUT) {
advertised *= 100;
if (advertised > MAX_ADVERTISED_TIMEOUT) {
CHECK(ISC_R_RANGE);
}
......@@ -16311,13 +16331,15 @@ named_server_tcptimeouts(isc_lex_t *lex, isc_buffer_t **text) {
isc_task_endexclusive(named_g_server->task);
}
snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial);
snprintf(msg, sizeof(msg), "tcp-initial-timeout=%u\n", initial / 100);
CHECK(putstr(text, msg));
snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle);
snprintf(msg, sizeof(msg), "tcp-idle-timeout=%u\n", idle / 100);
CHECK(putstr(text, msg));
snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n", keepalive);
snprintf(msg, sizeof(msg), "tcp-keepalive-timeout=%u\n",
keepalive / 100);
CHECK(putstr(text, msg));
snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u", advertised);
snprintf(msg, sizeof(msg), "tcp-advertised-timeout=%u",
advertised / 100);
CHECK(putstr(text, msg));
cleanup:
......
......@@ -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
......
#!/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
############################################################################
# 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
; 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
/*
* 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;
};
; 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
#!/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
#!/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
#!/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
......@@ -44,3 +44,11 @@ Bug Fixes
- Fix a crash when transferring a zone over TLS, after "named" previously
skipped a master. [GL #2562]
- It was discovered that the TCP idle and initial timeouts were incorrectly
applied in the BIND 9.16 and 9.17 branches. Only the ``tcp-initial-timeout``
was applied on the whole connection, even if the connection were still active,
which could cause a large zone transfer to be sent back to the client. The
default setting for ``tcp-initial-timeout`` was 30 seconds, which meant that
any TCP connection taking more than 30 seconds was abruptly terminated. This
has been fixed [GL #2573].