Skip to content

Commit 0abb7ee

Browse files
committed
Fix emNET support and add tests
The emNET `wolfSSL_LastError` branches were incorrect. The second one was never hit and would never compile. The first one inverts error codes that should not be inverted. This fixes that code and adds a test with a shim layer to test emNET calls without using emNET.
1 parent b573823 commit 0abb7ee

7 files changed

Lines changed: 460 additions & 6 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: emNET non-blocking handshake test
2+
3+
# START OF COMMON SECTION
4+
on:
5+
push:
6+
branches: [ 'master', 'main', 'release/**' ]
7+
pull_request:
8+
branches: [ '*' ]
9+
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
# END OF COMMON SECTION
14+
15+
# Build wolfSSL with -DWOLFSSL_EMNET using the clean-room shim in
16+
# tests/emnet/ (IP/IP.h + emnet_shim.c), link a non-blocking TLS 1.3
17+
# handshake test with -Wl,--wrap=recv,--wrap=send, and run it.
18+
#
19+
# On current master the test is expected to FAIL because of an
20+
# unreachable `#elif defined(WOLFSSL_EMNET)` branch in
21+
# src/wolfio.c:wolfSSL_LastError() — shadowed by the combined
22+
# WOLFSSL_LINUXKM||WOLFSSL_EMNET branch that returns -err. The
23+
# would-block path therefore reports WOLFSSL_CBIO_ERR_GENERAL instead
24+
# of WOLFSSL_CBIO_ERR_WANT_READ/WANT_WRITE. The follow-up fix to
25+
# wolfio.c will flip this workflow green.
26+
27+
jobs:
28+
emnet_nonblock:
29+
name: wolfSSL emNET non-blocking handshake
30+
if: github.repository_owner == 'wolfssl'
31+
runs-on: ubuntu-24.04
32+
timeout-minutes: 20
33+
steps:
34+
- name: Checkout wolfSSL
35+
uses: actions/checkout@v4
36+
37+
- name: Install build deps
38+
uses: ./.github/actions/install-apt-deps
39+
with:
40+
packages: autoconf automake libtool build-essential
41+
42+
- name: Bootstrap
43+
run: ./autogen.sh
44+
45+
- name: Configure wolfSSL (WOLFSSL_EMNET + emNET shim headers)
46+
run: |
47+
./configure \
48+
--enable-static --disable-shared \
49+
--enable-tls13 --disable-oldtls \
50+
--enable-ecc --disable-examples \
51+
CFLAGS="-DWOLFSSL_EMNET -I$(pwd)/tests/emnet"
52+
53+
- name: Build wolfSSL
54+
run: make -j$(nproc)
55+
56+
- name: Build emNET non-blocking test
57+
run: make -C tests/emnet
58+
59+
- name: Run emNET non-blocking test
60+
run: make -C tests/emnet run

src/wolfio.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,12 @@ static WC_INLINE int wolfSSL_LastError(int err, SOCKET_T sd)
150150
return WSAGetLastError();
151151
#elif defined(EBSNET)
152152
return xn_getlasterror();
153-
#elif defined(WOLFSSL_LINUXKM) || defined(WOLFSSL_EMNET)
153+
#elif defined(WOLFSSL_LINUXKM)
154154
return -err; /* Return provided error value with corrected sign. */
155+
#elif defined(WOLFSSL_EMNET)
156+
/* emNET BSD sockets return the IP_ERR_* value (negative) directly
157+
* from send/recv on failure; no translation needed. */
158+
return err;
155159
#elif defined(FUSION_RTOS)
156160
#include <fclerrno.h>
157161
return FCL_GET_ERRNO;
@@ -169,10 +173,6 @@ static WC_INLINE int wolfSSL_LastError(int err, SOCKET_T sd)
169173
}
170174
return err;
171175
}
172-
#elif defined(WOLFSSL_EMNET)
173-
/* Get the real socket error */
174-
IP_SOCK_getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, (int)sizeof(old));
175-
return err;
176176
#else
177177
return errno;
178178
#endif

