Skip to content

Commit ef510d2

Browse files
committed
STM32H5 port refresh - SSH server fixes
1 parent 924e935 commit ef510d2

3 files changed

Lines changed: 118 additions & 7 deletions

File tree

src/port/stm32h563/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ SRCS += $(ROOT)/src/port/wolfssl_io.c
9090
# HTTPS web server - uses existing wolfIP httpd
9191
ifeq ($(ENABLE_HTTPS),1)
9292
CFLAGS += -DENABLE_HTTPS
93+
CFLAGS += -DWOLFIP_ENABLE_HTTP
9394
SRCS += $(ROOT)/src/http/httpd.c
9495
endif
9596

src/port/stm32h563/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,40 @@ Goodbye!
641641
Connection to 192.168.0.197 closed.
642642
```
643643

644+
### SSH Connection Hangs After Authentication Failure
645+
646+
**Symptom:** After one or more failed SSH login attempts the next connection hangs
647+
`ssh admin@<device-ip>` blocks and "SSH: Client connected" never appears in
648+
the UART log.
649+
650+
**Root cause:** wolfIP's listen socket can be corrupted between connections.
651+
When a new client SYN arrives while the state machine is busy in
652+
`SSH_STATE_KEY_EXCHANGE` (e.g. because a concurrent connection is being torn
653+
down), wolfIP places the listen socket in `TCP_SYN_RCVD`. If
654+
`wolfIP_sock_accept()` is not called within wolfIP's internal RTO window
655+
(≈200 ms), wolfIP re-sends the SYN-ACK directly from the listen socket. When
656+
the client's ACK arrives the listen socket transitions from `TCP_SYN_RCVD`
657+
`TCP_ESTABLISHED`, which is a state `wolfIP_sock_accept()` does not accept, so
658+
it returns `-1` on every call. A secondary failure path exists when the RTO
659+
fires `TCP_CTRL_RTO_MAXRTX` (6) times: wolfIP then destroys the listen socket
660+
entirely.
661+
662+
**Fix (already applied):** `ssh_server.c` detects `wolfIP_sock_accept()` returning
663+
`-1` and automatically calls `ssh_reinit_listen()` to close and recreate the
664+
listen socket on port 22. In addition, `wolfSSH_shutdown()` is now skipped for
665+
connections that never reached the authenticated (`CONNECTED`) state, which
666+
reduces the CLOSING-state latency and narrows the timing window.
667+
668+
**If the issue re-occurs** (e.g. after a very rapid series of failed attempts),
669+
UART will now show:
670+
671+
```
672+
SSH: Listen socket error, reinitializing
673+
SSH: Listen socket recovered
674+
```
675+
676+
and the next connection attempt will succeed.
677+
644678
### Generating Custom SSH Host Key
645679

646680
The included test host key is for development only. Generate your own:

src/port/stm32h563/ssh_server.c

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,14 @@ static struct {
5959
int rx_len;
6060
uint32_t start_tick;
6161
int channel_open;
62+
uint16_t port; /* saved for listen socket recovery */
63+
uint8_t session_established; /* 1 if SSH channel opened (CONNECTED reached) */
6264
} server;
6365

6466
/* External functions from wolfssh_io.c */
6567
extern void wolfSSH_CTX_SetIO_wolfIP(WOLFSSH_CTX *ctx);
6668
extern int wolfSSH_SetIO_wolfIP(WOLFSSH *ssh, struct wolfIP *stack, int fd);
69+
extern void wolfSSH_CleanupIO_wolfIP(WOLFSSH *ssh);
6770

6871
#ifdef DEBUG_WOLFSSH
6972
/* wolfSSH logging callback */
@@ -185,9 +188,9 @@ static int handle_command(const char *cmd, char *response, int max_len)
185188
const char *bye = "\r\nGoodbye!\r\n";
186189
len = strlen(bye);
187190
if (len < max_len) {
188-
memcpy(response, bye, len);
191+
memcpy(response, bye, len + 1); /* include null terminator */
189192
}
190-
return -1; /* Signal to close connection */
193+
return -len; /* Negative = close, magnitude = byte count to send */
191194
}
192195
else if (cmd[0] != '\0' && cmd[0] != '\r' && cmd[0] != '\n') {
193196
const char *unknown = "\r\nUnknown command. Type 'help' for available commands.\r\n\r\n";
@@ -215,6 +218,7 @@ int ssh_server_init(struct wolfIP *stack, uint16_t port, ssh_debug_cb debug)
215218
server.listen_fd = -1;
216219
server.client_fd = -1;
217220
server.state = SSH_STATE_LISTENING;
221+
server.port = port;
218222

219223
debug_print("SSH: Initializing wolfSSH\n");
220224

@@ -295,6 +299,56 @@ int ssh_server_init(struct wolfIP *stack, uint16_t port, ssh_debug_cb debug)
295299
return 0;
296300
}
297301

302+
/* Re-open the listen socket when wolfIP has corrupted it (e.g. listen socket
303+
* got stuck in TCP_ESTABLISHED after the RTO fired and the client ACK arrived
304+
* before wolfIP_sock_accept() was called). */
305+
static int ssh_reinit_listen(void)
306+
{
307+
struct wolfIP_sockaddr_in addr;
308+
int ret;
309+
310+
debug_print("SSH: Reinitializing listen socket\n");
311+
312+
/* Close the broken listen socket (ignore errors) */
313+
if (server.listen_fd >= 0) {
314+
wolfIP_sock_close(server.stack, server.listen_fd);
315+
server.listen_fd = -1;
316+
}
317+
318+
/* Create a new listen socket */
319+
server.listen_fd = wolfIP_sock_socket(server.stack,
320+
AF_INET, IPSTACK_SOCK_STREAM, 0);
321+
if (server.listen_fd < 0) {
322+
debug_print("SSH: reinit socket() failed\n");
323+
return -1;
324+
}
325+
326+
memset(&addr, 0, sizeof(addr));
327+
addr.sin_family = AF_INET;
328+
addr.sin_port = ee16(server.port);
329+
addr.sin_addr.s_addr = 0;
330+
331+
ret = wolfIP_sock_bind(server.stack, server.listen_fd,
332+
(struct wolfIP_sockaddr *)&addr, sizeof(addr));
333+
if (ret < 0) {
334+
debug_print("SSH: reinit bind() failed\n");
335+
wolfIP_sock_close(server.stack, server.listen_fd);
336+
server.listen_fd = -1;
337+
return -1;
338+
}
339+
340+
ret = wolfIP_sock_listen(server.stack, server.listen_fd, 1);
341+
if (ret < 0) {
342+
debug_print("SSH: reinit listen() failed\n");
343+
wolfIP_sock_close(server.stack, server.listen_fd);
344+
server.listen_fd = -1;
345+
return -1;
346+
}
347+
348+
debug_print("SSH: Listen socket recovered\n");
349+
return 0;
350+
}
351+
298352
int ssh_server_poll(void)
299353
{
300354
int ret;
@@ -308,8 +362,16 @@ int ssh_server_poll(void)
308362
(struct wolfIP_sockaddr *)&client_addr, &addr_len);
309363
if (ret >= 0) {
310364
server.client_fd = ret;
365+
server.session_established = 0;
311366
debug_print("SSH: Client connected\n");
312367
server.state = SSH_STATE_ACCEPTING;
368+
} else if (ret == -1) {
369+
/* Listen socket in unexpected state (e.g. stuck in
370+
* TCP_ESTABLISHED after wolfIP's RTO mechanism fired and the
371+
* client ACK arrived before wolfIP_sock_accept() was called,
372+
* or destroyed after TCP_CTRL_RTO_MAXRTX retries).
373+
* Reinitialize to recover. */
374+
ssh_reinit_listen();
313375
}
314376
break;
315377

@@ -339,6 +401,7 @@ int ssh_server_poll(void)
339401
ret = wolfSSH_accept(server.ssh);
340402
if (ret == WS_SUCCESS) {
341403
debug_print("SSH: Handshake complete\n");
404+
server.session_established = 1;
342405
server.state = SSH_STATE_CONNECTED;
343406
server.rx_len = 0;
344407

@@ -350,7 +413,10 @@ int ssh_server_poll(void)
350413
wolfSSH_stream_send(server.ssh, (byte *)welcome, strlen(welcome));
351414
} else {
352415
int err = wolfSSH_get_error(server.ssh);
353-
if (err != WS_WANT_READ && err != WS_WANT_WRITE) {
416+
/* Check both ret and err: wolfSSH may return WANT_READ/WRITE
417+
* either as the return value or stored via wolfSSH_get_error() */
418+
if (err != WS_WANT_READ && err != WS_WANT_WRITE &&
419+
ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
354420
debug_print("SSH: Handshake failed\n");
355421
server.state = SSH_STATE_CLOSING;
356422
}
@@ -384,8 +450,8 @@ int ssh_server_poll(void)
384450
response, sizeof(response));
385451

386452
if (resp_len < 0) {
387-
/* Exit requested */
388-
wolfSSH_stream_send(server.ssh, (byte *)response, strlen(response));
453+
/* Exit requested: magnitude encodes byte count */
454+
wolfSSH_stream_send(server.ssh, (byte *)response, (word32)(-resp_len));
389455
server.state = SSH_STATE_CLOSING;
390456
break;
391457
}
@@ -404,7 +470,8 @@ int ssh_server_poll(void)
404470
}
405471
} else if (ret < 0) {
406472
int err = wolfSSH_get_error(server.ssh);
407-
if (err != WS_WANT_READ && err != WS_WANT_WRITE) {
473+
if (err != WS_WANT_READ && err != WS_WANT_WRITE &&
474+
ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
408475
debug_print("SSH: Connection closed\n");
409476
server.state = SSH_STATE_CLOSING;
410477
}
@@ -413,10 +480,19 @@ int ssh_server_poll(void)
413480

414481
case SSH_STATE_CLOSING:
415482
if (server.ssh) {
416-
wolfSSH_shutdown(server.ssh);
483+
/* Only send SSH_MSG_DISCONNECT if the session was fully
484+
* established; calling wolfSSH_shutdown() on a half-open
485+
* session (e.g. auth failure) can trigger unexpected I/O
486+
* that delays the return to LISTENING and widens the window
487+
* in which a new SYN can corrupt the listen socket state. */
488+
if (server.session_established) {
489+
wolfSSH_shutdown(server.ssh);
490+
}
491+
wolfSSH_CleanupIO_wolfIP(server.ssh);
417492
wolfSSH_free(server.ssh);
418493
server.ssh = NULL;
419494
}
495+
server.session_established = 0;
420496
if (server.client_fd >= 0) {
421497
wolfIP_sock_close(server.stack, server.client_fd);
422498
server.client_fd = -1;

0 commit comments

Comments
 (0)