diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.conf b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.conf index ff1e9baebf0..42c247c8488 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.conf +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico2_w_zephyr/board.conf @@ -3,6 +3,12 @@ CONFIG_NET_IPV4=y CONFIG_NET_DHCPV4=y CONFIG_NET_SOCKETS=y +# TCP. Nothing in the port or the SoC defaults turns it on, so until now every +# SOCK_STREAM socket on these boards failed inside net_context_get() with +# EPROTOTYPE -- surfaced by socketpool as "Out of sockets" -- which also means +# the web workflow's listener never opened. HTTP(S) clients and servers need it. +CONFIG_NET_TCP=y + CONFIG_WIFI=y CONFIG_WIFI_NM_WPA_SUPPLICANT_LEGACY_CRYPTO=n CONFIG_NET_L2_WIFI_MGMT=y @@ -19,6 +25,21 @@ CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256=y CONFIG_MBEDTLS_ENTROPY_C=y CONFIG_MBEDTLS_CTR_DRBG_C=y +# The collector's captive portal serves HTTPS from a PEM certificate chain +# (fullchain.pem / key.pem via ssl.SSLContext.load_cert_chain); mbedTLS only +# parses DER unless PEM decoding is compiled in. +CONFIG_MBEDTLS_PEM_PARSE_C=y + +# TLS 1.2 ECDHE. Zephyr's mbedTLS 4 default serves secp256r1 through the p256-m +# PSA driver and leaves the builtin ECP module out. mbedTLS then defines a dummy +# MBEDTLS_ECP_MAX_BITS of 1, and ssl.h sizes the TLS 1.2 premaster buffer +# (union mbedtls_ssl_premaster_secret._pms_ecdh[MBEDTLS_ECP_MAX_BYTES]) from +# it, so every ECDHE key agreement fails with PSA_ERROR_BUFFER_TOO_SMALL -- a +# 1-byte buffer for a 32-byte shared secret -- and surfaces as OSError 138 on +# the first read of any TLS connection, client or server. Keep the builtin +# ECP module (p256-m off) until the header sizes that buffer from PSA. +CONFIG_MBEDTLS_PSA_P256M_DRIVER_ENABLED=n + # Bluetooth over the shared gSPI bus (CONFIG_BT itself comes from the # zephyr,bt-hci chosen node in the overlay). # @@ -46,8 +67,16 @@ CONFIG_BT_EXT_ADV=n CONFIG_NET_MAX_CONTEXTS=12 CONFIG_NET_MAX_CONN=16 +# File descriptors. Zephyr sizes the fd table from the subsystems' declared +# needs (ZVFS_OPEN_ADD_SIZE_*), which came to 4 here; the DHCPv4 server takes +# one socket plus the socket-service eventfd, leaving a program two, and +# socketpool.socket() then fails with "Out of sockets" at the first TLS +# listener. Give sockets a real ceiling (each fd entry is ~12 bytes). +CONFIG_ZVFS_OPEN_MAX=16 + # Zephyr defaults BT_MAX_CONN to 1, which would limit the hub to a single # peer and force a connectionless (advertisement-only) node protocol. The # CYW43439 controller is not the constraint; raise it so nodes can connect # and sync data. Costs RAM per connection. CONFIG_BT_MAX_CONN=4 + diff --git a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.conf b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.conf index ad03cc6d10e..100e830d220 100644 --- a/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.conf +++ b/ports/zephyr-cp/boards/raspberrypi/rpi_pico_w_zephyr/board.conf @@ -3,6 +3,12 @@ CONFIG_NET_IPV4=y CONFIG_NET_DHCPV4=y CONFIG_NET_SOCKETS=y +# TCP. Nothing in the port or the SoC defaults turns it on, so until now every +# SOCK_STREAM socket on these boards failed inside net_context_get() with +# EPROTOTYPE -- surfaced by socketpool as "Out of sockets" -- which also means +# the web workflow's listener never opened. HTTP(S) clients and servers need it. +CONFIG_NET_TCP=y + CONFIG_WIFI=y CONFIG_WIFI_NM_WPA_SUPPLICANT_LEGACY_CRYPTO=n CONFIG_NET_L2_WIFI_MGMT=y @@ -19,6 +25,28 @@ CONFIG_MBEDTLS_CIPHERSUITE_TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256=y CONFIG_MBEDTLS_ENTROPY_C=y CONFIG_MBEDTLS_CTR_DRBG_C=y +# The collector's captive portal serves HTTPS from a PEM certificate chain +# (fullchain.pem / key.pem via ssl.SSLContext.load_cert_chain); mbedTLS only +# parses DER unless PEM decoding is compiled in. +CONFIG_MBEDTLS_PEM_PARSE_C=y + +# TLS 1.2 ECDHE. Zephyr's mbedTLS 4 default serves secp256r1 through the p256-m +# PSA driver and leaves the builtin ECP module out. mbedTLS then defines a dummy +# MBEDTLS_ECP_MAX_BITS of 1, and ssl.h sizes the TLS 1.2 premaster buffer +# (union mbedtls_ssl_premaster_secret._pms_ecdh[MBEDTLS_ECP_MAX_BYTES]) from +# it, so every ECDHE key agreement fails with PSA_ERROR_BUFFER_TOO_SMALL -- a +# 1-byte buffer for a 32-byte shared secret -- and surfaces as OSError 138 on +# the first read of any TLS connection, client or server. Keep the builtin +# ECP module (p256-m off) until the header sizes that buffer from PSA. +CONFIG_MBEDTLS_PSA_P256M_DRIVER_ENABLED=n + +# File descriptors. Zephyr sizes the fd table from the subsystems' declared +# needs (ZVFS_OPEN_ADD_SIZE_*), which came to 4 here; the DHCPv4 server takes +# one socket plus the socket-service eventfd, leaving a program two, and +# socketpool.socket() then fails with "Out of sockets" at the first TLS +# listener. Give sockets a real ceiling (each fd entry is ~12 bytes). +CONFIG_ZVFS_OPEN_MAX=16 + CONFIG_TEST_RANDOM_GENERATOR=y # Bluetooth over the shared gSPI bus (CONFIG_BT itself comes from the diff --git a/ports/zephyr-cp/common-hal/socketpool/Socket.c b/ports/zephyr-cp/common-hal/socketpool/Socket.c index bb626857a98..4e49b8b11e5 100644 --- a/ports/zephyr-cp/common-hal/socketpool/Socket.c +++ b/ports/zephyr-cp/common-hal/socketpool/Socket.c @@ -9,6 +9,7 @@ #include "shared/runtime/interrupt_char.h" #include "py/mperrno.h" #include "py/runtime.h" +#include "bindings/zephyr_kernel/__init__.h" #include "shared-bindings/socketpool/SocketPool.h" #include "common-hal/socketpool/__init__.h" #include "common-hal/wifi/__init__.h" @@ -156,6 +157,13 @@ static bool _socketpool_socket(socketpool_socketpool_obj_t *self, sock->ipproto = ipproto; sock->pool = self; sock->timeout_ms = (uint)-1; + // The object was allocated with a finaliser and zeroed, so until a socket + // exists it must read as closed (num < 0). Left at 0 after a failed + // zsock_socket(), the finaliser later called zsock_shutdown(0) -- fd 0 + // belongs to the socket service's eventfd, whose shorter vtable has no + // shutdown slot -- and the CPU branched into cdc_acm_1's data (usage + // fault, halt). + sock->num = -1; int socknum = zsock_socket(sock->family, sock->type, sock->ipproto); if (socknum < 0) { @@ -205,6 +213,11 @@ socketpool_socket_obj_t *common_hal_socketpool_socket(socketpool_socketpool_obj_ socketpool_socket_obj_t *sock = mp_obj_malloc_with_finaliser(socketpool_socket_obj_t, &socketpool_socket_type); if (!_socketpool_socket(self, family, type, proto, sock)) { + // Say which limit was hit (ENOMEM: net_contexts, ENFILE/EMFILE: the + // fd table) rather than a generic message. + if (errno != 0) { + raise_zephyr_error(-errno); + } mp_raise_RuntimeError(MP_ERROR_TEXT("Out of sockets")); } return sock; @@ -255,6 +268,13 @@ int socketpool_socket_accept(socketpool_socket_obj_t *self, mp_obj_t *peer_out, accepted->pool = self->pool; accepted->connected = true; accepted->type = self->type; + accepted->family = self->family; + accepted->ipproto = self->ipproto; + // Inherit the listener's timeout, as the other ports do. A freshly + // allocated socket object reads as timeout 0 (non-blocking), which made + // the first ssl recv on an accepted connection raise EAGAIN before the + // TLS handshake had a chance to complete. + accepted->timeout_ms = self->timeout_ms; } if (peer_out) { @@ -277,6 +297,14 @@ socketpool_socket_obj_t *common_hal_socketpool_socket_accept(socketpool_socket_o sock->pool = self->pool; sock->connected = true; sock->type = self->type; + sock->family = self->family; + sock->ipproto = self->ipproto; + // Inherit the listener's timeout, as the other ports do. A freshly + // allocated object reads as timeout 0 (non-blocking), and ssl's + // recv_into relies on the plain socket's recv blocking for it: the + // first read on an accepted TLS connection raised EAGAIN before the + // handshake could complete. + sock->timeout_ms = self->timeout_ms; return sock; } else { diff --git a/shared-module/ssl/SSLSocket.c b/shared-module/ssl/SSLSocket.c index a9969505509..a184b3f2910 100644 --- a/shared-module/ssl/SSLSocket.c +++ b/shared-module/ssl/SSLSocket.c @@ -280,7 +280,16 @@ ssl_sslsocket_obj_t *common_hal_ssl_sslcontext_wrap_socket(ssl_sslcontext_obj_t goto cleanup; } - if (self->crt_bundle_attach != NULL) { + if (server_side && !(self->cacert_buf && self->cacert_bytes)) { + // On a server the CA store is about authenticating *clients*. A + // context that only had load_cert_chain() called on it -- the normal + // HTTPS-server setup -- must not demand a client certificate, which is + // what the default root bundle turned into: every handshake failed + // with MBEDTLS_ERR_SSL_NO_CLIENT_CERTIFICATE. Match CPython, where a + // server-side context defaults to CERT_NONE; a program that loads its + // own CA with load_verify_locations() still gets client verification. + mbedtls_ssl_conf_authmode(&o->conf, MBEDTLS_SSL_VERIFY_NONE); + } else if (self->crt_bundle_attach != NULL) { mbedtls_ssl_conf_authmode(&o->conf, MBEDTLS_SSL_VERIFY_REQUIRED); self->crt_bundle_attach(&o->conf); } else if (self->cacert_buf && self->cacert_bytes) {