tests/emnet/IP/IP.h

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* IP.h -- clean-room shim for the subset of SEGGER emNET (embOS/IP) API
2+
* that wolfSSL's WOLFSSL_EMNET port compiles against. Written from the
3+
* public API surface documented in SEGGER UM07001; contains no SEGGER
4+
* source.
5+
*
6+
* Scope: enough to build wolfSSL with -DWOLFSSL_EMNET on a POSIX host
7+
* for CI test purposes. Only error constants and IP_SOCK_getsockopt are
8+
* provided here; the runtime behaviour of send/recv under emNET is
9+
* emulated by emnet_shim.c via linker --wrap.
10+
*/
11+
12+
#ifndef WOLFSSL_EMNET_SHIM_IP_H
13+
#define WOLFSSL_EMNET_SHIM_IP_H
14+
15+
#include <sys/types.h>
16+
#include <sys/socket.h>
17+
#include <netinet/in.h>
18+
#include <arpa/inet.h>
19+
#include <unistd.h>
20+
#include <fcntl.h>
21+
#include <errno.h>
22+
23+
#ifdef __cplusplus
24+
extern "C" {
25+
#endif
26+
27+
/* emNET error codes (UM07001). Values match the public ABI. */
28+
#define IP_ERR_CONN_ABORTED (-5)
29+
#define IP_ERR_WOULD_BLOCK (-6)
30+
#define IP_ERR_CONN_REFUSED (-7)
31+
#define IP_ERR_CONN_RESET (-8)
32+
#define IP_ERR_PIPE (-13)
33+
#define IP_ERR_FAULT (-25)
34+
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);
39+
40+
#ifdef __cplusplus
41+
}
42+
#endif
43+
44+
#endif /* WOLFSSL_EMNET_SHIM_IP_H */

tests/emnet/Makefile

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Build the emNET non-blocking handshake test.
2+
# Requires wolfSSL already configured + built at the repo root with
3+
# WOLFSSL_EMNET and the emnet IP include path, e.g.:
4+
#
5+
# cd $(repo_root)
6+
# ./autogen.sh
7+
# ./configure --enable-static --disable-shared --enable-tls13 \
8+
# --disable-oldtls CFLAGS="-DWOLFSSL_EMNET \
9+
# -I$$(pwd)/tests/emnet"
10+
# make
11+
# make -C tests/emnet run
12+
13+
CURDIR := $(shell pwd)
14+
WOLFSSL_ROOT := $(abspath $(CURDIR)/../..)
15+
WOLFSSL_LIB := $(WOLFSSL_ROOT)/src/.libs/libwolfssl.a
16+
17+
CC ?= cc
18+
CFLAGS_TEST := -Wall -Wextra -O2 -g \
19+
-DWOLFSSL_EMNET \
20+
-I$(CURDIR) \
21+
-I$(WOLFSSL_ROOT)
22+
LDFLAGS_TEST := -Wl,--wrap=recv,--wrap=send
23+
LIBS_TEST := $(WOLFSSL_LIB) -lpthread -lm
24+
25+
TEST_BIN := emnet_nonblock_test
26+
27+
.PHONY: all run clean check-lib
28+
29+
all: $(TEST_BIN)
30+
31+
check-lib:
32+
@if [ ! -f "$(WOLFSSL_LIB)" ]; then \
33+
echo "error: $(WOLFSSL_LIB) not found."; \
34+
echo "Build wolfSSL first (see header of this Makefile)."; \
35+
exit 1; \
36+
fi
37+
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)
40+
41+
emnet_nonblock_test.o: emnet_nonblock_test.c
42+
$(CC) $(CFLAGS_TEST) -c $< -o $@
43+
44+
emnet_shim.o: emnet_shim.c IP/IP.h
45+
$(CC) $(CFLAGS_TEST) -c $< -o $@
46+
47+
# Run from the repo root so the relative cert paths resolve.
48+
run: $(TEST_BIN)
49+
cd $(WOLFSSL_ROOT) && ./tests/emnet/$(TEST_BIN)
50+
51+
clean:
52+
rm -f *.o $(TEST_BIN)

