summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Doc/library/ssl.rst18
-rw-r--r--Modules/_ssl.c82
2 files changed, 83 insertions, 17 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index ef8540c0f7..ccaa18360d 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -830,6 +830,11 @@ SSL Sockets
.. versionchanged:: 3.5
The :meth:`sendfile` method was added.
+ .. versionchanged:: 3.5
+ The :meth:`shutdown` does not reset the socket timeout each time bytes
+ are received or sent. The socket timeout is now to maximum total duration
+ of the shutdown.
+
SSL sockets also have the following additional methods and attributes:
@@ -845,6 +850,11 @@ SSL sockets also have the following additional methods and attributes:
As at any time a re-negotiation is possible, a call to :meth:`read` can also
cause write operations.
+ .. versionchanged:: 3.5
+ The socket timeout is no more reset each time bytes are received or sent.
+ The socket timeout is now to maximum total duration to read up to *len*
+ bytes.
+
.. method:: SSLSocket.write(buf)
Write *buf* to the SSL socket and return the number of bytes written. The
@@ -856,6 +866,10 @@ SSL sockets also have the following additional methods and attributes:
As at any time a re-negotiation is possible, a call to :meth:`write` can
also cause read operations.
+ .. versionchanged:: 3.5
+ The socket timeout is no more reset each time bytes are received or sent.
+ The socket timeout is now to maximum total duration to write *buf*.
+
.. note::
The :meth:`~SSLSocket.read` and :meth:`~SSLSocket.write` methods are the
@@ -877,6 +891,10 @@ SSL sockets also have the following additional methods and attributes:
:attr:`~SSLContext.check_hostname` attribute of the socket's
:attr:`~SSLSocket.context` is true.
+ .. versionchanged:: 3.5
+ The socket timeout is no more reset each time bytes are received or sent.
+ The socket timeout is now to maximum total duration of the handshake.
+
.. method:: SSLSocket.getpeercert(binary_form=False)
If there is no certificate for the peer on the other end of the connection,
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index bb838b0dba..b7b39dd372 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -223,7 +223,7 @@ static PyTypeObject PySSLMemoryBIO_Type;
static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args);
static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args);
-static int PySSL_select(PySocketSockObject *s, int writing);
+static int PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout);
static PyObject *PySSL_peercert(PySSLSocket *self, PyObject *args);
static PyObject *PySSL_cipher(PySSLSocket *self);
@@ -248,6 +248,10 @@ typedef enum {
#define GET_SOCKET(obj) ((obj)->Socket ? \
(PySocketSockObject *) PyWeakref_GetObject((obj)->Socket) : NULL)
+/* If sock is NULL, use a timeout of 0 second */
+#define GET_SOCKET_TIMEOUT(sock) \
+ ((sock != NULL) ? (sock)->sock_timeout : 0)
+
/*
* SSL errors.
*/
@@ -565,6 +569,8 @@ static PyObject *PySSL_SSLdo_handshake(PySSLSocket *self)
int err;
int sockstate, nonblocking;
PySocketSockObject *sock = GET_SOCKET(self);
+ _PyTime_t timeout, deadline = 0;
+ int has_timeout;
if (sock) {
if (((PyObject*)sock) == Py_None) {
@@ -580,6 +586,11 @@ static PyObject *PySSL_SSLdo_handshake(PySSLSocket *self)
BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
}
+ timeout = GET_SOCKET_TIMEOUT(sock);
+ has_timeout = (timeout > 0);
+ if (has_timeout)
+ deadline = _PyTime_GetMonotonicClock() + timeout;
+
/* Actually negotiate SSL connection */
/* XXX If SSL_do_handshake() returns 0, it's also a failure. */
do {
@@ -591,10 +602,13 @@ static PyObject *PySSL_SSLdo_handshake(PySSLSocket *self)
if (PyErr_CheckSignals())
goto error;
+ if (has_timeout)
+ timeout = deadline - _PyTime_GetMonotonicClock();
+
if (err == SSL_ERROR_WANT_READ) {
- sockstate = PySSL_select(sock, 0);
+ sockstate = PySSL_select(sock, 0, timeout);
} else if (err == SSL_ERROR_WANT_WRITE) {
- sockstate = PySSL_select(sock, 1);
+ sockstate = PySSL_select(sock, 1, timeout);
} else {
sockstate = SOCKET_OPERATION_OK;
}
@@ -1611,7 +1625,7 @@ static void PySSL_dealloc(PySSLSocket *self)
*/
static int
-PySSL_select(PySocketSockObject *s, int writing)
+PySSL_select(PySocketSockObject *s, int writing, _PyTime_t timeout)
{
int rc;
#ifdef HAVE_POLL
@@ -1624,10 +1638,14 @@ PySSL_select(PySocketSockObject *s, int writing)
#endif
/* Nothing to do unless we're in timeout mode (not non-blocking) */
- if ((s == NULL) || (s->sock_timeout == 0))
+ if ((s == NULL) || (timeout == 0))
return SOCKET_IS_NONBLOCKING;
- else if (s->sock_timeout < 0)
- return SOCKET_IS_BLOCKING;
+ else if (timeout < 0) {
+ if (s->sock_timeout > 0)
+ return SOCKET_HAS_TIMED_OUT;
+ else
+ return SOCKET_IS_BLOCKING;
+ }
/* Guard against closed socket */
if (s->sock_fd < 0)
@@ -1639,8 +1657,8 @@ PySSL_select(PySocketSockObject *s, int writing)
pollfd.fd = s->sock_fd;
pollfd.events = writing ? POLLOUT : POLLIN;
- /* s->sock_timeout is in seconds, timeout in ms */
- ms = (int)_PyTime_AsMilliseconds(s->sock_timeout, _PyTime_ROUND_CEILING);
+ /* timeout is in seconds, poll() uses milliseconds */
+ ms = (int)_PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
assert(ms <= INT_MAX);
PySSL_BEGIN_ALLOW_THREADS
@@ -1651,7 +1669,7 @@ PySSL_select(PySocketSockObject *s, int writing)
if (!_PyIsSelectable_fd(s->sock_fd))
return SOCKET_TOO_LARGE_FOR_SELECT;
- _PyTime_AsTimeval_noraise(s->sock_timeout, &tv, _PyTime_ROUND_CEILING);
+ _PyTime_AsTimeval_noraise(timeout, &tv, _PyTime_ROUND_CEILING);
FD_ZERO(&fds);
FD_SET(s->sock_fd, &fds);
@@ -1679,6 +1697,8 @@ static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args)
int err;
int nonblocking;
PySocketSockObject *sock = GET_SOCKET(self);
+ _PyTime_t timeout, deadline = 0;
+ int has_timeout;
if (sock != NULL) {
if (((PyObject*)sock) == Py_None) {
@@ -1707,7 +1727,12 @@ static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args)
BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
}
- sockstate = PySSL_select(sock, 1);
+ timeout = GET_SOCKET_TIMEOUT(sock);
+ has_timeout = (timeout > 0);
+ if (has_timeout)
+ deadline = _PyTime_GetMonotonicClock() + timeout;
+
+ sockstate = PySSL_select(sock, 1, timeout);
if (sockstate == SOCKET_HAS_TIMED_OUT) {
PyErr_SetString(PySocketModule.timeout_error,
"The write operation timed out");
@@ -1731,10 +1756,13 @@ static PyObject *PySSL_SSLwrite(PySSLSocket *self, PyObject *args)
if (PyErr_CheckSignals())
goto error;
+ if (has_timeout)
+ timeout = deadline - _PyTime_GetMonotonicClock();
+
if (err == SSL_ERROR_WANT_READ) {
- sockstate = PySSL_select(sock, 0);
+ sockstate = PySSL_select(sock, 0, timeout);
} else if (err == SSL_ERROR_WANT_WRITE) {
- sockstate = PySSL_select(sock, 1);
+ sockstate = PySSL_select(sock, 1, timeout);
} else {
sockstate = SOCKET_OPERATION_OK;
}
@@ -1801,6 +1829,8 @@ static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args)
int err;
int nonblocking;
PySocketSockObject *sock = GET_SOCKET(self);
+ _PyTime_t timeout, deadline = 0;
+ int has_timeout;
if (sock != NULL) {
if (((PyObject*)sock) == Py_None) {
@@ -1842,6 +1872,11 @@ static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args)
BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
}
+ timeout = GET_SOCKET_TIMEOUT(sock);
+ has_timeout = (timeout > 0);
+ if (has_timeout)
+ deadline = _PyTime_GetMonotonicClock() + timeout;
+
do {
PySSL_BEGIN_ALLOW_THREADS
count = SSL_read(self->ssl, mem, len);
@@ -1851,10 +1886,13 @@ static PyObject *PySSL_SSLread(PySSLSocket *self, PyObject *args)
if (PyErr_CheckSignals())
goto error;
+ if (has_timeout)
+ timeout = deadline - _PyTime_GetMonotonicClock();
+
if (err == SSL_ERROR_WANT_READ) {
- sockstate = PySSL_select(sock, 0);
+ sockstate = PySSL_select(sock, 0, timeout);
} else if (err == SSL_ERROR_WANT_WRITE) {
- sockstate = PySSL_select(sock, 1);
+ sockstate = PySSL_select(sock, 1, timeout);
} else if (err == SSL_ERROR_ZERO_RETURN &&
SSL_get_shutdown(self->ssl) == SSL_RECEIVED_SHUTDOWN)
{
@@ -1908,6 +1946,8 @@ static PyObject *PySSL_SSLshutdown(PySSLSocket *self)
int err, ssl_err, sockstate, nonblocking;
int zeros = 0;
PySocketSockObject *sock = GET_SOCKET(self);
+ _PyTime_t timeout, deadline = 0;
+ int has_timeout;
if (sock != NULL) {
/* Guard against closed socket */
@@ -1924,6 +1964,11 @@ static PyObject *PySSL_SSLshutdown(PySSLSocket *self)
BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
}
+ timeout = GET_SOCKET_TIMEOUT(sock);
+ has_timeout = (timeout > 0);
+ if (has_timeout)
+ deadline = _PyTime_GetMonotonicClock() + timeout;
+
while (1) {
PySSL_BEGIN_ALLOW_THREADS
/* Disable read-ahead so that unwrap can work correctly.
@@ -1953,12 +1998,15 @@ static PyObject *PySSL_SSLshutdown(PySSLSocket *self)
continue;
}
+ if (has_timeout)
+ timeout = deadline - _PyTime_GetMonotonicClock();
+
/* Possibly retry shutdown until timeout or failure */
ssl_err = SSL_get_error(self->ssl, err);
if (ssl_err == SSL_ERROR_WANT_READ)
- sockstate = PySSL_select(sock, 0);
+ sockstate = PySSL_select(sock, 0, timeout);
else if (ssl_err == SSL_ERROR_WANT_WRITE)
- sockstate = PySSL_select(sock, 1);
+ sockstate = PySSL_select(sock, 1, timeout);
else
break;