Skip to content

Commit e2fee8f

Browse files
committed
Restore IP_SOCK_getsockopt emNET error lookup
The merge of the old TranslateReturnCode into wolfSSL_LastError dropped the IP_SOCK_getsockopt(SO_ERROR) lookup that emNET integrations rely on to retrieve the canonical IP_ERR_* for a failed recv/send, leaving a broken branch that returned the raw return value and mishandled the POSIX-facade convention. Restore the historic pattern (fixing the optlen pointer-vs-int typo along the way) so both native and POSIX-facade emNET stacks produce correct SOCKET_EWOULDBLOCK == IP_ERR_WOULD_BLOCK mappings, and extend the CI shim with an emNET-faithful IP_SOCK_getsockopt (SO_ERROR-then-errno fallback, since Linux does not stash EAGAIN in SO_ERROR) plus a second test binary linked without -Wl,--wrap=recv,--wrap=send to exercise the POSIX-facade path the original shim-wrapped binary bypassed.
1 parent 568cb5e commit e2fee8f

8 files changed

Lines changed: 199 additions & 93 deletions

File tree

.github/workflows/emnet-nonblock.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,16 @@ jobs:
4444
--enable-static --disable-shared \
4545
--enable-tls13 --disable-oldtls \
4646
--enable-ecc --disable-examples \
47+
--disable-benchmark --disable-crypttests \
4748
CFLAGS="-DWOLFSSL_EMNET -I$(pwd)/tests/emnet"
4849
49-
- name: Build wolfSSL
50-
run: make -j$(nproc)
50+
# The WOLFSSL_EMNET branch in src/wolfio.c calls IP_SOCK_getsockopt
51+
# which only resolves against the test shim. Build only the static
52+
# library; examples/benchmark/crypttests are disabled above, but
53+
# explicitly target the .a to avoid pulling in any residual link
54+
# targets that would fail with an undefined reference.
55+
- name: Build wolfSSL static library
56+
run: make -j$(nproc) src/libwolfssl.la
5157

5258
- name: Build emNET non-blocking test
5359
run: make -C tests/emnet

src/wolfio.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,24 @@ static WC_INLINE int wolfSSL_LastError(int err, SOCKET_T sd)
153153
#elif defined(WOLFSSL_LINUXKM)
154154
return -err; /* Return provided error value with corrected sign. */
155155
#elif defined(WOLFSSL_EMNET)
156-
/* emNET BSD sockets return the IP_ERR_* value (negative) directly
157-
* from send/recv on failure; no translation needed. */
156+
/* Any negative recv/send return is a SOCKET_ERROR sentinel under
157+
* emNET; the canonical IP_ERR_* lives in the socket SO_ERROR.
158+
* Retrieving it via IP_SOCK_getsockopt works across both emNET
159+
* integrator conventions (native: recv returns IP_ERR_* directly;
160+
* POSIX facade: recv returns -1 with errno set). If the lookup
161+
* itself fails, fall back to IP_ERR_FAULT rather than returning
162+
* the raw -1 sentinel — the latter matches no SOCKET_E* constant
163+
* and would regress into WOLFSSL_CBIO_ERR_GENERAL. */
164+
if (err < 0) {
165+
int sock_err = err;
166+
if (IP_SOCK_getsockopt(sd, SOL_SOCKET, SO_ERROR, &sock_err,
167+
(int)sizeof(sock_err)) == 0) {
168+
err = sock_err;
169+
}
170+
else if (err == -1) {
171+
err = IP_ERR_FAULT;
172+
}
173+
}
158174
return err;
159175
#elif defined(FUSION_RTOS)
160176
#include <fclerrno.h>

tests/emnet/IP/IP.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ extern "C" {
3232
#define IP_ERR_PIPE (-13)
3333
#define IP_ERR_FAULT (-25)
3434

35-
/* BSD-style socket option retrieval. Signature matches the SEGGER API:
36-
* length is passed by pointer of type int*, unlike POSIX socklen_t*. */
37-
int IP_SOCK_getsockopt(int sd, int level, int optname,
38-
void *optval, int *optlen);
35+
/* BSD-style socket option retrieval. Signature matches the SEGGER
36+
* emNET API (UM07001, IP_Socket.h): the length is passed by value as
37+
* a plain int, not a POSIX socklen_t* / int*. */
38+
int IP_SOCK_getsockopt(int hSock, int Level, int Name,
39+
void *pVal, int ValLen);
3940

4041
#ifdef __cplusplus
4142
}

