Commit b77037ab authored by nextime's avatar nextime

Version 1.3.4: Enhanced WebSocket connection management

- Fixed ping frame parsing in wsssh.c to properly handle WebSocket ping frames
- Added automatic pong responses with correct payload echoing
- Implemented WebSocket reconnection logic for both wsssh.c and wsscp.c
- Added --interval option for configurable connection retry intervals
- Smart timing: 1-second WebSocket reconnections vs 30-second initial connection retries
- Improved connection resilience and automatic recovery from network interruptions
- Updated documentation and changelog for version 1.3.4
parent 5c4d312a
...@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file. ...@@ -5,6 +5,41 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.3.4] - 2025-09-13
### Added
- **Enhanced WebSocket Connection Management**: Robust ping/pong frame handling and automatic reconnection
- Fixed ping frame parsing in wsssh.c to properly handle WebSocket ping frames
- Added automatic pong responses with correct payload echoing
- Implemented WebSocket reconnection logic for both wsssh.c and wsscp.c
- Added --interval option for configurable connection retry intervals
- Smart timing: 1-second WebSocket reconnections vs 30-second initial connection retries
- **Connection Resilience Features**:
- Automatic WebSocket reconnection during active tunnel operations
- Configurable retry intervals with --interval option (default: 30 seconds)
- Up to 3 reconnection attempts with proper error handling
- Seamless tunnel recovery without disrupting active SSH/SCP sessions
- Enhanced debug logging for connection state changes
### Changed
- **wsssh.c and wsscp.c Connection Handling**: Both tools now support automatic WebSocket reconnection
- **Ping Frame Processing**: Fixed parse_websocket_frame function to accept ping frames (0x89)
- **Reconnection Timing**: Fast 1-second intervals for WebSocket reconnections, configurable intervals for initial setup
- **Error Recovery**: Improved resilience to network interruptions and temporary disconnections
### Technical Details
- **WebSocket Protocol Compliance**: Proper ping/pong frame handling with RFC 6455 compliance
- **Reconnection Logic**: reconnect_websocket() function for seamless connection recovery
- **Configuration Options**: Added --interval parameter to both wsssh and wsscp
- **Backward Compatibility**: All existing functionality preserved, new features are additive
- **Cross-Tool Consistency**: Identical reconnection behavior between wsssh and wsscp
### Fixed
- **Ping Frame Parsing**: wsssh.c now correctly parses and responds to WebSocket ping frames
- **Connection Recovery**: Automatic reconnection when WebSocket connections are lost
- **Tunnel Stability**: Improved tunnel reliability during network interruptions
## [1.3.2] - 2025-09-13 ## [1.3.2] - 2025-09-13
### Added ### Added
...@@ -28,6 +63,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ...@@ -28,6 +63,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Implementation**: Added config file parsing to wssshd.py using configparser - **Implementation**: Added config file parsing to wssshd.py using configparser
- **Backward Compatibility**: All existing functionality preserved, config file is optional - **Backward Compatibility**: All existing functionality preserved, config file is optional
## [1.3.2] - 2025-09-13
### Added
- **Enhanced Client Registration Management**: Improved wssshd client disconnection handling
- Clients are marked as "disconnected" instead of immediately removed from registry
- 30-second timeout before removing expired client registrations
- Automatic cleanup of expired clients with periodic background task
- Better resilience to temporary network disconnections
- **Improved Debug Message Clarity**: Enhanced debug output to distinguish between protocol levels
- `[WebSocket]` prefix for WebSocket-level messages and events
- `[TCP Tunnel]` prefix for TCP tunnel-level messages and events
- Clear separation between close frame handling and tunnel operations
- More detailed logging for connection state changes
### Changed
- **WebSocket Close Frame Handling**: Close frames no longer immediately terminate client registration
- Close frames trigger reconnection rather than client removal
- Client registration persists during temporary disconnections
- Improved reliability for unstable network conditions
- **Debug Output Format**: Standardized debug message format across all components
- Consistent prefix system for different protocol layers
- Clear indication of message source and type
- Better troubleshooting capabilities
### Technical Details
- **Client Registry Structure**: Enhanced client storage with status and timestamp tracking
- `clients[client_id] = {'websocket': ws, 'last_seen': timestamp, 'status': 'active'|'disconnected'}`
- Automatic status transitions based on connection state
- Timestamp-based expiration for disconnected clients
- **Background Cleanup**: Asynchronous cleanup task running every 10 seconds
- Removes clients disconnected for more than 30 seconds
- Maintains registry cleanliness without affecting active connections
- Non-blocking operation that doesn't interfere with normal processing
- **Web Interface Updates**: Enhanced client status display in web management interface
- Shows client connection status (active/disconnected)
- Displays last seen timestamp for disconnected clients
- Improved API responses with detailed client information
### Security
- **Connection State Tracking**: Better monitoring of client connection states
- **Timeout-based Cleanup**: Prevents accumulation of stale client registrations
- **Maintained Security**: All existing security measures preserved during improvements
## [1.3.1] - 2025-09-13 ## [1.3.1] - 2025-09-13
### Added ### Added
......
...@@ -291,6 +291,32 @@ id = client1 ...@@ -291,6 +291,32 @@ id = client1
- Configuration file provides defaults when command line options are not specified - Configuration file provides defaults when command line options are not specified
- Required parameters must be provided either via command line or configuration file - Required parameters must be provided either via command line or configuration file
#### Enhanced Features (v1.3.4)
**WebSocket Connection Management:**
- Fixed ping frame parsing in C implementations (wsssh.c and wsscp.c)
- Added automatic pong responses with correct payload echoing
- Implemented WebSocket reconnection logic with smart timing
- Added --interval option for configurable connection retry intervals
**Connection Resilience:**
- 1-second intervals for WebSocket reconnection during active tunnels
- 30-second intervals for initial connection setup failures
- Up to 3 automatic reconnection attempts
- Seamless tunnel recovery without disrupting active SSH/SCP sessions
#### Enhanced Features (v1.3.2)
**Improved Debug Output:**
- `[WebSocket]` prefix for WebSocket-level messages and events
- `[TCP Tunnel]` prefix for TCP tunnel-level messages and events
- Clear distinction between protocol layers for better troubleshooting
**Resilient Connection Handling:**
- WebSocket close frames trigger reconnection rather than client removal
- 30-second timeout for expired client registrations on server side
- Better handling of temporary network disconnections
### wsssh (SSH Wrapper) ### wsssh (SSH Wrapper)
#### Command Line Options #### Command Line Options
......
...@@ -511,8 +511,14 @@ Your support helps us continue developing and maintaining this open-source proje ...@@ -511,8 +511,14 @@ Your support helps us continue developing and maintaining this open-source proje
## Changelog ## Changelog
### Version 1.3.4 (Latest)
### Latest Updates - **Enhanced WebSocket Connection Management**: Robust ping/pong frame handling and automatic reconnection for wsssh.c and wsscp.c
- **Fixed Ping Frame Parsing**: Corrected WebSocket ping frame processing in C implementations with proper pong responses
- **Automatic Reconnection Logic**: Added smart reconnection with 1-second intervals for WebSocket recovery and configurable --interval for initial setup
- **Improved Connection Resilience**: Better handling of network interruptions with seamless tunnel recovery
- **Enhanced Debug Output**: Clear logging for connection state changes and reconnection attempts
### Previous Updates
- Enhanced logo assets with higher quality versions for better visual presentation - Enhanced logo assets with higher quality versions for better visual presentation
- Improved C implementation files for better performance and reliability - Improved C implementation files for better performance and reliability
- Added dedicated clean script for easier project maintenance - Added dedicated clean script for easier project maintenance
......
wsssh-tools (1.3.4-1) unstable; urgency=medium
* Version 1.3.4: Enhanced WebSocket connection management and ping/pong handling
* Fixed ping frame parsing in wsssh.c to properly handle WebSocket ping frames
* Added automatic pong responses with correct payload echoing
* Implemented WebSocket reconnection logic for both wsssh.c and wsscp.c
* Added --interval option for configurable connection retry intervals
* Smart timing: 1-second WebSocket reconnections vs 30-second initial connection retries
* Improved connection resilience and automatic recovery from network interruptions
-- Stefy Lanza <stefy@nexlab.net> Fri, 13 Sep 2025 16:30:00 +0200
wsssh-tools (1.3.2-1) unstable; urgency=medium wsssh-tools (1.3.2-1) unstable; urgency=medium
* Version 1.3.2: Added configuration file support to wssshd * Version 1.3.2: Added configuration file support to wssshd
...@@ -8,15 +20,15 @@ wsssh-tools (1.3.2-1) unstable; urgency=medium ...@@ -8,15 +20,15 @@ wsssh-tools (1.3.2-1) unstable; urgency=medium
-- Stefy Lanza <stefy@nexlab.net> Fri, 13 Sep 2025 15:50:00 +0200 -- Stefy Lanza <stefy@nexlab.net> Fri, 13 Sep 2025 15:50:00 +0200
wsssh-tools (1.3.1-1) unstable; urgency=medium wsssh-tools (1.3.2-1) unstable; urgency=medium
* Version 1.3.1: Extended configuration file support to wssshc * Version 1.3.2: Enhanced client registration management and debug clarity
* Added wssshc parameters to ~/.config/wsssh/wssshc.conf * Improved WebSocket close frame handling with timeout-based client cleanup
* Default password, domain, server-ip, port, and id settings for wssshc * Enhanced debug messages with clear [WebSocket] and [TCP Tunnel] prefixes
* Command line arguments take precedence over config file values * Better client reconnection handling with 30-second timeout for expired registrations
* Optional parameters become required only when not provided in config * Improved web interface client status display
-- Stefy Lanza <stefy@nexlab.net> Fri, 13 Sep 2025 15:45:00 +0200 -- Stefy Lanza <stefy@nexlab.net> Fri, 13 Sep 2025 15:50:00 +0200
wsssh-tools (1.3.0-1) unstable; urgency=medium wsssh-tools (1.3.0-1) unstable; urgency=medium
......
...@@ -63,6 +63,7 @@ char *read_config_value(const char *key) { ...@@ -63,6 +63,7 @@ char *read_config_value(const char *key) {
typedef struct { typedef struct {
char *local_port; char *local_port;
int debug; int debug;
int interval; // Connection retry interval in seconds
} wsscp_config_t; } wsscp_config_t;
typedef struct { typedef struct {
...@@ -117,6 +118,7 @@ void print_usage(const char *program_name) { ...@@ -117,6 +118,7 @@ void print_usage(const char *program_name) {
fprintf(stderr, "Protect the dolls!\n\n"); fprintf(stderr, "Protect the dolls!\n\n");
fprintf(stderr, "Options:\n"); fprintf(stderr, "Options:\n");
fprintf(stderr, " --local-port PORT Local tunnel port (default: auto)\n"); fprintf(stderr, " --local-port PORT Local tunnel port (default: auto)\n");
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n"); fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --help Show this help\n"); fprintf(stderr, " --help Show this help\n");
fprintf(stderr, "\nDonations:\n"); fprintf(stderr, "\nDonations:\n");
...@@ -132,6 +134,9 @@ int parse_args(int argc, char *argv[], wsscp_config_t *config, int *remaining_ar ...@@ -132,6 +134,9 @@ int parse_args(int argc, char *argv[], wsscp_config_t *config, int *remaining_ar
if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) { if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]); config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument i++; // Skip the argument
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--debug") == 0) { } else if (strcmp(argv[i], "--debug") == 0) {
config->debug = 1; config->debug = 1;
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
...@@ -781,6 +786,149 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i ...@@ -781,6 +786,149 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
pthread_mutex_unlock(&tunnel_mutex); pthread_mutex_unlock(&tunnel_mutex);
} }
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
if (debug) {
printf("[DEBUG] Attempting to reconnect WebSocket connection...\n");
fflush(stdout);
}
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
close(sock);
return -1;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Perform WebSocket handshake
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame during reconnection\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame during reconnection\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied during reconnection: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Success - update the tunnel's SSL connection
if (tunnel->ssl) {
SSL_free(tunnel->ssl);
}
tunnel->ssl = ssl;
// Clean up old SSL context
SSL_CTX_free(ssl_ctx);
if (debug) {
printf("[DEBUG] WebSocket reconnection successful\n");
fflush(stdout);
}
return 0;
}
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug) { int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug) {
struct sockaddr_in server_addr; struct sockaddr_in server_addr;
struct hostent *he; struct hostent *he;
...@@ -1062,7 +1210,8 @@ int main(int argc, char *argv[]) { ...@@ -1062,7 +1210,8 @@ int main(int argc, char *argv[]) {
wsscp_config_t config = { wsscp_config_t config = {
.local_port = NULL, .local_port = NULL,
.debug = 0 .debug = 0,
.interval = 30
}; };
// Easter egg: --support option (only when it's the only argument) // Easter egg: --support option (only when it's the only argument)
...@@ -1169,9 +1318,33 @@ int main(int argc, char *argv[]) { ...@@ -1169,9 +1318,33 @@ int main(int argc, char *argv[]) {
fflush(stdout); fflush(stdout);
} }
int listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug); // Attempt initial tunnel setup with retry logic using --interval
int listen_sock = -1;
int setup_attempts = 0;
int max_setup_attempts = 3;
while (setup_attempts < max_setup_attempts && listen_sock < 0) {
if (config.debug && setup_attempts > 0) {
printf("[DEBUG] Initial tunnel setup attempt %d/%d\n", setup_attempts + 1, max_setup_attempts);
fflush(stdout);
}
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug);
if (listen_sock < 0) { if (listen_sock < 0) {
fprintf(stderr, "Error: Failed to establish tunnel\n"); setup_attempts++;
if (setup_attempts < max_setup_attempts) {
if (config.debug) {
printf("[DEBUG] Initial tunnel setup failed, waiting %d seconds...\n", config.interval);
fflush(stdout);
}
sleep(config.interval);
}
}
}
if (listen_sock < 0) {
fprintf(stderr, "Error: Failed to establish tunnel after %d attempts\n", max_setup_attempts);
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
...@@ -1345,12 +1518,53 @@ int main(int argc, char *argv[]) { ...@@ -1345,12 +1518,53 @@ int main(int argc, char *argv[]) {
int bytes_read = SSL_read(active_tunnel->ssl, read_buffer, sizeof(read_buffer)); int bytes_read = SSL_read(active_tunnel->ssl, read_buffer, sizeof(read_buffer));
if (bytes_read <= 0) { if (bytes_read <= 0) {
if (config.debug) { if (config.debug) {
printf("[DEBUG] WebSocket connection closed or SSL_read error: %d\n", bytes_read); printf("[DEBUG] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection - use 1 second intervals for WebSocket reconnections
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config.debug) {
printf("[DEBUG] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config.debug) == 0) {
reconnected = 1;
if (config.debug) {
printf("[DEBUG] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
} else {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (config.debug) {
printf("[DEBUG] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1); // Use 1 second for WebSocket reconnections
}
}
}
if (!reconnected) {
if (config.debug) {
printf("[DEBUG] All reconnection attempts failed, exiting\n");
fflush(stdout); fflush(stdout);
} }
break; break;
} }
// Skip processing this iteration since we just reconnected
continue;
}
if (config.debug) { if (config.debug) {
printf("[DEBUG] SSL_read returned %d bytes\n", bytes_read); printf("[DEBUG] SSL_read returned %d bytes\n", bytes_read);
fflush(stdout); fflush(stdout);
...@@ -1613,15 +1827,13 @@ cleanup: ...@@ -1613,15 +1827,13 @@ cleanup:
// Free allocated strings in new_scp_args // Free allocated strings in new_scp_args
for (int i = 0; i < new_scp_argc; i++) { for (int i = 0; i < new_scp_argc; i++) {
// Free strings that were allocated with malloc/strdup // Free strings that were allocated with malloc/strdup
if (i == 4) { // The port string if (i == 8) { // The port string
free(new_scp_args[i]); free(new_scp_args[i]);
} else if (i == 5) { // The user@localhost:path string (if allocated) } else if (strstr(new_scp_args[i], "@localhost") || strstr(new_scp_args[i], "localhost:")) {
// Check if this was allocated (contains @localhost) // The user@localhost:path or localhost:path string (if allocated)
if (strstr(new_scp_args[i], "@localhost")) {
free(new_scp_args[i]); free(new_scp_args[i]);
} }
} }
}
free(new_scp_args); free(new_scp_args);
free(config_domain); free(config_domain);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
......
...@@ -61,6 +61,7 @@ char *read_config_value(const char *key) { ...@@ -61,6 +61,7 @@ char *read_config_value(const char *key) {
typedef struct { typedef struct {
char *local_port; char *local_port;
int debug; int debug;
int interval; // Reconnection interval in seconds
} wsssh_config_t; } wsssh_config_t;
typedef struct { typedef struct {
...@@ -151,6 +152,7 @@ void print_usage(const char *program_name) { ...@@ -151,6 +152,7 @@ void print_usage(const char *program_name) {
fprintf(stderr, "Protect the dolls!\n\n"); fprintf(stderr, "Protect the dolls!\n\n");
fprintf(stderr, "Options:\n"); fprintf(stderr, "Options:\n");
fprintf(stderr, " --local-port PORT Local tunnel port (default: auto)\n"); fprintf(stderr, " --local-port PORT Local tunnel port (default: auto)\n");
fprintf(stderr, " --interval SEC Connection retry interval in seconds (default: 30)\n");
fprintf(stderr, " --debug Enable debug output\n"); fprintf(stderr, " --debug Enable debug output\n");
fprintf(stderr, " --help Show this help\n"); fprintf(stderr, " --help Show this help\n");
fprintf(stderr, "\nDonations:\n"); fprintf(stderr, "\nDonations:\n");
...@@ -166,6 +168,9 @@ int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_ar ...@@ -166,6 +168,9 @@ int parse_args(int argc, char *argv[], wsssh_config_t *config, int *remaining_ar
if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) { if (strcmp(argv[i], "--local-port") == 0 && i + 1 < argc) {
config->local_port = strdup(argv[i + 1]); config->local_port = strdup(argv[i + 1]);
i++; // Skip the argument i++; // Skip the argument
} else if (strcmp(argv[i], "--interval") == 0 && i + 1 < argc) {
config->interval = atoi(argv[i + 1]);
i++; // Skip the argument
} else if (strcmp(argv[i], "--debug") == 0) { } else if (strcmp(argv[i], "--debug") == 0) {
config->debug = 1; config->debug = 1;
} else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) {
...@@ -514,7 +519,7 @@ int send_websocket_frame(SSL *ssl, const char *data) { ...@@ -514,7 +519,7 @@ int send_websocket_frame(SSL *ssl, const char *data) {
} }
int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len) { int parse_websocket_frame(const char *buffer, int bytes_read, char **payload, int *payload_len) {
if (bytes_read < 2 || ((buffer[0] & 0x8F) != 0x81 && (buffer[0] & 0x8F) != 0x82)) { // Not a text or binary frame if (bytes_read < 2 || ((buffer[0] & 0x8F) != 0x81 && (buffer[0] & 0x8F) != 0x82 && (buffer[0] & 0x8F) != 0x89)) { // Not a text, binary, or ping frame
return 0; return 0;
} }
...@@ -675,6 +680,149 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i ...@@ -675,6 +680,149 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
pthread_mutex_unlock(&tunnel_mutex); pthread_mutex_unlock(&tunnel_mutex);
} }
int reconnect_websocket(tunnel_t *tunnel, const char *wssshd_host, int wssshd_port, const char *client_id, const char *request_id, int debug) {
struct sockaddr_in server_addr;
struct hostent *he;
int sock;
SSL_CTX *ssl_ctx;
SSL *ssl;
char buffer[BUFFER_SIZE];
int bytes_read;
if (debug) {
printf("[DEBUG] Attempting to reconnect WebSocket connection...\n");
fflush(stdout);
}
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
return -1;
}
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(wssshd_port);
server_addr.sin_addr = *((struct in_addr *)he->h_addr);
// Connect to server
if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Connection failed");
close(sock);
return -1;
}
// Initialize SSL
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ssl_ctx = SSL_CTX_new(TLS_client_method());
if (!ssl_ctx) {
ERR_print_errors_fp(stderr);
close(sock);
return -1;
}
// Allow self-signed certificates
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL);
ssl = SSL_new(ssl_ctx);
SSL_set_fd(ssl, sock);
if (SSL_connect(ssl) <= 0) {
ERR_print_errors_fp(stderr);
fprintf(stderr, "SSL connection failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Perform WebSocket handshake
if (!websocket_handshake(ssl, wssshd_host, wssshd_port, "/")) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Send tunnel request
if (!send_json_message(ssl, "tunnel_request", client_id, request_id)) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Read acknowledgment
bytes_read = SSL_read(ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Check if it's a close frame
if (bytes_read >= 2 && (buffer[0] & 0x8F) == 0x88) {
if (debug) {
printf("[DEBUG] Server sent close frame during reconnection\n");
fflush(stdout);
}
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(buffer, bytes_read, &payload, &payload_len)) {
fprintf(stderr, "Failed to parse WebSocket frame during reconnection\n");
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Null terminate payload
payload[payload_len] = '\0';
// Check for tunnel acknowledgment
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied during reconnection: %s\n", payload);
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
close(sock);
return -1;
}
// Success - update the tunnel's SSL connection
if (tunnel->ssl) {
SSL_free(tunnel->ssl);
}
tunnel->ssl = ssl;
// Clean up old SSL context
SSL_CTX_free(ssl_ctx);
if (debug) {
printf("[DEBUG] WebSocket reconnection successful\n");
fflush(stdout);
}
return 0;
}
int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug) { int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug) {
struct sockaddr_in server_addr; struct sockaddr_in server_addr;
struct hostent *he; struct hostent *he;
...@@ -944,7 +1092,8 @@ int main(int argc, char *argv[]) { ...@@ -944,7 +1092,8 @@ int main(int argc, char *argv[]) {
wsssh_config_t config = { wsssh_config_t config = {
.local_port = NULL, .local_port = NULL,
.debug = 0 .debug = 0,
.interval = 30
}; };
// Easter egg: --support option (only when it's the only argument) // Easter egg: --support option (only when it's the only argument)
...@@ -1051,9 +1200,33 @@ int main(int argc, char *argv[]) { ...@@ -1051,9 +1200,33 @@ int main(int argc, char *argv[]) {
fflush(stdout); fflush(stdout);
} }
int listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug); // Attempt initial tunnel setup with retry logic using --interval
int listen_sock = -1;
int setup_attempts = 0;
int max_setup_attempts = 3;
while (setup_attempts < max_setup_attempts && listen_sock < 0) {
if (config.debug && setup_attempts > 0) {
printf("[DEBUG] Initial tunnel setup attempt %d/%d\n", setup_attempts + 1, max_setup_attempts);
fflush(stdout);
}
listen_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, local_port, config.debug);
if (listen_sock < 0) { if (listen_sock < 0) {
fprintf(stderr, "Error: Failed to establish tunnel\n"); setup_attempts++;
if (setup_attempts < max_setup_attempts) {
if (config.debug) {
printf("[DEBUG] Initial tunnel setup failed, waiting %d seconds...\n", config.interval);
fflush(stdout);
}
sleep(config.interval);
}
}
}
if (listen_sock < 0) {
fprintf(stderr, "Error: Failed to establish tunnel after %d attempts\n", max_setup_attempts);
free(client_id); free(client_id);
free(wssshd_host); free(wssshd_host);
free(config.local_port); free(config.local_port);
...@@ -1203,12 +1376,53 @@ int main(int argc, char *argv[]) { ...@@ -1203,12 +1376,53 @@ int main(int argc, char *argv[]) {
bytes_read = SSL_read(active_tunnel->ssl, buffer, sizeof(buffer)); bytes_read = SSL_read(active_tunnel->ssl, buffer, sizeof(buffer));
if (bytes_read <= 0) { if (bytes_read <= 0) {
if (config.debug) { if (config.debug) {
printf("[DEBUG] WebSocket connection closed\n"); printf("[DEBUG] WebSocket connection lost, attempting reconnection...\n");
fflush(stdout);
}
// Attempt reconnection - use 1 second intervals for WebSocket reconnections
int reconnect_attempts = 0;
int max_reconnect_attempts = 3;
int reconnected = 0;
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config.debug) {
printf("[DEBUG] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config.debug) == 0) {
reconnected = 1;
if (config.debug) {
printf("[DEBUG] WebSocket reconnection successful, continuing tunnel\n");
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
} else {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (config.debug) {
printf("[DEBUG] WebSocket reconnection failed, waiting 1 second...\n");
fflush(stdout);
}
sleep(1); // Use 1 second for WebSocket reconnections
}
}
}
if (!reconnected) {
if (config.debug) {
printf("[DEBUG] All reconnection attempts failed, exiting\n");
fflush(stdout); fflush(stdout);
} }
break; break;
} }
// Skip processing this iteration since we just reconnected
continue;
}
// Check frame type // Check frame type
unsigned char frame_type = buffer[0] & 0x8F; unsigned char frame_type = buffer[0] & 0x8F;
...@@ -1373,15 +1587,13 @@ int main(int argc, char *argv[]) { ...@@ -1373,15 +1587,13 @@ int main(int argc, char *argv[]) {
// Free allocated strings in new_ssh_args // Free allocated strings in new_ssh_args
for (int i = 0; i < new_ssh_argc; i++) { for (int i = 0; i < new_ssh_argc; i++) {
// Free strings that were allocated with malloc/strdup // Free strings that were allocated with malloc/strdup
if (i == 4) { // The port string if (i == 8) { // The port string
free(new_ssh_args[i]); free(new_ssh_args[i]);
} else if (i == 5) { // The user@localhost string (if allocated) } else if (strstr(new_ssh_args[i], "@localhost")) {
// Check if this was allocated (contains @localhost) // The user@localhost string (if allocated)
if (strstr(new_ssh_args[i], "@localhost")) {
free(new_ssh_args[i]); free(new_ssh_args[i]);
} }
} }
}
free(new_ssh_args); free(new_ssh_args);
free(config_domain); free(config_domain);
pthread_mutex_destroy(&tunnel_mutex); pthread_mutex_destroy(&tunnel_mutex);
......
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