Fix critical wsssh process hanging issue after SSH client disconnection

- Add 'broken' flag to tunnel_t struct to distinguish between normal closure and broken connections
- Set broken=1 when detecting EBADF/EPIPE/ECONNRESET errors in tunnel operations
- Modify main loop to immediately kill SSH child process and exit when tunnel breaks
- Exit with code 1 for error conditions, code 0 for normal termination
- Update CHANGELOG.md, README.md, and TODO.md for version 1.4.7
- Prevent indefinite hanging of wsssh process after tunnel failures
parent d6ee31e9
......@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.4.7] - 2025-09-16
### Fixed
- **Critical Process Exit Bug**: Fixed wsssh process hanging after SSH client disconnection
- Added `broken` flag to tunnel structure to distinguish between normal closure and broken connections
- Implemented proper tunnel state tracking for EBADF, EPIPE, and ECONNRESET errors
- Enhanced error handling in `handle_tunnel_data()` for SSH client disconnections
- Fixed main loop to immediately kill SSH child process and exit when tunnel breaks
- Added proper exit code handling: 0 for normal termination, 1 for error conditions
- Prevented indefinite hanging of wsssh process after tunnel failures
### Technical Details
- **Tunnel State Management**: Added `int broken` field to `tunnel_t` structure for connection state tracking
- **Error Detection**: Enhanced detection of broken SSH connections with proper error classification
- **Process Management**: Improved SSH child process termination logic with immediate cleanup
- **Exit Code Standards**: Implemented standard exit codes (0=success, 1=error) for better automation support
- **Memory Management**: Maintained proper cleanup of tunnel resources in all exit paths
## [1.4.6] - 2025-09-16
### Added
......
......@@ -597,7 +597,25 @@ Your support helps us continue developing and maintaining this open-source proje
## Changelog
### Version 1.4.6 (Latest)
### Version 1.4.7 (Latest)
**Critical Bug Fix:**
- **Process Exit Issue Resolution**: Fixed wsssh process hanging after SSH client disconnection
- Added `broken` flag to tunnel structure to distinguish between normal closure and broken connections
- Implemented proper tunnel state tracking for EBADF, EPIPE, and ECONNRESET errors
- Enhanced error handling in `handle_tunnel_data()` for SSH client disconnections
- Fixed main loop to immediately kill SSH child process and exit when tunnel breaks
- Added proper exit code handling: 0 for normal termination, 1 for error conditions
- Prevented indefinite hanging of wsssh process after tunnel failures
**Technical Improvements:**
- **Tunnel State Management**: Added `int broken` field to `tunnel_t` structure for connection state tracking
- **Error Detection**: Enhanced detection of broken SSH connections with proper error classification
- **Process Management**: Improved SSH child process termination logic with immediate cleanup
- **Exit Code Standards**: Implemented standard exit codes (0=success, 1=error) for better automation support
- **Memory Management**: Maintained proper cleanup of tunnel resources in all exit paths
### Version 1.4.6
**Major Refactoring:**
- **Code Architecture Overhaul**: Major refactoring to eliminate code duplication
......
# WebSocket SSH - Future Enhancements Roadmap
## Recently Completed (v1.4.7)
- [x] **Critical Process Exit Bug Fix**: Fixed wsssh process hanging after SSH client disconnection
- Added `broken` flag to tunnel structure to distinguish between normal closure and broken connections
- Implemented proper tunnel state tracking for EBADF, EPIPE, and ECONNRESET errors
- Enhanced error handling in `handle_tunnel_data()` for SSH client disconnections
- Fixed main loop to immediately kill SSH child process and exit when tunnel breaks
- Added proper exit code handling: 0 for normal termination, 1 for error conditions
- Prevented indefinite hanging of wsssh process after tunnel failures
- [x] **Documentation Updates**: Updated CHANGELOG.md, README.md, DOCUMENTATION.md, and TODO.md for version 1.4.7
## Recently Completed (v1.4.6)
- [x] **Code Refactoring and Library Architecture**: Major refactoring to eliminate code duplication
- Created shared libraries: `wssshlib.h/.c`, `websocket.h/.c`, `wssh_ssl.h/.c`, `tunnel.h/.c`
......
......@@ -139,13 +139,15 @@ void handle_tunnel_request(SSL *ssl, const char *request_id, int debug) {
active_tunnel->local_sock = -1; // Not used in wssshc
strcpy(active_tunnel->request_id, request_id);
active_tunnel->active = 1;
active_tunnel->broken = 0;
active_tunnel->ssl = ssl;
active_tunnel->outgoing_buffer = NULL; // wssshc doesn't use buffer
active_tunnel->incoming_buffer = NULL; // wssshc doesn't need incoming buffer
pthread_mutex_unlock(&tunnel_mutex);
if (debug) {
printf("[DEBUG] wssshc connected to target SSH server\n");
printf("[DEBUG - Tunnel] wssshc connected to target SSH server\n");
fflush(stdout);
}
// Send tunnel_ack back to server
......@@ -153,7 +155,8 @@ void handle_tunnel_request(SSL *ssl, const char *request_id, int debug) {
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_ack\",\"request_id\":\"%s\"}", request_id);
if (debug) {
printf("[DEBUG] Sending tunnel_ack: %s\n", ack_msg);
printf("[DEBUG - WebSockets] Sending tunnel_ack: %s\n", ack_msg);
fflush(stdout);
}
if (!send_websocket_frame(ssl, ack_msg)) {
......@@ -223,7 +226,7 @@ void *forward_tcp_to_ws(void *arg) {
// Check if socket is valid
if (sock < 0) {
if (debug) {
printf("[DEBUG] Socket not ready yet, waiting...\n");
printf("[DEBUG - Tunnel] Socket not ready yet, waiting...\n");
fflush(stdout);
}
pthread_mutex_unlock(&tunnel_mutex);
......@@ -256,7 +259,7 @@ void *forward_tcp_to_ws(void *arg) {
// Send any buffered data to the SSH client
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (debug) {
printf("[DEBUG] Sending %zu bytes of buffered data to SSH client\n", active_tunnel->incoming_buffer->used);
printf("[DEBUG - TCPConnection] Sending %zu bytes of buffered data to SSH client\n", active_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(client_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
......@@ -270,7 +273,7 @@ void *forward_tcp_to_ws(void *arg) {
}
if (debug) {
printf("[DEBUG] SSH client connected, starting data forwarding\n");
printf("[DEBUG - Tunnel] SSH client connected, starting data forwarding\n");
fflush(stdout);
}
}
......@@ -281,7 +284,7 @@ void *forward_tcp_to_ws(void *arg) {
if (sent > 0) {
frame_buffer_consume(active_tunnel->outgoing_buffer, sent);
if (debug) {
printf("[DEBUG] Sent %zd bytes from buffer to local socket\n", sent);
printf("[DEBUG - TCPConnection] Sent %zd bytes from buffer to local socket\n", sent);
fflush(stdout);
}
} else if (sent == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
......@@ -319,9 +322,9 @@ void *forward_tcp_to_ws(void *arg) {
if (bytes_read <= 0) {
if (debug) {
if (bytes_read == 0) {
printf("[DEBUG] TCP connection closed by peer (SSH client disconnected)\n");
printf("[DEBUG - TCPConnection] TCP connection closed by peer (SSH client disconnected)\n");
} else {
printf("[DEBUG] TCP connection error: %s\n", strerror(errno));
printf("[DEBUG - TCPConnection] TCP connection error: %s\n", strerror(errno));
}
fflush(stdout);
}
......@@ -329,13 +332,20 @@ void *forward_tcp_to_ws(void *arg) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
active_tunnel->active = 0;
active_tunnel->broken = 1;
// Send tunnel_close notification immediately when local connection breaks
if (debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification from forwarding thread...\n");
fflush(stdout);
}
send_tunnel_close(active_tunnel->ssl, active_tunnel->request_id, debug);
}
pthread_mutex_unlock(&tunnel_mutex);
break;
}
if (debug) {
printf("[DEBUG] Forwarding %d bytes from TCP to WebSocket\n", bytes_read);
printf("[DEBUG - TCPConnection] Forwarding %d bytes from TCP to WebSocket\n", bytes_read);
fflush(stdout);
}
......@@ -371,10 +381,25 @@ void *forward_tcp_to_ws(void *arg) {
}
if (debug) {
printf("[DEBUG] TCP to WebSocket forwarding thread exiting\n");
printf("[DEBUG - TCPConnection] TCP to WebSocket forwarding thread exiting\n");
fflush(stdout);
}
// Mark tunnel as inactive when forwarding thread exits due to broken connection
if (active_tunnel) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel->active) {
active_tunnel->active = 0;
if (debug) {
printf("[DEBUG - TCPConnection] Marked tunnel as inactive due to forwarding thread exit\n");
fflush(stdout);
}
// Send tunnel_close notification
send_tunnel_close(active_tunnel->ssl, active_tunnel->request_id, debug);
}
pthread_mutex_unlock(&tunnel_mutex);
}
free(args);
return NULL;
}
......@@ -422,14 +447,14 @@ void *forward_ws_to_ssh_server(void *arg) {
bytes_read = recv(ssh_sock, buffer, sizeof(buffer), 0);
if (bytes_read <= 0) {
if (debug) {
printf("[DEBUG] SSH server connection closed or error\n");
printf("[DEBUG - TCPConnection] SSH server connection closed or error\n");
fflush(stdout);
}
break;
}
if (debug) {
printf("[DEBUG] Forwarding %d bytes from SSH server to WebSocket\n", bytes_read);
printf("[DEBUG - TCPConnection] Forwarding %d bytes from SSH server to WebSocket\n", bytes_read);
fflush(stdout);
}
......@@ -465,7 +490,7 @@ void *forward_ws_to_ssh_server(void *arg) {
}
if (debug) {
printf("[DEBUG] SSH server to WebSocket forwarding thread exiting\n");
printf("[DEBUG - TCPConnection] SSH server to WebSocket forwarding thread exiting\n");
fflush(stdout);
}
......@@ -595,6 +620,9 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
active_tunnel->active = 0;
active_tunnel->broken = 1;
// Send tunnel_close notification immediately
send_tunnel_close(active_tunnel->ssl, active_tunnel->request_id, debug);
}
pthread_mutex_unlock(&tunnel_mutex);
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
......@@ -613,6 +641,9 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel) {
active_tunnel->active = 0;
active_tunnel->broken = 1;
// Send tunnel_close notification immediately
send_tunnel_close(active_tunnel->ssl, active_tunnel->request_id, debug);
}
pthread_mutex_unlock(&tunnel_mutex);
} else {
......@@ -639,6 +670,23 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
free(data);
}
void send_tunnel_close(SSL *ssl, const char *request_id, int debug) {
char close_msg[256];
snprintf(close_msg, sizeof(close_msg), "{\"type\":\"tunnel_close\",\"request_id\":\"%s\"}", request_id);
if (debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close: %s\n", close_msg);
fflush(stdout);
}
if (!send_websocket_frame(ssl, close_msg)) {
if (debug) {
printf("[DEBUG - Tunnel] Failed to send tunnel_close message\n");
fflush(stdout);
}
}
}
void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && strcmp(active_tunnel->request_id, request_id) == 0) {
......@@ -658,7 +706,8 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
free(active_tunnel);
active_tunnel = NULL;
if (debug) {
printf("[DEBUG] Tunnel %s closed\n", request_id);
printf("[DEBUG - Tunnel] Tunnel %s closed\n", request_id);
fflush(stdout);
}
}
pthread_mutex_unlock(&tunnel_mutex);
......@@ -1008,6 +1057,7 @@ int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id
strcpy(active_tunnel->request_id, request_id);
active_tunnel->local_sock = -1;
active_tunnel->active = 1;
active_tunnel->broken = 0;
active_tunnel->ssl = ssl;
// Start listening on local port
......
......@@ -36,6 +36,7 @@ typedef struct {
int sock; // SSH client connection socket (for wsssh) or SSH server connection socket (for wssshc)
char request_id[37]; // UUID string
int active;
int broken; // Flag to indicate if the tunnel is broken (not normal closure)
SSL *ssl; // WebSocket SSL connection
frame_buffer_t *outgoing_buffer; // Buffer for data to send to local socket (wsscp only)
frame_buffer_t *incoming_buffer; // Buffer for incoming data before connection is established
......@@ -59,6 +60,7 @@ void *tunnel_thread(void *arg);
void handle_tunnel_request(SSL *ssl, const char *request_id, int debug);
void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, int debug);
void handle_tunnel_close(SSL *ssl, const char *request_id, int debug);
void send_tunnel_close(SSL *ssl, const char *request_id, int debug);
void cleanup_tunnel(int debug);
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug);
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug, int use_buffer);
......
......@@ -544,6 +544,15 @@ int main(int argc, char *argv[]) {
break;
}
// Check if tunnel became inactive during processing
if (!active_tunnel || !active_tunnel->active) {
if (config.debug) {
printf("[DEBUG] Tunnel became inactive, exiting main loop\n");
fflush(stdout);
}
break;
}
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&readfds);
FD_SET(ssl_fd, &readfds);
......@@ -558,6 +567,14 @@ int main(int argc, char *argv[]) {
}
break;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive during timeout
if (!active_tunnel || !active_tunnel->active) {
if (config.debug) {
printf("[DEBUG] Tunnel became inactive during timeout, exiting\n");
fflush(stdout);
}
break;
}
// Timeout, continue loop
continue;
}
......@@ -886,5 +903,6 @@ cleanup:
free(config_domain);
pthread_mutex_destroy(&tunnel_mutex);
return 0;
// Ensure we exit the process
exit(0);
}
\ No newline at end of file
This diff is collapsed.
......@@ -371,7 +371,8 @@ int connect_to_server(const wssshc_config_t *config) {
fprintf(stderr, "SSL read error\n");
} else {
if (config->debug) {
printf("[DEBUG] Connection closed by server\n");
printf("[DEBUG - WebSockets] Connection closed by server\n");
fflush(stdout);
}
}
// Clean up tunnel resources before breaking
......@@ -380,7 +381,8 @@ int connect_to_server(const wssshc_config_t *config) {
}
if (config->debug) {
printf("[DEBUG] [WebSocket] Read %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", bytes_read, buffer[0], buffer[1], buffer[2], buffer[3]);
printf("[DEBUG - WebSockets] Read %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", bytes_read, buffer[0], buffer[1], buffer[2], buffer[3]);
fflush(stdout);
}
// Parse WebSocket frame with extended length support
......@@ -433,7 +435,8 @@ int connect_to_server(const wssshc_config_t *config) {
// Handle message
if (config->debug) {
printf("[DEBUG] [WebSocket] Received message: %s\n", buffer);
printf("[DEBUG - WebSockets] Received message: %s\n", buffer);
fflush(stdout);
}
// Parse JSON message
......@@ -450,7 +453,8 @@ int connect_to_server(const wssshc_config_t *config) {
if (close_quote) {
*close_quote = '\0';
if (config->debug) {
printf("[DEBUG] [WebSocket] Received tunnel_request for ID: %s\n", id_start);
printf("[DEBUG - WebSockets] Received tunnel_request for ID: %s\n", id_start);
fflush(stdout);
}
handle_tunnel_request(ssl, id_start, config->debug);
}
......@@ -459,7 +463,8 @@ int connect_to_server(const wssshc_config_t *config) {
}
} else if (strstr(buffer, "tunnel_data")) {
if (config->debug) {
printf("[DEBUG] [WebSocket] Received tunnel_data message\n");
printf("[DEBUG - WebSockets] Received tunnel_data message\n");
fflush(stdout);
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
......@@ -501,7 +506,8 @@ int connect_to_server(const wssshc_config_t *config) {
if (close_quote) {
*close_quote = '\0';
if (config->debug) {
printf("[DEBUG] [WebSocket] Received tunnel_close for ID: %s\n", id_start);
printf("[DEBUG - WebSockets] Received tunnel_close for ID: %s\n", id_start);
fflush(stdout);
}
handle_tunnel_close(ssl, id_start, config->debug);
}
......@@ -516,14 +522,16 @@ int connect_to_server(const wssshc_config_t *config) {
}
} else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) { // Close frame
if (config->debug) {
printf("[DEBUG] [WebSocket] Received close frame - cleaning up and reconnecting...\n");
printf("[DEBUG - WebSockets] Received close frame - cleaning up and reconnecting...\n");
fflush(stdout);
}
// Clean up tunnel resources before reconnecting
cleanup_tunnel(config->debug);
return 0;
} else if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x89) { // Ping frame
if (config->debug) {
printf("[DEBUG] [WebSocket] Received ping frame\n");
printf("[DEBUG - WebSockets] Received ping frame\n");
fflush(stdout);
}
// Parse ping frame and send pong with echoed payload
int masked = buffer[1] & 0x80;
......@@ -620,7 +628,8 @@ int connect_to_server(const wssshc_config_t *config) {
int pong_frame_len = pong_header_len + (int)pong_payload_len;
if (config->debug) {
printf("[DEBUG] [WebSocket] Sending pong frame, len: %d\n", pong_frame_len);
printf("[DEBUG - WebSockets] Sending pong frame, len: %d\n", pong_frame_len);
fflush(stdout);
}
if (SSL_write(ssl, pong_frame, pong_frame_len) <= 0) {
fprintf(stderr, "[ERROR] [WebSocket] Send pong failed\n");
......@@ -681,7 +690,8 @@ int main(int argc, char *argv[]) {
} else if (result == 0) {
// Close frame received - add small delay to prevent rapid reconnection
if (config.debug) {
printf("[DEBUG] Server initiated disconnect, reconnecting in 2 seconds...\n");
printf("[DEBUG - WebSockets] Server initiated disconnect, reconnecting in 2 seconds...\n");
fflush(stdout);
}
sleep(2);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment