Fix SSL/TLS timeout issues.

* connect.c (fd_read, fd_peek): Let implementation take care about timeout.
* gnutls.c (_do_handshake, _do_reauth, wgnutls_read_timeout): Fix support for interactive timeout.
* gnutls.c (wgnutls_peek): Let wgnutls_read_timeout() take care about timeout.
* openssl.c (openssl_read_peek): Fix 0 (-1) timeout.
* retr.c (fd_read_body): Avoid wrong 'interactive timeout'.
This commit is contained in:
Вячеслав Петрищев 2020-03-21 14:24:29 +06:00 committed by Tim Rühsen
parent c12a295496
commit 7a3a82faf8
4 changed files with 203 additions and 119 deletions

View File

@ -941,12 +941,14 @@ fd_read (int fd, char *buf, int bufsize, double timeout)
struct transport_info *info;
LAZY_RETRIEVE_INFO (info);
if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
return -1;
/* let imp->reader take care about timeout.
(or in worst case timeout can be 2*timeout) */
if (info && info->imp->reader)
return info->imp->reader (fd, buf, bufsize, info->ctx, timeout);
else
return sock_read (fd, buf, bufsize);
if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
return -1;
return sock_read (fd, buf, bufsize);
}
/* Like fd_read, except it provides a "preview" of the data that will
@ -966,12 +968,13 @@ fd_peek (int fd, char *buf, int bufsize, double timeout)
{
struct transport_info *info;
LAZY_RETRIEVE_INFO (info);
if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
return -1;
if (info && info->imp->peeker)
return info->imp->peeker (fd, buf, bufsize, info->ctx, timeout);
else
return sock_peek (fd, buf, bufsize);
if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
return -1;
return sock_peek (fd, buf, bufsize);
}
/* Write the entire contents of BUF to FD. If TIMEOUT is non-zero,

View File

@ -58,12 +58,20 @@ as that of the covered work. */
#include "host.h"
struct st_read_timer
{
double timeout;
double next_timeout;
struct ptimer *timer;
int timed_out;
};
static int
_do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock);
_do_handshake (gnutls_session_t session, int fd, struct st_read_timer *timeout);
#if GNUTLS_VERSION_NUMBER >= 0x030604
static int
_do_reauth (gnutls_session_t session, int fd, double timeout, int is_nonblock);
_do_reauth (gnutls_session_t session, int fd, struct st_read_timer *timeout);
#endif
static int
@ -255,17 +263,14 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout)
#ifdef F_GETFL
int flags = 0;
#endif
int ret = 0;
struct ptimer *timer = NULL;
struct wgnutls_transport_context *ctx = arg;
int timed_out = 0;
double next_timeout;
int ret = gnutls_record_check_pending (ctx->session);
struct st_read_timer read_timer = {(timeout == -1 ? opt.read_timeout : timeout), 0, NULL, 0};
errno = 0;
if (timeout == -1)
timeout = opt.read_timeout;
next_timeout = timeout;
if (timeout)
if (ret)
return gnutls_record_recv (ctx->session, buf, MIN (ret, bufsize));
if (read_timer.timeout)
{
#ifdef F_GETFL
flags = fcntl (fd, F_GETFL, 0);
@ -280,63 +285,83 @@ wgnutls_read_timeout (int fd, char *buf, int bufsize, void *arg, double timeout)
return -1;
#endif
timer = ptimer_new ();
if (timer == NULL)
read_timer.timer = ptimer_new ();
if (read_timer.timer == NULL)
{
ret = -1;
goto timer_err;
}
read_timer.next_timeout = read_timer.timeout;
}
ret = ctx->last_error;
do
{
ret = GNUTLS_E_AGAIN;
if (timeout)
if (ret == GNUTLS_E_REHANDSHAKE)
{
next_timeout = timeout - ptimer_measure (timer);
if (next_timeout <= 0)
int err;
DEBUGP (("GnuTLS: *** REHANDSHAKE while reading\n"));
if ((err = _do_handshake (ctx->session, fd, &read_timer)) != 0)
{
timed_out = 1;
ret = err;
break;
}
}
/* (rehandshake, reauth) needs some workaround for interactive timeout */
if (timeout == 0 || gnutls_record_check_pending (ctx->session)
|| select_fd_nb (fd, next_timeout, WAIT_FOR_READ))
#if GNUTLS_VERSION_NUMBER >= 0x030604
else if (ret == GNUTLS_E_REAUTH_REQUEST)
{
int err;
DEBUGP (("GnuTLS: *** re-authentication while reading\n"));
if ((err = _do_reauth (ctx->session, fd, &read_timer)) != 0)
{
ret = err;
break;
}
}
#endif
do
{
ret = gnutls_record_recv (ctx->session, buf, bufsize);
timed_out = timeout && ptimer_measure (timer) >= timeout;
if (!timed_out && ret == GNUTLS_E_REHANDSHAKE)
if (ret == GNUTLS_E_AGAIN && read_timer.timer)
{
DEBUGP (("GnuTLS: *** REHANDSHAKE while reading\n"));
if ((ret = _do_handshake (ctx->session, fd, opt.read_timeout, 1)) == 0)
ret = GNUTLS_E_AGAIN; /* restart reading */
int err = select_fd_nb (fd, read_timer.next_timeout, WAIT_FOR_READ);
if (err <= 0)
{
if (err == 0)
read_timer.timed_out = 1;
goto break_all;
}
if ( (read_timer.next_timeout = read_timer.timeout - ptimer_measure (read_timer.timer)) <= 0 )
{
read_timer.timed_out = 1;
goto break_all;
}
}
#if GNUTLS_VERSION_NUMBER >= 0x030604
if (!timed_out && ret == GNUTLS_E_REAUTH_REQUEST)
{
DEBUGP (("GnuTLS: *** re-authentication while reading\n"));
if ((ret = _do_reauth (ctx->session, fd, opt.read_timeout, 1)) == 0)
ret = GNUTLS_E_AGAIN; /* restart reading */
}
#endif
}
while (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED);
}
while (ret == GNUTLS_E_INTERRUPTED || (ret == GNUTLS_E_AGAIN && !timed_out));
while (ret == GNUTLS_E_REHANDSHAKE
#if GNUTLS_VERSION_NUMBER >= 0x030604
|| ret == GNUTLS_E_REAUTH_REQUEST
#endif
);
if (timeout)
break_all:
if (read_timer.timer)
{
ptimer_destroy (timer);
ptimer_destroy (read_timer.timer);
timer_err: ;
#ifdef F_GETFL
if (fcntl (fd, F_SETFL, flags) < 0)
return -1;
#else
const int zero = 0;
if (ioctl (fd, FIONBIO, &zero) < 0)
return -1;
{
const int zero = 0;
if (ioctl (fd, FIONBIO, &zero) < 0)
return -1;
}
#endif
if (timed_out && ret == GNUTLS_E_AGAIN)
if (read_timer.timed_out)
errno = ETIMEDOUT;
}
@ -346,7 +371,7 @@ timer_err: ;
static int
wgnutls_read (int fd, char *buf, int bufsize, void *arg, double timeout)
{
int ret = 0;
int ret;
struct wgnutls_transport_context *ctx = arg;
if (ctx->peeklen)
@ -362,22 +387,38 @@ wgnutls_read (int fd, char *buf, int bufsize, void *arg, double timeout)
}
ret = wgnutls_read_timeout (fd, buf, bufsize, arg, timeout);
if (ret < 0)
ctx->last_error = ret;
ctx->last_error = ret;
return ret;
}
static int
wgnutls_write (int fd _GL_UNUSED, char *buf, int bufsize, void *arg)
{
int ret;
struct wgnutls_transport_context *ctx = arg;
int ret = ctx->last_error;
/* it should never happen,
placed here only for debug msg. */
if (ret == GNUTLS_E_REHANDSHAKE)
{
DEBUGP (("GnuTLS: *** REHANDSHAKE while writing\n"));
if ((ret = _do_handshake (ctx->session, fd, NULL)) != 0)
goto ext;
}
#if GNUTLS_VERSION_NUMBER >= 0x030604
else if (ret == GNUTLS_E_REAUTH_REQUEST)
{
DEBUGP (("GnuTLS: *** re-authentication while writing\n"));
if ((ret = _do_reauth (ctx->session, fd, NULL)) != 0)
goto ext;
}
#endif
do
ret = gnutls_record_send (ctx->session, buf, bufsize);
while (ret == GNUTLS_E_INTERRUPTED || ret == GNUTLS_E_AGAIN);
if (ret < 0)
ctx->last_error = ret;
ext:
ctx->last_error = ret;
return ret;
}
@ -386,11 +427,13 @@ wgnutls_poll (int fd, double timeout, int wait_for, void *arg)
{
struct wgnutls_transport_context *ctx = arg;
if (timeout)
return ctx->peeklen || gnutls_record_check_pending (ctx->session)
|| select_fd (fd, timeout, wait_for);
else
return ctx->peeklen || gnutls_record_check_pending (ctx->session);
if ((wait_for & WAIT_FOR_READ)
&& (ctx->peeklen || gnutls_record_check_pending (ctx->session)))
return 1;
if (timeout == -1)
timeout = opt.read_timeout;
return select_fd (fd, timeout, wait_for);
}
static int
@ -410,13 +453,14 @@ wgnutls_peek (int fd, char *buf, int bufsize, void *arg, double timeout)
bufsize = sizeof ctx->peekbuf;
if (bufsize > offset)
{
if (timeout && gnutls_record_check_pending (ctx->session) == 0
{ /* let wgnutls_read_timeout() take care about timeout */
/*if (timeout && gnutls_record_check_pending (ctx->session) == 0
&& select_fd (fd, 0.0, WAIT_FOR_READ) <= 0)
read = 0;
else
else*/
read = wgnutls_read_timeout (fd, buf + offset, bufsize - offset,
ctx, timeout);
ctx->last_error = read;
if (read < 0)
{
if (offset)
@ -440,8 +484,17 @@ static const char *
wgnutls_errstr (int fd _GL_UNUSED, void *arg)
{
struct wgnutls_transport_context *ctx = arg;
return (ctx->last_error == GNUTLS_E_AGAIN && errno == ETIMEDOUT ?
strerror (ETIMEDOUT) : gnutls_strerror (ctx->last_error));
if (ctx->last_error > 0
|| ((ctx->last_error == GNUTLS_E_AGAIN
|| ctx->last_error == GNUTLS_E_REHANDSHAKE
#if GNUTLS_VERSION_NUMBER >= 0x030604
|| ctx->last_error == GNUTLS_E_REAUTH_REQUEST
#endif
) && errno == ETIMEDOUT))
return NULL;
return gnutls_strerror (ctx->last_error);
}
static void
@ -469,14 +522,16 @@ static struct transport_implementation wgnutls_transport =
};
static int
_do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock)
_do_handshake (gnutls_session_t session, int fd, struct st_read_timer *read_timer)
{
#ifdef F_GETFL
int flags = 0;
#endif
int err;
double next_timeout = (read_timer ? read_timer->next_timeout : opt.read_timeout);
if (!is_nonblock && timeout)
/* if (read_timer != NULL) - fd is already non blocking */
if (!read_timer && next_timeout)
{
#ifdef F_GETFL
flags = fcntl (fd, F_GETFL, 0);
@ -497,30 +552,46 @@ _do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock
{
err = gnutls_handshake (session);
if (timeout && err == GNUTLS_E_AGAIN)
if (err == GNUTLS_E_AGAIN && next_timeout)
{
int sel;
if (gnutls_record_get_direction (session))
{
/* wait for writeability */
err = select_fd_nb (fd, timeout, WAIT_FOR_WRITE);
sel = WAIT_FOR_WRITE;
}
else
{
/* wait for readability */
err = select_fd_nb (fd, timeout, WAIT_FOR_READ);
sel = WAIT_FOR_READ;
}
sel = select_fd_nb (fd, next_timeout, sel);
if (err <= 0)
if (sel <= 0)
{
if (err == 0)
if (sel == 0)
{
errno = ETIMEDOUT;
err = -1;
if (read_timer)
goto read_timedout;
else
{
errno = ETIMEDOUT;
err = -1;
}
}
break;
}
err = GNUTLS_E_AGAIN;
if (read_timer)
{
if ( (read_timer->next_timeout = read_timer->timeout - ptimer_measure (read_timer->timer)) <= 0 )
{
read_timedout: /* return GNUTLS_E_REHANDSHAKE for gnutls_read */
err = GNUTLS_E_REHANDSHAKE;
read_timer->timed_out = 1;
break;
}
next_timeout = read_timer->next_timeout;
}
}
else if (err < 0)
{
@ -537,7 +608,7 @@ _do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock
}
while (err && gnutls_error_is_fatal (err) == 0);
if (!is_nonblock && timeout)
if (!read_timer && next_timeout)
{
#ifdef F_GETFL
if (fcntl (fd, F_SETFL, flags) < 0)
@ -554,14 +625,16 @@ _do_handshake (gnutls_session_t session, int fd, double timeout, int is_nonblock
#if GNUTLS_VERSION_NUMBER >= 0x030604
static int
_do_reauth (gnutls_session_t session, int fd, double timeout, int is_nonblock)
_do_reauth (gnutls_session_t session, int fd, struct st_read_timer *read_timer)
{
#ifdef F_GETFL
int flags = 0;
#endif
int err;
double next_timeout = (read_timer ? read_timer->next_timeout : opt.read_timeout);
if (!is_nonblock && timeout)
/* if (read_timer != NULL) - fd is already non blocking */
if (!read_timer && next_timeout)
{
#ifdef F_GETFL
flags = fcntl (fd, F_GETFL, 0);
@ -582,30 +655,46 @@ _do_reauth (gnutls_session_t session, int fd, double timeout, int is_nonblock)
{
err = gnutls_reauth (session, 0);
if (timeout && err == GNUTLS_E_AGAIN)
if (err == GNUTLS_E_AGAIN && next_timeout)
{
int sel;
if (gnutls_record_get_direction (session))
{
/* wait for writeability */
err = select_fd_nb (fd, timeout, WAIT_FOR_WRITE);
sel = WAIT_FOR_WRITE;
}
else
{
/* wait for readability */
err = select_fd_nb (fd, timeout, WAIT_FOR_READ);
sel = WAIT_FOR_READ;
}
sel = select_fd_nb (fd, next_timeout, sel);
if (err <= 0)
if (sel <= 0)
{
if (err == 0)
if (sel == 0)
{
errno = ETIMEDOUT;
err = -1;
if (read_timer)
goto read_timedout;
else
{
errno = ETIMEDOUT;
err = -1;
}
}
break;
}
err = GNUTLS_E_AGAIN;
if (read_timer)
{
if ( (read_timer->next_timeout = read_timer->timeout - ptimer_measure (read_timer->timer)) <= 0 )
{
read_timedout: /* return GNUTLS_E_REAUTH_REQUEST for gnutls_read */
err = GNUTLS_E_REAUTH_REQUEST;
read_timer->timed_out = 1;
break;
}
next_timeout = read_timer->next_timeout;
}
}
else if (err < 0)
{
@ -614,7 +703,7 @@ _do_reauth (gnutls_session_t session, int fd, double timeout, int is_nonblock)
}
while (err && gnutls_error_is_fatal (err) == 0);
if (!is_nonblock && timeout)
if (!read_timer && next_timeout)
{
#ifdef F_GETFL
if (fcntl (fd, F_SETFL, flags) < 0)
@ -840,7 +929,7 @@ ssl_connect_wget (int fd, const char *hostname, int *continue_session)
}
}
err = _do_handshake (session, fd, opt.read_timeout, 0);
err = _do_handshake (session, fd, NULL);
if (err < 0)
{

View File

@ -482,7 +482,8 @@ struct openssl_read_args
int retval;
};
static void openssl_read_peek_callback(void *arg)
static void
openssl_read_peek_callback(void *arg)
{
struct openssl_read_args *args = (struct openssl_read_args *) arg;
struct openssl_transport_context *ctx = args->ctx;
@ -506,9 +507,6 @@ openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ss
struct openssl_transport_context *ctx = arg;
int ret = SSL_pending (ctx->conn);
if (bufsize == 0)
return 0;
if (ret)
ret = fn (ctx->conn, buf, MIN (bufsize, ret));
else
@ -520,7 +518,6 @@ openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ss
args.fn = fn;
args.ctx = ctx;
errno = 0;
if (timeout == -1)
timeout = opt.read_timeout;
@ -540,14 +537,14 @@ openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ss
#ifdef F_GETFL
#define NONBLOCK_DECL int flags = 0;
#define FD_SET_NONBLOCKED(_fd) \
flags = fcntl (_fd, F_GETFL, 0);\
if (flags < 0)\
return flags;\
if (fcntl (_fd, F_SETFL, flags | O_NONBLOCK))\
return -1;
flags = fcntl (_fd, F_GETFL, 0); \
if (flags < 0) \
return flags; \
if (fcntl (_fd, F_SETFL, flags | O_NONBLOCK)) \
return -1;
#define FD_SET_BLOCKED(_fd) \
if (fcntl (_fd, F_SETFL, flags) < 0)\
return -1;
if (fcntl (_fd, F_SETFL, flags) < 0) \
return -1;
#else
#define NONBLOCK_DECL
#define FD_SET_NONBLOCKED(_fd) \
@ -574,8 +571,7 @@ openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ss
_ret = -1; \
else \
{ \
if (_timeout == -1) \
_timeout = opt.read_timeout;
double next_timeout = _timeout;
#define TIMER_FREE(_fd) \
ptimer_destroy (timer); \
@ -590,7 +586,6 @@ openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ss
#define TIMER_WAIT(_fd, _conn, _ret, _timeout) \
{ \
int wait_for; \
double next_timeout; \
int err = SSL_get_error(_conn, _ret); \
if (err == SSL_ERROR_WANT_READ) \
wait_for = WAIT_FOR_READ; \
@ -598,20 +593,18 @@ openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ss
wait_for = WAIT_FOR_WRITE; \
else \
break; \
next_timeout = _timeout - ptimer_measure (timer); \
if (next_timeout < 0) \
{ \
timed_out = 1; \
break; \
} \
err = select_fd_nb (_fd, next_timeout, wait_for); \
if (err <= 0) \
{ \
if (err == 0) \
timedout: \
timed_out = 1; \
_ret = -1; \
break; \
} \
next_timeout = _timeout - ptimer_measure (timer); \
if (next_timeout <= 0) \
goto timedout; \
}
static int
@ -638,13 +631,10 @@ static int
openssl_read_peek (int fd, char *buf, int bufsize, void *arg, double timeout, ssl_fn_t fn)
{
struct openssl_transport_context *ctx = arg;
int ret;
int ret = SSL_pending (ctx->conn);
if (bufsize == 0)
return 0;
/* avoid wrong 'interactive timeout' when errno == ETIMEDOUT */
errno = 0;
ret = SSL_pending (ctx->conn);
if (timeout == -1)
timeout = opt.read_timeout;
/* If we have data available for immediate read, simply return that,
or do blocked read when timeout == 0 */
if (ret || timeout == 0)
@ -689,7 +679,7 @@ openssl_poll (int fd, double timeout, int wait_for, void *arg)
{
struct openssl_transport_context *ctx = arg;
SSL *conn = ctx->conn;
if (SSL_pending (conn))
if ((wait_for & WAIT_FOR_READ) && SSL_pending (conn))
return 1;
/* if (timeout == 0)
return 1; */

View File

@ -417,6 +417,8 @@ fd_read_body (const char *downloaded_filename, int fd, FILE *out, wgint toread,
timeout, so that the gauge can be updated regularly even
when the data arrives very slowly or stalls. */
tmout = 0.95;
/* avoid wrong 'interactive timeout' */
errno = 0;
if (opt.read_timeout)
{
double waittm;