tests/emnet/Makefile

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@
99
# -I$$(pwd)/tests/emnet"
1010
# make
1111
# make -C tests/emnet run
12+
#
13+
# Two binaries are produced, exercising the two emNET integrator
14+
# conventions observed in the wild:
15+
# emnet_nonblock_test - native: recv/send return IP_ERR_*
16+
# directly (linker --wrap + shim).
17+
# emnet_nonblock_test_posix - POSIX facade: recv/send return -1 and
18+
# set errno (no --wrap, plain glibc).
19+
# `make run` runs both and fails if either fails.
1220

1321
CURDIR := $(shell pwd)
1422
WOLFSSL_ROOT := $(abspath $(CURDIR)/../..)
@@ -19,14 +27,15 @@ CFLAGS_TEST := -Wall -Wextra -O2 -g \
1927
-DWOLFSSL_EMNET \
2028
-I$(CURDIR) \
2129
-I$(WOLFSSL_ROOT)
22-
LDFLAGS_TEST := -Wl,--wrap=recv,--wrap=send
30+
LDFLAGS_NATIVE := -Wl,--wrap=recv,--wrap=send
2331
LIBS_TEST := $(WOLFSSL_LIB) -lpthread -lm
2432

25-
TEST_BIN := emnet_nonblock_test
33+
TEST_BIN := emnet_nonblock_test
34+
TEST_BIN_POSIX := emnet_nonblock_test_posix
2635

2736
.PHONY: all run clean check-lib
2837

29-
all: $(TEST_BIN)
38+
all: $(TEST_BIN) $(TEST_BIN_POSIX)
3039

3140
check-lib:
3241
@if [ ! -f "$(WOLFSSL_LIB)" ]; then \
@@ -35,18 +44,44 @@ check-lib:
3544
exit 1; \
3645
fi
3746

38-
$(TEST_BIN): emnet_nonblock_test.o emnet_shim.o check-lib
39-
$(CC) $(LDFLAGS_TEST) -o $@ emnet_nonblock_test.o emnet_shim.o $(LIBS_TEST)
47+
# Native variant: --wrap=recv,--wrap=send via emnet_shim_wrap.o so the
48+
# raw recv/send return values match the native emNET ABI (IP_ERR_*
49+
# directly). Still links the common shim for IP_SOCK_getsockopt.
50+
# check-lib is an order-only dependency (after |) so its phony status
51+
# does not force the binary to relink on every `make` invocation.
52+
$(TEST_BIN): emnet_nonblock_test.o emnet_shim.o emnet_shim_wrap.o | check-lib
53+
$(CC) $(LDFLAGS_NATIVE) -o $@ emnet_nonblock_test.o emnet_shim.o \
54+
emnet_shim_wrap.o $(LIBS_TEST)
55+
56+
# POSIX-facade variant: no --wrap, no __wrap_* wrappers. recv/send
57+
# fall through to glibc's POSIX implementation (-1 + errno on
58+
# would-block); wolfSSL_LastError then retrieves the canonical
59+
# IP_ERR_* via IP_SOCK_getsockopt in the common shim.
60+
$(TEST_BIN_POSIX): emnet_nonblock_test_posix.o emnet_shim.o | check-lib
61+
$(CC) -o $@ emnet_nonblock_test_posix.o emnet_shim.o $(LIBS_TEST)
4062

4163
emnet_nonblock_test.o: emnet_nonblock_test.c
4264
$(CC) $(CFLAGS_TEST) -c $< -o $@
4365

66+
# Same source, compiled again so the object files are distinct and
67+
# parallel builds don't race on a single .o.
68+
emnet_nonblock_test_posix.o: emnet_nonblock_test.c
69+
$(CC) $(CFLAGS_TEST) -c $< -o $@
70+
4471
emnet_shim.o: emnet_shim.c IP/IP.h
4572
$(CC) $(CFLAGS_TEST) -c $< -o $@
4673

