diff --git a/Makefile.in b/Makefile.in index aa620b205..40be55327 100644 --- a/Makefile.in +++ b/Makefile.in @@ -57,7 +57,7 @@ STRIP=@STRIP@ CC=@CC@ CPPFLAGS=-I. @CPPFLAGS@ PYTHON_CPPFLAGS=-I. @PYTHON_CPPFLAGS@ -CFLAGS=@CFLAGS@ +CFLAGS=-DSRCDIR=$(srcdir) @CFLAGS@ LDFLAGS=@LDFLAGS@ LIBS=@LIBS@ LIBOBJS=@LIBOBJS@ @@ -306,10 +306,11 @@ longcheck: longtest test: unittest$(EXEEXT) testbound$(EXEEXT) ./unittest$(EXEEXT) ./testbound$(EXEEXT) -s - for x in testdata/*.rpl; do echo -n "$$x "; if ./testbound$(EXEEXT) -p $$x >/dev/null 2>&1; then echo OK; else echo failed; exit 1; fi done + for x in $(srcdir)/testdata/*.rpl; do echo -n "$$x "; if ./testbound$(EXEEXT) -p $$x >/dev/null 2>&1; then echo OK; else echo failed; exit 1; fi done @echo test OK longtest: tests + if test ! $(srcdir)/testdata -ef ./testdata; then rm -rf testcode testdata; mkdir testcode testdata; cp -R $(srcdir)/testdata/*.sh $(srcdir)/testdata/*.tdir testdata; cp $(srcdir)/testcode/*.sh testcode; fi if test -x "`which bash`"; then bash testcode/do-tests.sh; else sh testcode/do-tests.sh; fi lib: libunbound.la unbound.h diff --git a/daemon/worker.c b/daemon/worker.c index 44a989a4e..8568ac3cf 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1708,9 +1708,9 @@ worker_init(struct worker* worker, struct config_file *cfg, worker->comsig = NULL; } worker->front = listen_create(worker->base, ports, - cfg->msg_buffer_size, (int)cfg->incoming_num_tcp, - worker->daemon->listen_sslctx, dtenv, worker_handle_request, - worker); + cfg->msg_buffer_size, (int)cfg->incoming_num_tcp, + cfg->tcp_idle_timeout, worker->daemon->listen_sslctx, + dtenv, worker_handle_request, worker); if(!worker->front) { log_err("could not create listening sockets"); worker_delete(worker); diff --git a/doc/Changelog b/doc/Changelog index 3e2cabc5e..08e4fca33 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -1,3 +1,16 @@ +31 July 2018: Wouter + - Patches from Jim Hague (Sinodun) for EDNS KeepAlive. + - Sort out test runs when the build directory isn't the project + root directory. + - Add config tcp-idle-timeout (default 30s). This applies to + client connections only; the timeout on TCP connections upstream + is unaffected. + - Error if EDNS Keepalive received over UDP. + - Add edns-tcp-keepalive and edns-tcp-keepalive timeout options + and implement option in client responses. + - Correct and expand manual page entries for keepalive and idle timeout. + - Implement progressive backoff of TCP idle/keepalive timeout. + 30 July 2018: Wouter - Fix #4136: insufficiency from mismatch of FLEX capability between released tarball and build host. diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index e5c76b5f9..e41e91100 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -389,6 +389,11 @@ Note that not all platform supports socket option to set MSS (TCP_MAXSEG). Default is system default MSS determined by interface MTU and negotiation between Unbound and other servers. .TP +.B tcp-idle-timeout: \fI\fR +The period Unbound will wait for a query on a TCP connection. +If this timeout expires Unbound closes the connection. +This option defaults to 30000 milliseconds. +.TP .B tcp\-upstream: \fI Enable or disable whether the upstream queries use TCP only for transport. Default is no. Useful in tunneling scenarios. diff --git a/services/listen_dnsport.c b/services/listen_dnsport.c index 3d3c5cbe5..5cae35ec8 100644 --- a/services/listen_dnsport.c +++ b/services/listen_dnsport.c @@ -1227,8 +1227,9 @@ listen_cp_insert(struct comm_point* c, struct listen_dnsport* front) struct listen_dnsport* listen_create(struct comm_base* base, struct listen_port* ports, - size_t bufsize, int tcp_accept_count, void* sslctx, - struct dt_env* dtenv, comm_point_callback_type* cb, void *cb_arg) + size_t bufsize, int tcp_accept_count, int tcp_idle_timeout, + void* sslctx, struct dt_env* dtenv, + comm_point_callback_type* cb, void *cb_arg) { struct listen_dnsport* front = (struct listen_dnsport*) malloc(sizeof(struct listen_dnsport)); @@ -1254,10 +1255,12 @@ listen_create(struct comm_base* base, struct listen_port* ports, else if(ports->ftype == listen_type_tcp || ports->ftype == listen_type_tcp_dnscrypt) cp = comm_point_create_tcp(base, ports->fd, - tcp_accept_count, bufsize, cb, cb_arg); + tcp_accept_count, tcp_idle_timeout, + bufsize, cb, cb_arg); else if(ports->ftype == listen_type_ssl) { cp = comm_point_create_tcp(base, ports->fd, - tcp_accept_count, bufsize, cb, cb_arg); + tcp_accept_count, tcp_idle_timeout, + bufsize, cb, cb_arg); cp->ssl = sslctx; } else if(ports->ftype == listen_type_udpancil || ports->ftype == listen_type_udpancil_dnscrypt) diff --git a/services/listen_dnsport.h b/services/listen_dnsport.h index fac0f7970..d97cfc7c2 100644 --- a/services/listen_dnsport.h +++ b/services/listen_dnsport.h @@ -145,7 +145,8 @@ void listening_ports_free(struct listen_port* list); * @return: the malloced listening structure, ready for use. NULL on error. */ struct listen_dnsport* listen_create(struct comm_base* base, - struct listen_port* ports, size_t bufsize, int tcp_accept_count, + struct listen_port* ports, size_t bufsize, + int tcp_accept_count, int tcp_idle_timeout, void* sslctx, struct dt_env *dtenv, comm_point_callback_type* cb, void* cb_arg); diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 80e3685c0..f192c0368 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -856,6 +856,7 @@ run_scenario(struct replay_runtime* runtime) struct listen_dnsport* listen_create(struct comm_base* base, struct listen_port* ATTR_UNUSED(ports), size_t bufsize, int ATTR_UNUSED(tcp_accept_count), + int ATTR_UNUSED(tcp_idle_timeout), void* ATTR_UNUSED(sslctx), struct dt_env* ATTR_UNUSED(dtenv), comm_point_callback_type* cb, void* cb_arg) { diff --git a/testcode/streamtcp.c b/testcode/streamtcp.c index 0a636395f..ce335f6c8 100644 --- a/testcode/streamtcp.c +++ b/testcode/streamtcp.c @@ -44,6 +44,8 @@ #include #endif #include +#include +#include #include "util/locks.h" #include "util/log.h" #include "util/net_help.h" @@ -71,6 +73,7 @@ static void usage(char* argv[]) printf("-f server what ipaddr@portnr to send the queries to\n"); printf("-u use UDP. No retries are attempted.\n"); printf("-n do not wait for an answer.\n"); + printf("-d secs delay after connection before sending query\n"); printf("-s use ssl\n"); printf("-h this help text\n"); exit(1); @@ -275,7 +278,8 @@ static int get_random(void) /** send the TCP queries and print answers */ static void -send_em(const char* svr, int udp, int usessl, int noanswer, int num, char** qs) +send_em(const char* svr, int udp, int usessl, int noanswer, int delay, + int num, char** qs) { sldns_buffer* buf = sldns_buffer_new(65553); int fd = open_svr(svr, udp); @@ -310,6 +314,8 @@ send_em(const char* svr, int udp, int usessl, int noanswer, int num, char** qs) } } for(i=0; i> .tpkg.var.test +echo "FWD_PORT=$FWD_PORT" >> .tpkg.var.test + +# start forwarder +get_ldns_testns +$LDNS_TESTNS -p $FWD_PORT tcp_idle_timeout.testns >fwd.log 2>&1 & +FWD_PID=$! +echo "FWD_PID=$FWD_PID" >> .tpkg.var.test + +# make config file +sed -e 's/@PORT\@/'$UNBOUND_PORT'/' -e 's/@TOPORT\@/'$FWD_PORT'/' < tcp_idle_timeout.conf > ub.conf +# start unbound in the background +PRE="../.." +$PRE/unbound -d -c ub.conf >unbound.log 2>&1 & +UNBOUND_PID=$! +echo "UNBOUND_PID=$UNBOUND_PID" >> .tpkg.var.test + +cat .tpkg.var.test +wait_ldns_testns_up fwd.log +wait_unbound_up unbound.log + diff --git a/testdata/tcp_idle_timeout.tdir/tcp_idle_timeout.test b/testdata/tcp_idle_timeout.tdir/tcp_idle_timeout.test new file mode 100644 index 000000000..30852e075 --- /dev/null +++ b/testdata/tcp_idle_timeout.tdir/tcp_idle_timeout.test @@ -0,0 +1,63 @@ +# #-- tcp_idle_timeout.test --# +# source the master var file when it's there +[ -f ../.tpkg.var.master ] && source ../.tpkg.var.master +# use .tpkg.var.test for in test variable passing +[ -f .tpkg.var.test ] && source .tpkg.var.test + +if uname | grep MINGW >/dev/null; then + echo "no job control in shell on windows. end test" + exit 0 +fi + +PRE="../.." +. ../common.sh +get_make +(cd $PRE; $MAKE streamtcp) + +# first test a single TCP query with no delay. +echo "> query www.example.com." +$PRE/streamtcp -f 127.0.0.1@$UNBOUND_PORT www.example.com. A IN >outfile 2>&1 +if test "$?" -ne 0; then + echo "exit status not OK" + echo "> cat logfiles" + cat outfile + cat fwd.log + cat unbound.log + echo "Not OK" + exit 1 +else + echo "exit status OK" +fi +echo "> cat logfiles" +cat outfile +cat fwd.log +cat unbound.log +echo "> check answer" +if grep "10.20.30.40" outfile; then + echo "OK" +else + echo "Not OK" + exit 1 +fi + +# now test query with delay should fail. +echo "> query www.example.com." +$PRE/streamtcp -d 4 -f 127.0.0.1@$UNBOUND_PORT www.example.com. A IN >outfile 2>&1 +if test "$?" -eq 0; then + echo "exit status OK" + echo "> cat logfiles" + cat outfile + cat fwd.log + cat unbound.log + echo "Not OK" + exit 1 +else + echo "exit status not OK" +fi +echo "> cat logfiles" +cat outfile +cat fwd.log +cat unbound.log +echo "OK" + +exit 0 diff --git a/testdata/tcp_idle_timeout.tdir/tcp_idle_timeout.testns b/testdata/tcp_idle_timeout.tdir/tcp_idle_timeout.testns new file mode 100644 index 000000000..14647723f --- /dev/null +++ b/testdata/tcp_idle_timeout.tdir/tcp_idle_timeout.testns @@ -0,0 +1,42 @@ +; nameserver test file +$ORIGIN example.com. +$TTL 3600 + +ENTRY_BEGIN +MATCH UDP opcode qtype qname +REPLY QR AA NOERROR TC +ADJUST copy_id +SECTION QUESTION +www IN A +ENTRY_END + +ENTRY_BEGIN +MATCH TCP opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id sleep=2 +SECTION QUESTION +www IN A +SECTION ANSWER +www IN A 10.20.30.40 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www2 IN A +SECTION ANSWER +www2 IN A 10.20.30.42 +ENTRY_END + +ENTRY_BEGIN +MATCH opcode qtype qname +REPLY QR AA NOERROR +ADJUST copy_id +SECTION QUESTION +www3 IN A +SECTION ANSWER +www3 IN A 10.20.30.43 +ENTRY_END + diff --git a/util/config_file.c b/util/config_file.c index 694a8c6ce..d5726432c 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -104,6 +104,7 @@ config_create(void) cfg->udp_upstream_without_downstream = 0; cfg->tcp_mss = 0; cfg->outgoing_tcp_mss = 0; + cfg->tcp_idle_timeout = 30 * 1000; /* 30s in millisecs */ cfg->ssl_service_key = NULL; cfg->ssl_service_pem = NULL; cfg->ssl_port = UNBOUND_DNS_OVER_TLS_PORT; @@ -455,6 +456,7 @@ int config_set_option(struct config_file* cfg, const char* opt, udp_upstream_without_downstream) else S_NUMBER_NONZERO("tcp-mss:", tcp_mss) else S_NUMBER_NONZERO("outgoing-tcp-mss:", outgoing_tcp_mss) + else S_NUMBER_NONZERO("tcp-idle-timeout:", tcp_idle_timeout) else S_YNO("ssl-upstream:", ssl_upstream) else S_STR("ssl-service-key:", ssl_service_key) else S_STR("ssl-service-pem:", ssl_service_pem) @@ -878,6 +880,7 @@ config_get_option(struct config_file* cfg, const char* opt, else O_YNO(opt, "udp-upstream-without-downstream", udp_upstream_without_downstream) else O_DEC(opt, "tcp-mss", tcp_mss) else O_DEC(opt, "outgoing-tcp-mss", outgoing_tcp_mss) + else O_DEC(opt, "tcp-idle-timeout", tcp_idle_timeout) else O_YNO(opt, "ssl-upstream", ssl_upstream) else O_STR(opt, "ssl-service-key", ssl_service_key) else O_STR(opt, "ssl-service-pem", ssl_service_pem) diff --git a/util/config_file.h b/util/config_file.h index 7af0067f9..c27595b74 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -99,6 +99,8 @@ struct config_file { int tcp_mss; /** maximum segment size of tcp socket for outgoing queries */ int outgoing_tcp_mss; + /** tcp idle timeout */ + int tcp_idle_timeout; /** private key file for dnstcp-ssl service (enabled if not NULL) */ char* ssl_service_key; diff --git a/util/configlexer.lex b/util/configlexer.lex index 14aafbf76..dbad5eab2 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -233,6 +233,7 @@ do-tcp{COLON} { YDVAR(1, VAR_DO_TCP) } tcp-upstream{COLON} { YDVAR(1, VAR_TCP_UPSTREAM) } tcp-mss{COLON} { YDVAR(1, VAR_TCP_MSS) } outgoing-tcp-mss{COLON} { YDVAR(1, VAR_OUTGOING_TCP_MSS) } +tcp-idle-timeout{COLON} { YDVAR(1, VAR_TCP_IDLE_TIMEOUT) } ssl-upstream{COLON} { YDVAR(1, VAR_SSL_UPSTREAM) } tls-upstream{COLON} { YDVAR(1, VAR_SSL_UPSTREAM) } ssl-service-key{COLON} { YDVAR(1, VAR_SSL_SERVICE_KEY) } diff --git a/util/configparser.y b/util/configparser.y index 1c264bc6e..e60002f53 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -72,7 +72,7 @@ extern struct config_parser_state* cfg_parser; %token VAR_SERVER VAR_VERBOSITY VAR_NUM_THREADS VAR_PORT %token VAR_OUTGOING_RANGE VAR_INTERFACE %token VAR_DO_IP4 VAR_DO_IP6 VAR_PREFER_IP6 VAR_DO_UDP VAR_DO_TCP -%token VAR_TCP_MSS VAR_OUTGOING_TCP_MSS +%token VAR_TCP_MSS VAR_OUTGOING_TCP_MSS VAR_TCP_IDLE_TIMEOUT %token VAR_CHROOT VAR_USERNAME VAR_DIRECTORY VAR_LOGFILE VAR_PIDFILE %token VAR_MSG_CACHE_SIZE VAR_MSG_CACHE_SLABS VAR_NUM_QUERIES_PER_THREAD %token VAR_RRSET_CACHE_SIZE VAR_RRSET_CACHE_SLABS VAR_OUTGOING_NUM_TCP @@ -180,7 +180,7 @@ content_server: server_num_threads | server_verbosity | server_port | server_outgoing_range | server_do_ip4 | server_do_ip6 | server_prefer_ip6 | server_do_udp | server_do_tcp | - server_tcp_mss | server_outgoing_tcp_mss | + server_tcp_mss | server_outgoing_tcp_mss | server_tcp_idle_timeout | server_interface | server_chroot | server_username | server_directory | server_logfile | server_pidfile | server_msg_cache_size | server_msg_cache_slabs | @@ -631,6 +631,19 @@ server_outgoing_tcp_mss: VAR_OUTGOING_TCP_MSS STRING_ARG free($2); } ; +server_tcp_idle_timeout: VAR_TCP_IDLE_TIMEOUT STRING_ARG + { + OUTYY(("P(server_tcp_idle_timeout:%s)\n", $2)); + if(atoi($2) == 0 && strcmp($2, "0") != 0) + yyerror("number expected"); + else if (atoi($2) > 120000) + cfg_parser->cfg->tcp_idle_timeout = 120000; + else if (atoi($2) < 1) + cfg_parser->cfg->tcp_idle_timeout = 1; + else cfg_parser->cfg->tcp_idle_timeout = atoi($2); + free($2); + } + ; server_tcp_upstream: VAR_TCP_UPSTREAM STRING_ARG { OUTYY(("P(server_tcp_upstream:%s)\n", $2)); diff --git a/util/netevent.c b/util/netevent.c index 5cc8606fa..49d52a349 100644 --- a/util/netevent.c +++ b/util/netevent.c @@ -82,7 +82,7 @@ # endif #endif -/** The TCP reading or writing query timeout in milliseconds */ +/** The TCP writing query timeout in milliseconds */ #define TCP_QUERY_TIMEOUT 120000 /** The TCP timeout in msec for fast queries, above half are used */ #define TCP_QUERY_TIMEOUT_FAST 200 @@ -737,7 +737,6 @@ setup_tcp_handler(struct comm_point* c, int fd, int cur, int max) #endif c->tcp_is_reading = 1; c->tcp_byte_count = 0; - c->tcp_timeout_msec = TCP_QUERY_TIMEOUT; /* if more than half the tcp handlers are in use, use a shorter * timeout for this TCP connection, we need to make space for * other connections to be able to get attention */ @@ -2525,6 +2524,7 @@ comm_point_create_tcp_handler(struct comm_base *base, c->tcp_is_reading = 0; c->tcp_byte_count = 0; c->tcp_parent = parent; + c->tcp_timeout_msec = parent->tcp_timeout_msec; c->max_tcp_count = 0; c->cur_tcp_count = 0; c->tcp_handlers = NULL; @@ -2565,7 +2565,8 @@ comm_point_create_tcp_handler(struct comm_base *base, } struct comm_point* -comm_point_create_tcp(struct comm_base *base, int fd, int num, size_t bufsize, +comm_point_create_tcp(struct comm_base *base, int fd, int num, + int idle_timeout, size_t bufsize, comm_point_callback_type* callback, void* callback_arg) { struct comm_point* c = (struct comm_point*)calloc(1, @@ -2587,6 +2588,7 @@ comm_point_create_tcp(struct comm_base *base, int fd, int num, size_t bufsize, c->timeout = NULL; c->tcp_is_reading = 0; c->tcp_byte_count = 0; + c->tcp_timeout_msec = idle_timeout; c->tcp_parent = NULL; c->max_tcp_count = num; c->cur_tcp_count = 0; @@ -2665,6 +2667,7 @@ comm_point_create_tcp_out(struct comm_base *base, size_t bufsize, c->timeout = NULL; c->tcp_is_reading = 0; c->tcp_byte_count = 0; + c->tcp_timeout_msec = TCP_QUERY_TIMEOUT; c->tcp_parent = NULL; c->max_tcp_count = 0; c->cur_tcp_count = 0; diff --git a/util/netevent.h b/util/netevent.h index 6819f57f8..921a05e43 100644 --- a/util/netevent.h +++ b/util/netevent.h @@ -443,6 +443,7 @@ struct comm_point* comm_point_create_udp_ancil(struct comm_base* base, * @param fd: file descriptor of open TCP socket set to listen nonblocking. * @param num: becomes max_tcp_count, the routine allocates that * many tcp handler commpoints. + * @param idle_timeout: TCP idle timeout in ms. * @param bufsize: size of buffer to create for handlers. * @param callback: callback function pointer for TCP handlers. * @param callback_arg: will be passed to your callback function. @@ -452,7 +453,7 @@ struct comm_point* comm_point_create_udp_ancil(struct comm_base* base, * Inits timeout to NULL. All handlers are on the free list. */ struct comm_point* comm_point_create_tcp(struct comm_base* base, - int fd, int num, size_t bufsize, + int fd, int num, int idle_timeout, size_t bufsize, comm_point_callback_type* callback, void* callback_arg); /**