Implement comprehensive keep-alive system and debug enhancements

- Fix tunnel_data debug logging suppression with proper JSON spacing detection
- Add high-resolution timing for request_id generation using gettimeofday
- Implement complete keep-alive protocol with statistics tracking:
  * tunnel_keepalive messages with total_bytes and rate_bps
  * tunnel_keepalive_ack responses
  * 30-second keep-alive intervals
- Add dual-endpoint keep-alive monitoring in wssshd (220s timeout)
- Implement 190s timeout mechanism for tunnel cleanup
- Add periodic tunnel list debug output in wssshc (every 60 seconds)
- Track comprehensive tunnel statistics (bytes sent/received, transfer rates)
- Enhanced debug logging with detailed tunnel health information
- Proper message forwarding between wssshc  wssshd  wsssht
- Automatic tunnel closure on keep-alive timeout with cleanup
parent e25fd993
...@@ -55,6 +55,18 @@ class Tunnel: ...@@ -55,6 +55,18 @@ class Tunnel:
self.error_message = None self.error_message = None
self.metadata = {} self.metadata = {}
# Keep-alive statistics and timing
self.last_keepalive_sent = time.time()
self.last_keepalive_received = time.time()
self.total_bytes_sent = 0
self.total_bytes_received = 0
self.bytes_last_period = 0
self.last_stats_reset = time.time()
# Dual-endpoint keep-alive monitoring (220s timeout)
self.last_keepalive_from_client = time.time() # wssshc endpoint
self.last_keepalive_from_tool = time.time() # wsssht/wsscp endpoint
def update_status(self, new_status, error_message=None): def update_status(self, new_status, error_message=None):
"""Update tunnel status and timestamp""" """Update tunnel status and timestamp"""
self.status = new_status self.status = new_status
...@@ -113,7 +125,9 @@ class Tunnel: ...@@ -113,7 +125,9 @@ class Tunnel:
'tool_private_ip': self.tool_private_ip, 'tool_private_ip': self.tool_private_ip,
'tool_public_port': self.tool_public_port, 'tool_public_port': self.tool_public_port,
'tool_private_port': self.tool_private_port, 'tool_private_port': self.tool_private_port,
'error_message': self.error_message 'error_message': self.error_message,
'last_keepalive_from_client': self.last_keepalive_from_client,
'last_keepalive_from_tool': self.last_keepalive_from_tool
} }
def __str__(self): def __str__(self):
......
This diff is collapsed.
...@@ -265,6 +265,15 @@ void handle_tunnel_request(SSL *ssl, const char *request_id, int debug, const ch ...@@ -265,6 +265,15 @@ void handle_tunnel_request(SSL *ssl, const char *request_id, int debug, const ch
new_tunnel->incoming_buffer = NULL; // wssshc doesn't need incoming buffer new_tunnel->incoming_buffer = NULL; // wssshc doesn't need incoming buffer
new_tunnel->server_version_sent = 0; // Not used for raw TCP new_tunnel->server_version_sent = 0; // Not used for raw TCP
// Initialize keep-alive statistics
time_t current_time = time(NULL);
new_tunnel->last_keepalive_sent = current_time;
new_tunnel->last_keepalive_received = current_time;
new_tunnel->total_bytes_sent = 0;
new_tunnel->total_bytes_received = 0;
new_tunnel->bytes_last_period = 0;
new_tunnel->last_stats_reset = current_time;
// Add the new tunnel to the array // Add the new tunnel to the array
if (!add_tunnel(new_tunnel)) { if (!add_tunnel(new_tunnel)) {
if (target_sock >= 0) close(target_sock); if (target_sock >= 0) close(target_sock);
...@@ -449,6 +458,15 @@ void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, servic ...@@ -449,6 +458,15 @@ void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, servic
new_tunnel->incoming_buffer = NULL; // wssshc doesn't need incoming buffer new_tunnel->incoming_buffer = NULL; // wssshc doesn't need incoming buffer
new_tunnel->server_version_sent = 0; // Not used for raw TCP/UDP new_tunnel->server_version_sent = 0; // Not used for raw TCP/UDP
// Initialize keep-alive statistics
time_t current_time = time(NULL);
new_tunnel->last_keepalive_sent = current_time;
new_tunnel->last_keepalive_received = current_time;
new_tunnel->total_bytes_sent = 0;
new_tunnel->total_bytes_received = 0;
new_tunnel->bytes_last_period = 0;
new_tunnel->last_stats_reset = current_time;
// Add the new tunnel to the array // Add the new tunnel to the array
if (!add_tunnel(new_tunnel)) { if (!add_tunnel(new_tunnel)) {
if (target_sock >= 0) close(target_sock); if (target_sock >= 0) close(target_sock);
...@@ -708,6 +726,16 @@ void *forward_tcp_to_ws(void *arg) { ...@@ -708,6 +726,16 @@ void *forward_tcp_to_ws(void *arg) {
} }
hex_data[actual_hex_len] = '\0'; hex_data[actual_hex_len] = '\0';
// Track sent bytes for statistics
size_t data_len = bytes_read;
pthread_mutex_lock(&tunnel_mutex);
tunnel_t *stats_tunnel = find_tunnel_by_request_id(request_id);
if (stats_tunnel) {
stats_tunnel->total_bytes_sent += data_len;
stats_tunnel->bytes_last_period += data_len;
}
pthread_mutex_unlock(&tunnel_mutex);
// Send as tunnel_data // Send as tunnel_data
size_t msg_size = strlen("{\"type\":\"tunnel_data\",\"request_id\":\"\",\"data\":\"\"}") + strlen(request_id) + actual_hex_len + 1; size_t msg_size = strlen("{\"type\":\"tunnel_data\",\"request_id\":\"\",\"data\":\"\"}") + strlen(request_id) + actual_hex_len + 1;
char *message = malloc(msg_size); char *message = malloc(msg_size);
...@@ -720,8 +748,8 @@ void *forward_tcp_to_ws(void *arg) { ...@@ -720,8 +748,8 @@ void *forward_tcp_to_ws(void *arg) {
continue; continue;
} }
snprintf(message, msg_size, snprintf(message, msg_size,
"{\"type\":\"tunnel_data\",\"request_id\":\"%s\",\"data\":\"%s\"}", "{\"type\":\"tunnel_data\",\"request_id\":\"%s\",\"data\":\"%s\"}",
request_id, hex_data); request_id, hex_data);
if (!send_websocket_frame(ssl, message)) { if (!send_websocket_frame(ssl, message)) {
if (debug) { if (debug) {
...@@ -843,6 +871,16 @@ void *forward_ws_to_ssh_server(void *arg) { ...@@ -843,6 +871,16 @@ void *forward_ws_to_ssh_server(void *arg) {
} }
hex_data[actual_hex_len] = '\0'; hex_data[actual_hex_len] = '\0';
// Track sent bytes for statistics (responses from target)
size_t data_len = bytes_read;
pthread_mutex_lock(&tunnel_mutex);
tunnel_t *stats_tunnel = find_tunnel_by_request_id(request_id);
if (stats_tunnel) {
stats_tunnel->total_bytes_sent += data_len;
stats_tunnel->bytes_last_period += data_len;
}
pthread_mutex_unlock(&tunnel_mutex);
// Send as tunnel_response (from target back to WebSocket) // Send as tunnel_response (from target back to WebSocket)
size_t msg_size = strlen("{\"type\":\"tunnel_response\",\"request_id\":\"\",\"data\":\"\"}") + strlen(request_id) + actual_hex_len + 1; size_t msg_size = strlen("{\"type\":\"tunnel_response\",\"request_id\":\"\",\"data\":\"\"}") + strlen(request_id) + actual_hex_len + 1;
char *message = malloc(msg_size); char *message = malloc(msg_size);
...@@ -855,8 +893,8 @@ void *forward_ws_to_ssh_server(void *arg) { ...@@ -855,8 +893,8 @@ void *forward_ws_to_ssh_server(void *arg) {
continue; continue;
} }
snprintf(message, msg_size, snprintf(message, msg_size,
"{\"type\":\"tunnel_response\",\"request_id\":\"%s\",\"data\":\"%s\"}", "{\"type\":\"tunnel_response\",\"request_id\":\"%s\",\"data\":\"%s\"}",
request_id, hex_data); request_id, hex_data);
if (!send_websocket_frame(ssl, message)) { if (!send_websocket_frame(ssl, message)) {
if (debug) { if (debug) {
...@@ -890,6 +928,14 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id ...@@ -890,6 +928,14 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
return; return;
} }
// Track received bytes for statistics
size_t hex_len = strlen(data_hex);
if (hex_len % 2 == 0) {
size_t data_len = hex_len / 2;
tunnel->total_bytes_received += data_len;
tunnel->bytes_last_period += data_len;
}
// Validate hex data length // Validate hex data length
size_t hex_len = strlen(data_hex); size_t hex_len = strlen(data_hex);
if (hex_len % 2 != 0) { if (hex_len % 2 != 0) {
...@@ -1086,6 +1132,122 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i ...@@ -1086,6 +1132,122 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
pthread_mutex_unlock(&tunnel_mutex); pthread_mutex_unlock(&tunnel_mutex);
} }
void handle_tunnel_keepalive(SSL *ssl, const char *request_id, unsigned long long total_bytes, double rate_bps, int debug) {
pthread_mutex_lock(&tunnel_mutex);
tunnel_t *tunnel = find_tunnel_by_request_id(request_id);
if (tunnel) {
tunnel->last_keepalive_received = time(NULL);
if (debug) {
printf("[DEBUG - Tunnel] Keep-alive received for tunnel %s: total=%llu bytes, rate=%.2f B/s\n",
request_id, total_bytes, rate_bps);
fflush(stdout);
}
// Send ACK response
char ack_msg[256];
snprintf(ack_msg, sizeof(ack_msg), "{\"type\":\"tunnel_keepalive_ack\",\"request_id\":\"%s\"}", request_id);
if (debug) {
printf("[DEBUG - WebSockets] Sending tunnel_keepalive_ack: %s\n", ack_msg);
fflush(stdout);
}
if (!send_websocket_frame(ssl, ack_msg)) {
if (debug) {
printf("[DEBUG - Tunnel] Failed to send keep-alive ACK for tunnel %s\n", request_id);
fflush(stdout);
}
}
} else {
if (debug) {
printf("[DEBUG - Tunnel] Keep-alive received for unknown tunnel %s\n", request_id);
fflush(stdout);
}
}
pthread_mutex_unlock(&tunnel_mutex);
}
void handle_tunnel_keepalive_ack(SSL *ssl __attribute__((unused)), const char *request_id, int debug) {
pthread_mutex_lock(&tunnel_mutex);
tunnel_t *tunnel = find_tunnel_by_request_id(request_id);
if (tunnel) {
if (debug) {
printf("[DEBUG - Tunnel] Keep-alive ACK received for tunnel %s\n", request_id);
fflush(stdout);
}
// ACK received, tunnel is alive
} else {
if (debug) {
printf("[DEBUG - Tunnel] Keep-alive ACK received for unknown tunnel %s\n", request_id);
fflush(stdout);
}
}
pthread_mutex_unlock(&tunnel_mutex);
}
void send_tunnel_keepalive(SSL *ssl, tunnel_t *tunnel, int debug) {
if (!tunnel || !tunnel->active) return;
time_t current_time = time(NULL);
// Reset stats every 30 seconds
if (current_time - tunnel->last_stats_reset >= 30) {
tunnel->bytes_last_period = 0;
tunnel->last_stats_reset = current_time;
}
// Calculate rate (bytes per second over last 30 seconds)
double rate_bps = 0.0;
if (current_time > tunnel->last_stats_reset) {
rate_bps = (double)tunnel->bytes_last_period / (current_time - tunnel->last_stats_reset);
}
// Send keep-alive message
char keepalive_msg[512];
unsigned long long total_bytes = tunnel->total_bytes_sent + tunnel->total_bytes_received;
snprintf(keepalive_msg, sizeof(keepalive_msg),
"{\"type\":\"tunnel_keepalive\",\"request_id\":\"%s\",\"total_bytes\":%llu,\"rate_bps\":%.2f}",
tunnel->request_id, total_bytes, rate_bps);
if (debug) {
printf("[DEBUG - Tunnel] Sending keep-alive for tunnel %s: %s\n", tunnel->request_id, keepalive_msg);
fflush(stdout);
}
if (send_websocket_frame(ssl, keepalive_msg)) {
tunnel->last_keepalive_sent = current_time;
} else if (debug) {
printf("[DEBUG - Tunnel] Failed to send keep-alive for tunnel %s\n", tunnel->request_id);
fflush(stdout);
}
}
void check_keepalive_timeouts(int debug) {
time_t current_time = time(NULL);
const int timeout_seconds = 190; // 3 * 30 + 10 seconds margin
pthread_mutex_lock(&tunnel_mutex);
for (int i = 0; i < active_tunnels_count; i++) {
tunnel_t *tunnel = active_tunnels[i];
if (tunnel && tunnel->active) {
// Check if we haven't received a keep-alive from the other side for too long
if (current_time - tunnel->last_keepalive_received > timeout_seconds) {
if (debug) {
printf("[DEBUG - Tunnel] Keep-alive timeout for tunnel %s (%ld seconds since last keep-alive)\n",
tunnel->request_id, current_time - tunnel->last_keepalive_received);
fflush(stdout);
}
// Send tunnel close and mark as broken
tunnel->active = 0;
tunnel->broken = 1;
send_tunnel_close(tunnel->ssl, tunnel->request_id, debug);
}
}
}
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) { 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 sockaddr_in server_addr;
struct hostent *he; struct hostent *he;
......
/* /*
* WebSocket SSH Library - Tunnel management * WSSSH Library - Tunnel management
* *
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me * Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
* *
...@@ -42,6 +42,14 @@ typedef struct { ...@@ -42,6 +42,14 @@ typedef struct {
frame_buffer_t *incoming_buffer; // Buffer for incoming data before connection is established frame_buffer_t *incoming_buffer; // Buffer for incoming data before connection is established
int server_version_sent; // Flag to indicate if server version was sent early int server_version_sent; // Flag to indicate if server version was sent early
pthread_t forward_thread; // Thread ID for the forwarding thread pthread_t forward_thread; // Thread ID for the forwarding thread
// Keep-alive statistics and timing
time_t last_keepalive_sent; // Last time we sent a keep-alive
time_t last_keepalive_received; // Last time we received a keep-alive
unsigned long long total_bytes_sent; // Total bytes sent through tunnel
unsigned long long total_bytes_received; // Total bytes received through tunnel
unsigned long long bytes_last_period; // Bytes transferred in last 30-second period
time_t last_stats_reset; // When we last reset the period stats
} tunnel_t; } tunnel_t;
// Service configuration structure // Service configuration structure
...@@ -90,6 +98,10 @@ void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, servic ...@@ -90,6 +98,10 @@ void handle_tunnel_request_with_service(SSL *ssl, const char *request_id, servic
void handle_tunnel_data(SSL *ssl, const char *request_id, const char *data_hex, 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 handle_tunnel_close(SSL *ssl, const char *request_id, int debug);
void send_tunnel_close(SSL *ssl, const char *request_id, int debug); void send_tunnel_close(SSL *ssl, const char *request_id, int debug);
void handle_tunnel_keepalive(SSL *ssl, const char *request_id, unsigned long long total_bytes, double rate_bps, int debug);
void handle_tunnel_keepalive_ack(SSL *ssl, const char *request_id, int debug);
void send_tunnel_keepalive(SSL *ssl, tunnel_t *tunnel, int debug);
void check_keepalive_timeouts(int debug);
void cleanup_tunnel(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 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, const char *tunnel_host); int setup_tunnel(const char *wssshd_host, int wssshd_port, const char *client_id, int local_port, int debug, int use_buffer, const char *tunnel_host);
......
/* /*
* WebSocket SSH Library - Shared utilities implementation * WSSSH Library - Shared utilities implementation
* *
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me * Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
* *
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include <linux/if.h> #include <linux/if.h>
#include <linux/route.h> #include <linux/route.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/time.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
...@@ -283,12 +284,12 @@ int find_available_port() { ...@@ -283,12 +284,12 @@ int find_available_port() {
} }
void generate_request_id(char *request_id, size_t size) { void generate_request_id(char *request_id, size_t size) {
static int seeded = 0; // Seed the random number generator with high-resolution time and process ID
if (!seeded) { // Include microseconds for better uniqueness
// Seed the random number generator with current time and process ID struct timeval tv;
srand((unsigned int)time(NULL) ^ (unsigned int)getpid()); gettimeofday(&tv, NULL);
seeded = 1; unsigned int seed = (unsigned int)(tv.tv_sec ^ tv.tv_usec ^ getpid());
} srand(seed);
const char charset[] = "0123456789abcdef"; const char charset[] = "0123456789abcdef";
for (size_t i = 0; i < size - 1; i++) { for (size_t i = 0; i < size - 1; i++) {
......
This diff is collapsed.
/* /*
* WebSocket SSH Tunnel (wsssht) - Main Program * WSSSH Tunnel (wsssht) - Main Program
* WebSocket tunnel setup tool for manual connections. * WebSocket tunnel setup tool for manual connections.
* *
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me * Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
...@@ -384,6 +384,14 @@ int main(int argc, char *argv[]) { ...@@ -384,6 +384,14 @@ int main(int argc, char *argv[]) {
static char frame_buffer[BUFFER_SIZE * 4]; static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0; static int frame_buffer_used = 0;
// Keep-alive message variables
time_t last_keepalive_time = time(NULL);
const int keepalive_interval = 30; // Send keep-alive every 30 seconds
// Keep-alive timeout check variables
time_t last_timeout_check = time(NULL);
const int timeout_check_interval = 10; // Check timeouts every 10 seconds
while (1) { while (1) {
// Get SSL fd with mutex protection // Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex); pthread_mutex_lock(&tunnel_mutex);
...@@ -474,6 +482,24 @@ int main(int argc, char *argv[]) { ...@@ -474,6 +482,24 @@ int main(int argc, char *argv[]) {
goto cleanup_and_exit; goto cleanup_and_exit;
} }
pthread_mutex_unlock(&tunnel_mutex); pthread_mutex_unlock(&tunnel_mutex);
// Send keep-alive messages if needed
time_t current_time = time(NULL);
if (current_time - last_keepalive_time >= keepalive_interval) {
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnel && active_tunnel->active) {
send_tunnel_keepalive(current_ssl, active_tunnel, config.debug);
}
pthread_mutex_unlock(&tunnel_mutex);
last_keepalive_time = current_time;
}
// Check for keep-alive timeouts
if (current_time - last_timeout_check >= timeout_check_interval) {
check_keepalive_timeouts(config.debug);
last_timeout_check = current_time;
}
continue; continue;
} }
...@@ -741,6 +767,63 @@ int main(int argc, char *argv[]) { ...@@ -741,6 +767,63 @@ int main(int argc, char *argv[]) {
} }
} }
} }
} else if (strstr(buffer, "tunnel_keepalive")) {
// Extract request_id, total_bytes, and rate_bps
char *id_start = strstr(buffer, "\"request_id\"");
char *total_start = strstr(buffer, "\"total_bytes\"");
char *rate_start = strstr(buffer, "\"rate_bps\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
unsigned long long total_bytes = 0;
double rate_bps = 0.0;
// Parse total_bytes
if (total_start) {
char *total_colon = strchr(total_start, ':');
if (total_colon) {
total_bytes = strtoull(total_colon + 1, NULL, 10);
}
}
// Parse rate_bps
if (rate_start) {
char *rate_colon = strchr(rate_start, ':');
if (rate_colon) {
rate_bps = strtod(rate_colon + 1, NULL);
}
}
handle_tunnel_keepalive(current_ssl, id_start, total_bytes, rate_bps, config.debug);
}
}
}
}
} else if (strstr(buffer, "tunnel_keepalive_ack")) {
// Extract request_id
char *id_start = strstr(buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
char *open_quote = strchr(colon, '"');
if (open_quote) {
id_start = open_quote + 1;
char *close_quote = strchr(id_start, '"');
if (close_quote) {
*close_quote = '\0';
handle_tunnel_keepalive_ack(current_ssl, id_start, config.debug);
}
}
}
}
} else { } else {
if (config.debug) { if (config.debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer); printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
......
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