tests/emnet/emnet_nonblock_test.c

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/* emnet_nonblock_test.c -- non-blocking TLS 1.3 handshake over a
2+
* socketpair, with wolfSSL built for WOLFSSL_EMNET and the recv/send
3+
* error surface translated by emnet_shim.c.
4+
*
5+
* Purpose: expose the unreachable `#elif defined(WOLFSSL_EMNET)` branch
6+
* in src/wolfio.c:172 (shadowed by the WOLFSSL_LINUXKM|WOLFSSL_EMNET
7+
* branch at line 153). On current master the shadowing branch returns
8+
* `-err`, flipping IP_ERR_WOULD_BLOCK (-6) to +6, which no longer
9+
* matches SOCKET_EWOULDBLOCK, so TranslateIoReturnCode falls through to
10+
* WOLFSSL_CBIO_ERR_GENERAL and the handshake aborts the first time
11+
* the socket would block. The test exits non-zero with a diagnostic
12+
* naming the unexpected error; the follow-up fix will make it pass.
13+
*/
14+
15+
#include <errno.h>
16+
#include <fcntl.h>
17+
#include <poll.h>
18+
#include <pthread.h>
19+
#include <stdio.h>
20+
#include <stdlib.h>
21+
#include <string.h>
22+
#include <sys/socket.h>
23+
#include <sys/types.h>
24+
#include <unistd.h>
25+
26+
#include <wolfssl/options.h>
27+
#include <wolfssl/ssl.h>
28+
29+
#define MAX_ITERS 200 /* handshake loop safety cap */
30+
#define POLL_MS 500
31+
32+
#define CERT_PATH "certs/server-ecc.pem"
33+
#define KEY_PATH "certs/ecc-key.pem"
34+
#define CA_PATH "certs/ca-ecc-cert.pem"
35+
36+
struct side {
37+
int fd;
38+
const char *name;
39+
WOLFSSL *ssl;
40+
int (*fn)(WOLFSSL *);
41+
int saw_would_block;
42+
int completed;
43+
int failed;
44+
int last_err;
45+
};
46+
47+
static void set_nonblock(int fd)
48+
{
49+
int flags = fcntl(fd, F_GETFL, 0);
50+
if (flags < 0) {
51+
perror("fcntl F_GETFL");
52+
exit(2);
53+
}
54+
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
55+
perror("fcntl F_SETFL O_NONBLOCK");
56+
exit(2);
57+
}
58+
}
59+
60+
static void *run_side(void *arg)
61+
{
62+
struct side *s = (struct side *)arg;
63+
int iter;
64+
65+
for (iter = 0; iter < MAX_ITERS; iter++) {
66+
int ret = s->fn(s->ssl);
67+
if (ret == WOLFSSL_SUCCESS) {
68+
s->completed = 1;
69+
return NULL;
70+
}
71+
72+
int err = wolfSSL_get_error(s->ssl, ret);
73+
s->last_err = err;
74+
75+
if (err == WOLFSSL_ERROR_WANT_READ) {
76+
struct pollfd pfd = { .fd = s->fd, .events = POLLIN };
77+
s->saw_would_block = 1;
78+
poll(&pfd, 1, POLL_MS);
79+
continue;
80+
}
81+
if (err == WOLFSSL_ERROR_WANT_WRITE) {
82+
struct pollfd pfd = { .fd = s->fd, .events = POLLOUT };
83+
s->saw_would_block = 1;
84+
poll(&pfd, 1, POLL_MS);
85+
continue;
86+
}
87+
88+
/* Anything else on a non-blocking handshake is a failure. */
89+
char errstr[WOLFSSL_MAX_ERROR_SZ];
90+
wolfSSL_ERR_error_string_n((unsigned long)err,
91+
errstr, sizeof(errstr));
92+
fprintf(stderr,
93+
"FAIL: %s: wolfSSL_get_error=%d (%s) after iter=%d. "
94+
"Expected WANT_READ/WANT_WRITE on a non-blocking socketpair. "
95+
"This is the emNET non-blocking error-translation bug in "
96+
"src/wolfio.c (unreachable WOLFSSL_EMNET branch in "
97+
"wolfSSL_LastError).\n",
98+
s->name, err, errstr, iter);
99+
s->failed = 1;
100+
return NULL;
101+
}
102+
103+
fprintf(stderr, "FAIL: %s: handshake did not complete within %d "
104+
"iterations (last err=%d)\n",
105+
s->name, MAX_ITERS, s->last_err);
106+
s->failed = 1;
107+
return NULL;
108+
}
109+
110+
int main(void)
111+
{
112+
int sv[2];
113+
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) {
114+
perror("socketpair");
115+
return 2;
116+
}
117+
set_nonblock(sv[0]);
118+
set_nonblock(sv[1]);
119+
120+
wolfSSL_Init();
121+
122+
WOLFSSL_CTX *sctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method());
123+
WOLFSSL_CTX *cctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
124+
if (!sctx || !cctx) {
125+
fprintf(stderr, "wolfSSL_CTX_new failed\n");
126+
return 2;
127+
}
128+
129+
if (wolfSSL_CTX_use_certificate_file(sctx, CERT_PATH,
130+
WOLFSSL_FILETYPE_PEM) != WOLFSSL_SUCCESS) {
131+
fprintf(stderr, "failed to load server cert %s\n", CERT_PATH);
132+
return 2;
133+
}
134+
if (wolfSSL_CTX_use_PrivateKey_file(sctx, KEY_PATH,
135+
WOLFSSL_FILETYPE_PEM) != WOLFSSL_SUCCESS) {
136+
fprintf(stderr, "failed to load server key %s\n", KEY_PATH);
137+
return 2;
138+
}
139+
if (wolfSSL_CTX_load_verify_locations(cctx, CA_PATH, NULL)
140+
!= WOLFSSL_SUCCESS) {
141+
fprintf(stderr, "failed to load CA %s\n", CA_PATH);
142+
return 2;
143+
}
144+
145+
WOLFSSL *server_ssl = wolfSSL_new(sctx);
146+
WOLFSSL *client_ssl = wolfSSL_new(cctx);
147+
if (!server_ssl || !client_ssl) {
148+
fprintf(stderr, "wolfSSL_new failed\n");
149+
return 2;
150+
}
151+
152+
wolfSSL_set_fd(server_ssl, sv[0]);
153+
wolfSSL_set_fd(client_ssl, sv[1]);
154+
155+
struct side server = { .fd = sv[0], .name = "server",
156+
.ssl = server_ssl, .fn = wolfSSL_accept };
157+
struct side client = { .fd = sv[1], .name = "client",
158+
.ssl = client_ssl, .fn = wolfSSL_connect };
159+
160+
pthread_t st, ct;
161+
pthread_create(&st, NULL, run_side, &server);
162+
pthread_create(&ct, NULL, run_side, &client);
163+
pthread_join(st, NULL);
164+
pthread_join(ct, NULL);
165+
166+
int rc = 0;
167+
if (server.failed || client.failed) {
168+
rc = 1;
169+
} else if (!server.completed || !client.completed) {
170+
fprintf(stderr, "FAIL: handshake incomplete (server=%d client=%d)\n",
171+
server.completed, client.completed);
172+
rc = 1;
173+
} else if (!server.saw_would_block && !client.saw_would_block) {
174+
fprintf(stderr, "FAIL: handshake completed but never hit a "
175+
"non-blocking path. Test scaffolding not exercising "
176+
"the WOLFSSL_EMNET error-translation code.\n");
177+
rc = 1;
178+
} else {
179+
printf("OK: handshake completed, would-block paths exercised\n");
180+
}
181+
182+
wolfSSL_free(server_ssl);
183+
wolfSSL_free(client_ssl);
184+
wolfSSL_CTX_free(sctx);
185+
wolfSSL_CTX_free(cctx);
186+
wolfSSL_Cleanup();
187+
close(sv[0]);
188+
close(sv[1]);
189+
return rc;
190+
}

0 commit comments

Comments
 (0)