Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/codespell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ jobs:
uses: codespell-project/actions-codespell@v2
with:
skip: .git,./IDE,*.der,*.pem
ignore_words_list: inh,inout,keypair,nd,parm,rcv,ser,tha,HSI,TE,UE,Synopsys
ignore_words_list: inh,inout,keypair,nd,parm,rcv,ser,tha,HSI,TE,UE,Synopsys,synopsys
1 change: 1 addition & 0 deletions src/port/stm32h563/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ SRCS += $(ROOT)/src/port/wolfssl_io.c
# HTTPS web server - uses existing wolfIP httpd
ifeq ($(ENABLE_HTTPS),1)
CFLAGS += -DENABLE_HTTPS
CFLAGS += -DWOLFIP_ENABLE_HTTP
SRCS += $(ROOT)/src/http/httpd.c
endif

Expand Down
34 changes: 34 additions & 0 deletions src/port/stm32h563/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,40 @@ Goodbye!
Connection to 192.168.0.197 closed.
```

### SSH Connection Hangs After Authentication Failure

**Symptom:** After one or more failed SSH login attempts the next connection hangs
— `ssh admin@<device-ip>` blocks and "SSH: Client connected" never appears in
the UART log.

**Root cause:** wolfIP's listen socket can be corrupted between connections.
When a new client SYN arrives while the state machine is busy in
`SSH_STATE_KEY_EXCHANGE` (e.g. because a concurrent connection is being torn
down), wolfIP places the listen socket in `TCP_SYN_RCVD`. If
`wolfIP_sock_accept()` is not called within wolfIP's internal RTO window
(≈200 ms), wolfIP re-sends the SYN-ACK directly from the listen socket. When
the client's ACK arrives the listen socket transitions from `TCP_SYN_RCVD` →
`TCP_ESTABLISHED`, which is a state `wolfIP_sock_accept()` does not accept, so
it returns `-1` on every call. A secondary failure path exists when the RTO
fires `TCP_CTRL_RTO_MAXRTX` (6) times: wolfIP then destroys the listen socket
entirely.

**Fix (already applied):** `ssh_server.c` detects `wolfIP_sock_accept()` returning
`-1` and automatically calls `ssh_reinit_listen()` to close and recreate the
listen socket on the configured SSH port. In addition, `wolfSSH_shutdown()` is now skipped for
connections that never reached the authenticated (`CONNECTED`) state, which
reduces the CLOSING-state latency and narrows the timing window.

**If the issue re-occurs** (e.g. after a very rapid series of failed attempts),
UART will now show:

```
SSH: Reinitializing listen socket
SSH: Listen socket recovered
```

and the next connection attempt will succeed.

### Generating Custom SSH Host Key

The included test host key is for development only. Generate your own:
Expand Down
118 changes: 100 additions & 18 deletions src/port/stm32h563/ssh_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,14 @@ static struct {
int rx_len;
uint32_t start_tick;
int channel_open;
uint16_t port; /* saved for listen socket recovery */
uint8_t session_established; /* 1 if SSH channel opened (CONNECTED reached) */
} server;

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

#ifdef DEBUG_WOLFSSH
/* wolfSSH logging callback */
Expand Down Expand Up @@ -154,9 +157,9 @@ static int handle_command(const char *cmd, char *response, int max_len)
" uptime - Show uptime in seconds\r\n"
" exit - Close SSH session\r\n\r\n";
len = strlen(help);
if (len < max_len) {
memcpy(response, help, len);
}
if (len >= max_len)
len = max_len - 1;
memcpy(response, help, len);
}
else if (strncmp(cmd, "info", 4) == 0) {
const char *info =
Expand All @@ -165,9 +168,9 @@ static int handle_command(const char *cmd, char *response, int max_len)
"SSH: wolfSSH\r\n"
"TLS: wolfSSL TLS 1.3\r\n\r\n";
len = strlen(info);
if (len < max_len) {
memcpy(response, info, len);
}
if (len >= max_len)
len = max_len - 1;
memcpy(response, info, len);
}
else if (strncmp(cmd, "uptime", 6) == 0) {
uint32_t uptime = ssh_server_get_uptime();
Expand All @@ -179,22 +182,25 @@ static int handle_command(const char *cmd, char *response, int max_len)
strcpy(response, prefix);
strcat(response, num_buf);
strcat(response, suffix);
} else {
len = 0;
}
}
else if (strncmp(cmd, "exit", 4) == 0 || strncmp(cmd, "quit", 4) == 0) {
const char *bye = "\r\nGoodbye!\r\n";
len = strlen(bye);
if (len < max_len) {
memcpy(response, bye, len);
}
return -1; /* Signal to close connection */
if (len >= max_len)
len = max_len - 1;
memcpy(response, bye, len);
response[len] = '\0';
return -len; /* Negative = close, magnitude = byte count to send */
}
else if (cmd[0] != '\0' && cmd[0] != '\r' && cmd[0] != '\n') {
const char *unknown = "\r\nUnknown command. Type 'help' for available commands.\r\n\r\n";
len = strlen(unknown);
if (len < max_len) {
memcpy(response, unknown, len);
}
if (len >= max_len)
len = max_len - 1;
memcpy(response, unknown, len);
}
else {
/* Empty command - just return prompt */
Expand All @@ -215,6 +221,7 @@ int ssh_server_init(struct wolfIP *stack, uint16_t port, ssh_debug_cb debug)
server.listen_fd = -1;
server.client_fd = -1;
server.state = SSH_STATE_LISTENING;
server.port = port;

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

Expand Down Expand Up @@ -295,6 +302,56 @@ int ssh_server_init(struct wolfIP *stack, uint16_t port, ssh_debug_cb debug)
return 0;
}

/* Re-open the listen socket when wolfIP has corrupted it (e.g. listen socket
* got stuck in TCP_ESTABLISHED after the RTO fired and the client ACK arrived
* before wolfIP_sock_accept() was called). */
static int ssh_reinit_listen(void)
{
struct wolfIP_sockaddr_in addr;
int ret;

debug_print("SSH: Reinitializing listen socket\n");

/* Close the broken listen socket (ignore errors) */
if (server.listen_fd >= 0) {
wolfIP_sock_close(server.stack, server.listen_fd);
server.listen_fd = -1;
}

/* Create a new listen socket */
server.listen_fd = wolfIP_sock_socket(server.stack,
AF_INET, IPSTACK_SOCK_STREAM, 0);
if (server.listen_fd < 0) {
debug_print("SSH: reinit socket() failed\n");
return -1;
}

memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = ee16(server.port);
addr.sin_addr.s_addr = 0;

ret = wolfIP_sock_bind(server.stack, server.listen_fd,
(struct wolfIP_sockaddr *)&addr, sizeof(addr));
if (ret < 0) {
debug_print("SSH: reinit bind() failed\n");
wolfIP_sock_close(server.stack, server.listen_fd);
server.listen_fd = -1;
return -1;
}

ret = wolfIP_sock_listen(server.stack, server.listen_fd, 1);
if (ret < 0) {
debug_print("SSH: reinit listen() failed\n");
wolfIP_sock_close(server.stack, server.listen_fd);
server.listen_fd = -1;
return -1;
}

debug_print("SSH: Listen socket recovered\n");
return 0;
}

int ssh_server_poll(void)
{
int ret;
Expand All @@ -308,8 +365,19 @@ int ssh_server_poll(void)
(struct wolfIP_sockaddr *)&client_addr, &addr_len);
if (ret >= 0) {
server.client_fd = ret;
server.session_established = 0;
debug_print("SSH: Client connected\n");
server.state = SSH_STATE_ACCEPTING;
} else if (ret == -1) {
/* Listen socket in unexpected state (e.g. stuck in
* TCP_ESTABLISHED after wolfIP's RTO mechanism fired and the
* client ACK arrived before wolfIP_sock_accept() was called,
* or destroyed after TCP_CTRL_RTO_MAXRTX retries).
* Reinitialize to recover. */
if (ssh_reinit_listen() < 0) {
debug_print("SSH: Listen reinit failed\n");
return -1;
}
}
break;

Expand Down Expand Up @@ -339,6 +407,7 @@ int ssh_server_poll(void)
ret = wolfSSH_accept(server.ssh);
if (ret == WS_SUCCESS) {
debug_print("SSH: Handshake complete\n");
server.session_established = 1;
server.state = SSH_STATE_CONNECTED;
server.rx_len = 0;

Expand All @@ -350,7 +419,10 @@ int ssh_server_poll(void)
wolfSSH_stream_send(server.ssh, (byte *)welcome, strlen(welcome));
} else {
int err = wolfSSH_get_error(server.ssh);
if (err != WS_WANT_READ && err != WS_WANT_WRITE) {
/* Check both ret and err: wolfSSH may return WANT_READ/WRITE
* either as the return value or stored via wolfSSH_get_error() */
if (err != WS_WANT_READ && err != WS_WANT_WRITE &&
ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
debug_print("SSH: Handshake failed\n");
server.state = SSH_STATE_CLOSING;
}
Expand Down Expand Up @@ -384,8 +456,8 @@ int ssh_server_poll(void)
response, sizeof(response));

if (resp_len < 0) {
/* Exit requested */
wolfSSH_stream_send(server.ssh, (byte *)response, strlen(response));
/* Exit requested: magnitude encodes byte count */
wolfSSH_stream_send(server.ssh, (byte *)response, (word32)(-resp_len));
server.state = SSH_STATE_CLOSING;
break;
}
Expand All @@ -404,7 +476,8 @@ int ssh_server_poll(void)
}
} else if (ret < 0) {
int err = wolfSSH_get_error(server.ssh);
if (err != WS_WANT_READ && err != WS_WANT_WRITE) {
if (err != WS_WANT_READ && err != WS_WANT_WRITE &&
ret != WS_WANT_READ && ret != WS_WANT_WRITE) {
debug_print("SSH: Connection closed\n");
server.state = SSH_STATE_CLOSING;
}
Expand All @@ -413,10 +486,19 @@ int ssh_server_poll(void)

case SSH_STATE_CLOSING:
if (server.ssh) {
wolfSSH_shutdown(server.ssh);
/* Only send SSH_MSG_DISCONNECT if the session was fully
* established; calling wolfSSH_shutdown() on a half-open
* session (e.g. auth failure) can trigger unexpected I/O
* that delays the return to LISTENING and widens the window
* in which a new SYN can corrupt the listen socket state. */
if (server.session_established) {
wolfSSH_shutdown(server.ssh);
}
wolfSSH_CleanupIO_wolfIP(server.ssh);
wolfSSH_free(server.ssh);
server.ssh = NULL;
}
server.session_established = 0;
if (server.client_fd >= 0) {
wolfIP_sock_close(server.stack, server.client_fd);
server.client_fd = -1;
Expand Down