Skip to content

extmod/modussl: fix socket and ussl read/write errors for non-blocking sockets #6615

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions docs/library/ussl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,36 @@ facilities for network sockets, both client-side and server-side.
Functions
---------

.. function:: ussl.wrap_socket(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=CERT_NONE, ca_certs=None)

.. function:: ussl.wrap_socket(sock, server_side=False, keyfile=None, certfile=None, cert_reqs=CERT_NONE, ca_certs=None, do_handshake=True)
Takes a `stream` *sock* (usually usocket.socket instance of ``SOCK_STREAM`` type),
and returns an instance of ssl.SSLSocket, which wraps the underlying stream in
an SSL context. Returned object has the usual `stream` interface methods like
``read()``, ``write()``, etc. In MicroPython, the returned object does not expose
socket interface and methods like ``recv()``, ``send()``. In particular, a
server-side SSL socket should be created from a normal socket returned from
``read()``, ``write()``, etc.
A server-side SSL socket should be created from a normal socket returned from
:meth:`~usocket.socket.accept()` on a non-SSL listening server socket.

- *do_handshake* determines whether the handshake is done as part of the ``wrap_socket``
or whether it is deferred to be done as part of the initial reads or writes
(there is no ``do_handshake`` method as in CPython).
For blocking sockets doing the handshake immediately is standard. For non-blocking
sockets (i.e. when the *sock* passed into ``wrap_socket`` is in non-blocking mode)
the handshake should generally be deferred because otherwise ``wrap_socket`` blocks
until it completes. Note that in AXTLS the handshake can be deferred until the first
read or write but it then blocks until completion.

Depending on the underlying module implementation in a particular
:term:`MicroPython port`, some or all keyword arguments above may be not supported.

.. warning::
.. warnings::

Some implementations of ``ussl`` module do NOT validate server certificates,
which makes an SSL connection established prone to man-in-the-middle attacks.

CPython's ``wrap_socket`` returns an ``SSLSocket`` object which has methods typical
for sockets, such as ``send``, ``recv``, etc. MicroPython's ``wrap_socket``
returns an object more similar to CPython's ``SSLObject`` which does not have
these socket methods.

Exceptions
----------

Expand Down
32 changes: 27 additions & 5 deletions extmod/modussl_axtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,16 @@ STATIC mp_obj_ssl_socket_t *ussl_socket_new(mp_obj_t sock, struct ssl_args *args
o->ssl_sock = ssl_client_new(o->ssl_ctx, (long)sock, NULL, 0, ext);

if (args->do_handshake.u_bool) {
int res = ssl_handshake_status(o->ssl_sock);

if (res != SSL_OK) {
ussl_raise_error(res);
int r = ssl_handshake_status(o->ssl_sock);

if (r != SSL_OK) {
ssl_display_error(r);
if (r == SSL_CLOSE_NOTIFY || r == SSL_ERROR_CONN_LOST) { // EOF
r = MP_ENOTCONN;
} else if (r == SSL_EAGAIN) {
r = MP_EAGAIN;
}
ussl_raise_error(r);
}
}

Expand Down Expand Up @@ -242,8 +248,24 @@ STATIC mp_uint_t ussl_socket_write(mp_obj_t o_in, const void *buf, mp_uint_t siz
return MP_STREAM_ERROR;
}

mp_int_t r = ssl_write(o->ssl_sock, buf, size);
mp_int_t r;
eagain:
r = ssl_write(o->ssl_sock, buf, size);
if (r == 0) {
// see comment in ussl_socket_read above
if (o->blocking) {
goto eagain;
} else {
r = SSL_EAGAIN;
}
}
if (r < 0) {
if (r == SSL_CLOSE_NOTIFY || r == SSL_ERROR_CONN_LOST) {
return 0; // EOF
}
if (r == SSL_EAGAIN) {
r = MP_EAGAIN;
}
*errcode = r;
return MP_STREAM_ERROR;
}
Expand Down
3 changes: 2 additions & 1 deletion extmod/modussl_mbedtls.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ STATIC int _mbedtls_ssl_send(void *ctx, const byte *buf, size_t len) {
}
}

// _mbedtls_ssl_recv is called by mbedtls to receive bytes from the underlying socket
STATIC int _mbedtls_ssl_recv(void *ctx, byte *buf, size_t len) {
mp_obj_t sock = *(mp_obj_t *)ctx;

Expand Down Expand Up @@ -171,7 +172,7 @@ STATIC mp_obj_ssl_socket_t *socket_new(mp_obj_t sock, struct ssl_args *args) {
mbedtls_pk_init(&o->pkey);
mbedtls_ctr_drbg_init(&o->ctr_drbg);
#ifdef MBEDTLS_DEBUG_C
// Debug level (0-4)
// Debug level (0-4) 1=warning, 2=info, 3=debug, 4=verbose
mbedtls_debug_set_threshold(0);
#endif

Expand Down
36 changes: 14 additions & 22 deletions ports/esp32/modsocket.c
Original file line number Diff line number Diff line change
Expand Up @@ -152,16 +152,6 @@ void usocket_events_handler(void) {

#endif // MICROPY_PY_USOCKET_EVENTS

NORETURN static void exception_from_errno(int _errno) {
// Here we need to convert from lwip errno values to MicroPython's standard ones
if (_errno == EADDRINUSE) {
_errno = MP_EADDRINUSE;
} else if (_errno == EINPROGRESS) {
_errno = MP_EINPROGRESS;
}
mp_raise_OSError(_errno);
}

static inline void check_for_exceptions(void) {
mp_handle_pending(true);
}
Expand Down Expand Up @@ -284,7 +274,7 @@ STATIC mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, siz

sock->fd = lwip_socket(sock->domain, sock->type, sock->proto);
if (sock->fd < 0) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}
_socket_settimeout(sock, UINT64_MAX);

Expand All @@ -298,7 +288,7 @@ STATIC mp_obj_t socket_bind(const mp_obj_t arg0, const mp_obj_t arg1) {
int r = lwip_bind(self->fd, res->ai_addr, res->ai_addrlen);
lwip_freeaddrinfo(res);
if (r < 0) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}
return mp_const_none;
}
Expand All @@ -309,7 +299,7 @@ STATIC mp_obj_t socket_listen(const mp_obj_t arg0, const mp_obj_t arg1) {
int backlog = mp_obj_get_int(arg1);
int r = lwip_listen(self->fd, backlog);
if (r < 0) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}
return mp_const_none;
}
Expand All @@ -330,7 +320,7 @@ STATIC mp_obj_t socket_accept(const mp_obj_t arg0) {
break;
}
if (errno != EAGAIN) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}
check_for_exceptions();
}
Expand Down Expand Up @@ -372,7 +362,7 @@ STATIC mp_obj_t socket_connect(const mp_obj_t arg0, const mp_obj_t arg1) {
MP_THREAD_GIL_ENTER();
lwip_freeaddrinfo(res);
if (r != 0) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}