47-
# Run from the repo root so the relative cert paths resolve.
48-
run: $(TEST_BIN)
49-
cd $(WOLFSSL_ROOT) && ./tests/emnet/$(TEST_BIN)
74+
emnet_shim_wrap.o: emnet_shim_wrap.c IP/IP.h
75+
$(CC) $(CFLAGS_TEST) -c $< -o $@
76+
77+
# Run from the repo root so the relative cert paths resolve. Run both
78+
# binaries; any failure propagates via `set -e`.
79+
run: $(TEST_BIN) $(TEST_BIN_POSIX)
80+
cd $(WOLFSSL_ROOT) && set -e && \
81+
echo "--- native IP_ERR_* convention ---" && \
82+
./tests/emnet/$(TEST_BIN) && \
83+
echo "--- POSIX -1 + errno convention ---" && \
84+
./tests/emnet/$(TEST_BIN_POSIX)
5085

5186
clean:
52-
rm -f *.o $(TEST_BIN)
87+
rm -f *.o $(TEST_BIN) $(TEST_BIN_POSIX)

tests/emnet/emnet_nonblock_test.c

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,13 @@ static void set_nonblock(int fd)
6161
* success (ready or timeout, caller retries), -1 on hard failure. */
6262
static int wait_io(struct side *s, short events, int iter)
6363
{
64-
struct pollfd pfd = { .fd = s->fd, .events = events };
65-
int r = poll(&pfd, 1, POLL_MS);
64+
struct pollfd pfd;
65+
int r;
66+
67+
pfd.fd = s->fd;
68+
pfd.events = events;
69+
pfd.revents = 0;
70+
r = poll(&pfd, 1, POLL_MS);
6671
if (r >= 0)
6772
return 0;
6873
if (errno == EINTR)
@@ -76,15 +81,17 @@ static void *run_side(void *arg)
7681
{
7782
struct side *s = (struct side *)arg;
7883
int iter;
84+
int ret;
85+
int err;
7986

8087
for (iter = 0; iter < MAX_ITERS; iter++) {
81-
int ret = s->fn(s->ssl);
88+
ret = s->fn(s->ssl);
8289
if (ret == WOLFSSL_SUCCESS) {
8390
s->completed = 1;
8491
return NULL;
8592
}
8693

87-
int err = wolfSSL_get_error(s->ssl, ret);
94+
err = wolfSSL_get_error(s->ssl, ret);
8895
s->last_err = err;
8996

9097
if (err == WOLFSSL_ERROR_WANT_READ) {
@@ -125,6 +132,16 @@ static void *run_side(void *arg)
125132
int main(void)
126133
{
127134
int sv[2];
135+
WOLFSSL_CTX *sctx;
136+
WOLFSSL_CTX *cctx;
137+
WOLFSSL *server_ssl;
138+
WOLFSSL *client_ssl;
139+
struct side server;
140+
struct side client;
141+
pthread_t st, ct;
142+
int prc;
143+
int rc;
144+
128145
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
129146
perror("socketpair");
130147
return 2;
@@ -134,8 +151,8 @@ int main(void)
134151

135152
wolfSSL_Init();
136153

137-
WOLFSSL_CTX *sctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
138-
WOLFSSL_CTX *cctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
154+
sctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
155+
cctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
139156
if (!sctx || !cctx) {
140157
fprintf(stderr, "wolfSSL_CTX_new failed\n");
141158
return 2;
@@ -157,8 +174,8 @@ int main(void)
157174
return 2;
158175
}
159176

160-
WOLFSSL *server_ssl = wolfSSL_new(sctx);
161-
WOLFSSL *client_ssl = wolfSSL_new(cctx);
177+
server_ssl = wolfSSL_new(sctx);
178+
client_ssl = wolfSSL_new(cctx);
162179
if (!server_ssl || !client_ssl) {
163180
fprintf(stderr, "wolfSSL_new failed\n");
164181
return 2;
@@ -167,13 +184,17 @@ int main(void)
167184
wolfSSL_set_fd(server_ssl, sv[0]);
168185
wolfSSL_set_fd(client_ssl, sv[1]);
169186

170-
struct side server = { .fd = sv[0], .name = "server",
171-
.ssl = server_ssl, .fn = wolfSSL_accept };
172-
struct side client = { .fd = sv[1], .name = "client",
173-
.ssl = client_ssl, .fn = wolfSSL_connect };
187+
memset(&server, 0, sizeof(server));
188+
memset(&client, 0, sizeof(client));
189+
server.fd = sv[0];
190+
server.name = "server";
191+
server.ssl = server_ssl;
192+
server.fn = wolfSSL_accept;
193+
client.fd = sv[1];
194+
client.name = "client";
195+
client.ssl = client_ssl;
196+
client.fn = wolfSSL_connect;
174197

175-
pthread_t st, ct;
176-
int prc;
177198
prc = pthread_create(&st, NULL, run_side, &server);
178199
if (prc != 0) {
179200
fprintf(stderr, "FAIL: pthread_create(server): %s\n", strerror(prc));
@@ -196,7 +217,7 @@ int main(void)
196217
return 2;
197218
}
198219

199-
int rc = 0;
220+
rc = 0;
200221
if (server.failed || client.failed) {
201222
rc = 1;
202223
} else if (!server.completed || !client.completed) {

tests/emnet/emnet_shim.c

Lines changed: 47 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
/* emnet_shim.c -- POSIX-backed shim for the emNET (embOS/IP) socket ABI
22
* used by wolfSSL when WOLFSSL_EMNET is defined.
33
*
4-
* The goal is to reproduce the error-reporting contract of emNET on top
5-
* of stock Linux BSD sockets: when the underlying socket signals
6-
* would-block, connection reset, etc., the shim surfaces the
7-
* corresponding IP_ERR_* negative constant (emNET convention) instead
8-
* of -1/errno (POSIX convention). This is exactly what wolfSSL's
9-
* WOLFSSL_EMNET branch in wolfio.h/wolfio.c was written to consume, so
10-
* CI can drive the non-blocking handshake paths without the real
11-
* SEGGER stack.
4+
* Provides the canonical error lookup path wolfSSL's wolfSSL_LastError
5+
* relies on: IP_SOCK_getsockopt(SO_ERROR) returns the pending IP_ERR_*
6+
* for a socket, as required by UM07001's emNET API contract. On top of
7+
* Linux BSD sockets this is emulated by consulting POSIX SO_ERROR plus
8+
* the thread-local errno (because Linux does not store transient
9+
* would-block conditions in SO_ERROR).
1210
*
13-
* Linker wrapping:
14-
* -Wl,--wrap=recv,--wrap=send
15-
* hooks wolfSSL's RECV_FUNCTION/SEND_FUNCTION (which are the
16-
* unqualified POSIX send/recv on the WOLFSSL_EMNET build) without
17-
* patching any wolfSSL source.
11+
* The paired native-convention wrapper (emnet_shim_wrap.c) hooks
12+
* recv/send via -Wl,--wrap to also make the raw return value match
13+
* the native emNET ABI; that file is only linked into the native test
14+
* binary, whereas this translation unit is shared by both test
15+
* variants.
1816
*/
1917

2018
#include "IP/IP.h"
@@ -25,12 +23,9 @@
2523
#include <stddef.h>
2624
#include <string.h>
2725

28-
/* Forward declarations for the linker's --wrap mechanism. */
29-
ssize_t __real_recv(int sd, void *buf, size_t len, int flags);
30-
ssize_t __real_send(int sd, const void *buf, size_t len, int flags);
31-
32-
/* Translate a POSIX errno value into the emNET IP_ERR_* space. */
33-
static int emnet_errno_to_ip_err(int err)
26+
/* Translate a POSIX errno value into the emNET IP_ERR_* space. Shared
27+
* with the --wrap wrappers in emnet_shim_wrap.c, so not static. */
28+
int emnet_errno_to_ip_err(int err)
3429
{
3530
/* Linux, where this shim runs in CI, defines EWOULDBLOCK == EAGAIN,
3631
* so EAGAIN covers both. */
@@ -50,54 +45,45 @@ static int emnet_errno_to_ip_err(int err)
5045
}
5146
}
5247

53-
/* recv wrapper: preserve success/close semantics; on error return the
54-
* emNET-style negative error code in place of -1/errno. wolfSSL's
55-
* TranslateIoReturnCode uses err < 0 to branch into error handling and
56-
* then compares against SOCKET_EWOULDBLOCK == IP_ERR_WOULD_BLOCK. */
57-
ssize_t __wrap_recv(int sd, void *buf, size_t len, int flags)
48+
/* IP_SOCK_getsockopt: emulates the emNET ABI on top of POSIX. This is
49+
* the canonical error source for WOLFSSL_EMNET code paths — wolfSSL
50+
* calls it after a negative recv/send return to retrieve the real
51+
* IP_ERR_* value.
52+
*
53+
* For SO_ERROR we deliberately diverge from a pure POSIX pass-through:
54+
* Linux stores sticky socket errors (ECONNRESET etc.) in SO_ERROR but
55+
* does NOT store transient would-block conditions (EAGAIN/EWOULDBLOCK)
56+
* there — those live only in thread-local errno after the failing
57+
* syscall. Real emNET's SO_ERROR does carry would-block. To reproduce
58+
* that contract here, read POSIX SO_ERROR first, fall back to errno
59+
* when SO_ERROR is empty, then translate into the IP_ERR_* space. */
60+
int IP_SOCK_getsockopt(int hSock, int Level, int Name,
61+
void *pVal, int ValLen)
5862
{
59-
ssize_t ret = __real_recv(sd, buf, len, flags);
60-
if (ret < 0) {
61-
return (ssize_t)emnet_errno_to_ip_err(errno);
63+
if (pVal == NULL || ValLen <= 0) {
64+
errno = EINVAL;
65+
return -1;
6266
}
63-
return ret;
64-
}
6567

66-
ssize_t __wrap_send(int sd, const void *buf, size_t len, int flags)
67-
{
68-
ssize_t ret = __real_send(sd, buf, len, flags);
69-
if (ret < 0) {
70-
return (ssize_t)emnet_errno_to_ip_err(errno);
71-
}
72-
return ret;
73-
}
68+
if (Level == SOL_SOCKET && Name == SO_ERROR
69+
&& ValLen >= (int)sizeof(int)) {
70+
int saved_errno = errno;
71+
int so_err = 0;
72+
socklen_t posix_len = (socklen_t)sizeof(so_err);
73+
int ip_err;
7474

75-
/* IP_SOCK_getsockopt: kept to satisfy the emNET ABI surface expected
76-
* by WOLFSSL_EMNET-linked code. Delegates to POSIX getsockopt and, for
77-
* SO_ERROR, maps the returned POSIX errno value into emNET's IP_ERR_*
78-
* space so callers see emNET-style error reporting. */
79-
int IP_SOCK_getsockopt(int sd, int level, int optname,
80-
void *optval, int *optlen)
81-
{
82-
socklen_t posix_len;
83-
int rc;
75+
(void)getsockopt(hSock, SOL_SOCKET, SO_ERROR, &so_err, &posix_len);
76+
if (so_err == 0)
77+
so_err = saved_errno;
8478

85-
if (optlen == NULL) {
86-
errno = EINVAL;
87-
return -1;
79+
ip_err = emnet_errno_to_ip_err(so_err);
80+
memcpy(pVal, &ip_err, sizeof(ip_err));
81+
return 0;
8882
}
89-
posix_len = (socklen_t)*optlen;
90-
rc = getsockopt(sd, level, optname, optval, &posix_len);
91-
*optlen = (int)posix_len;
9283

93-
if (rc == 0 && level == SOL_SOCKET && optname == SO_ERROR
94-
&& optval != NULL && posix_len >= (socklen_t)sizeof(int)) {
95-
int so_err;
96-
memcpy(&so_err, optval, sizeof(so_err));
97-
if (so_err != 0) {
98-
so_err = emnet_errno_to_ip_err(so_err);
99-
memcpy(optval, &so_err, sizeof(so_err));
100-
}
84+
/* Pass-through for other options. */
85+
{
86+
socklen_t posix_len = (socklen_t)ValLen;
87+
return getsockopt(hSock, Level, Name, pVal, &posix_len);
10188
}
102-
return rc;
10389
}

0 commit comments

Comments
 (0)