return mp_const_none;
Expand All @@ -391,7 +381,7 @@ STATIC mp_obj_t socket_setsockopt(size_t n_args, const mp_obj_t *args) {
int val = mp_obj_get_int(args[3]);
int ret = lwip_setsockopt(self->fd, SOL_SOCKET, opt, &val, sizeof(int));
if (ret != 0) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}
break;
}
Expand Down Expand Up @@ -542,7 +532,7 @@ mp_obj_t _socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in,
int errcode;
mp_uint_t ret = _socket_read_data(self_in, vstr.buf, len, from, from_len, &errcode);
if (ret == MP_STREAM_ERROR) {
exception_from_errno(errcode);
mp_raise_OSError(errcode);
}

vstr.len = ret;
Expand Down Expand Up @@ -575,16 +565,17 @@ int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) {
MP_THREAD_GIL_EXIT();
int r = lwip_write(sock->fd, data + sentlen, datalen - sentlen);
MP_THREAD_GIL_ENTER();
if (r < 0 && errno != EWOULDBLOCK) {
exception_from_errno(errno);
// lwip returns EINPROGRESS when trying to send right after a non-blocking connect
if (r < 0 && errno != EWOULDBLOCK && errno != EINPROGRESS) {
mp_raise_OSError(errno);
}
if (r > 0) {
sentlen += r;
}
check_for_exceptions();
}
if (sentlen == 0) {
mp_raise_OSError(MP_ETIMEDOUT);
mp_raise_OSError(sock->retries == 0 ? MP_EWOULDBLOCK : MP_ETIMEDOUT);
}
return sentlen;
}
Expand Down Expand Up @@ -634,7 +625,7 @@ STATIC mp_obj_t socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_
return mp_obj_new_int_from_uint(ret);
}
if (ret == -1 && errno != EWOULDBLOCK) {
exception_from_errno(errno);
mp_raise_OSError(errno);
}
check_for_exceptions();
}
Expand Down Expand Up @@ -667,7 +658,8 @@ STATIC mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_
if (r > 0) {
return r;
}
if (r < 0 && errno != EWOULDBLOCK) {
// lwip returns MP_EINPROGRESS when trying to write right after a non-blocking connect
if (r < 0 && errno != EWOULDBLOCK && errno != EINPROGRESS) {
*errcode = errno;
return MP_STREAM_ERROR;
}
Expand Down
2 changes: 1 addition & 1 deletion ports/esp32/mpconfigport.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
#define MICROPY_MODULE_FROZEN_MPY (1)
#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool
#define MICROPY_CAN_OVERRIDE_BUILTINS (1)
#define MICROPY_USE_INTERNAL_ERRNO (1)
#define MICROPY_USE_INTERNAL_ERRNO (0) // errno.h from xtensa-esp32-elf/sys-include/sys
#define MICROPY_USE_INTERNAL_PRINTF (0) // ESP32 SDK requires its own printf
#define MICROPY_ENABLE_SCHEDULER (1)
#define MICROPY_SCHEDULER_DEPTH (8)
Expand Down
5 changes: 3 additions & 2 deletions tests/net_hosted/accept_timeout.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

try:
import usocket as socket
import uerrno as errno
except:
import socket
import socket, errno

try:
socket.socket.settimeout
Expand All @@ -18,5 +19,5 @@
try:
s.accept()
except OSError as er:
print(er.args[0] in (110, "timed out")) # 110 is ETIMEDOUT; CPython uses a string
print(er.args[0] in (errno.ETIMEDOUT, "timed out")) # CPython uses a string instead of errno
s.close()
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy