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:
self.error_message = None
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):
"""Update tunnel status and timestamp"""
self.status = new_status
......@@ -113,7 +125,9 @@ class Tunnel:
'tool_private_ip': self.tool_private_ip,
'tool_public_port': self.tool_public_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):
......
......@@ -22,6 +22,8 @@ TUNNEL_ACK_MSG = '{"type": "tunnel_ack", "request_id": "%s"}'
TUNNEL_CLOSE_MSG = '{"type": "tunnel_close", "request_id": "%s"}'
TUNNEL_REQUEST_MSG = '{"type": "tunnel_request", "request_id": "%s"}'
TUNNEL_ERROR_MSG = '{"type": "tunnel_error", "request_id": "%s", "error": "%s"}'
TUNNEL_KEEPALIVE_MSG = '{"type": "tunnel_keepalive", "request_id": "%s", "total_bytes": %llu, "rate_bps": %.2f}'
TUNNEL_KEEPALIVE_ACK_MSG = '{"type": "tunnel_keepalive_ack", "request_id": "%s"}'
REGISTERED_MSG = '{"type": "registered", "id": "%s"}'
REGISTRATION_ERROR_MSG = '{"type": "registration_error", "error": "%s"}'
SERVER_SHUTDOWN_MSG = '{"type": "server_shutdown", "message": "Server is shutting down"}'
......@@ -45,6 +47,66 @@ def cleanup_expired_clients():
del clients[client_id]
def check_keepalive_timeouts():
"""Check for tunnels that haven't received keep-alive messages from both endpoints for too long"""
current_time = time.time()
timeout_seconds = 220 # 4 * 30 + 10 seconds margin (4 lost messages + buffer)
tunnels_to_close = []
for request_id, tunnel in active_tunnels.items():
if tunnel.status == TunnelStatus.ACTIVE:
# Check both endpoints for keep-alive timeout
client_timeout = current_time - tunnel.last_keepalive_from_client > timeout_seconds
tool_timeout = current_time - tunnel.last_keepalive_from_tool > timeout_seconds
if client_timeout or tool_timeout:
# Determine which endpoint(s) timed out
timeout_reason = ""
if client_timeout and tool_timeout:
timeout_reason = "both endpoints"
elif client_timeout:
timeout_reason = "client (wssshc) endpoint"
else:
timeout_reason = "tool (wsssht/wsscp) endpoint"
if debug:
print(f"[DEBUG] [WebSocket] Keep-alive timeout for tunnel {request_id} from {timeout_reason}")
print(f"[DEBUG] [WebSocket] Client last keep-alive: {current_time - tunnel.last_keepalive_from_client:.1f} seconds ago")
print(f"[DEBUG] [WebSocket] Tool last keep-alive: {current_time - tunnel.last_keepalive_from_tool:.1f} seconds ago")
tunnels_to_close.append((request_id, timeout_reason))
# Close timed-out tunnels
for request_id, timeout_reason in tunnels_to_close:
tunnel = active_tunnels.get(request_id)
if tunnel:
# Update tunnel status to closing
tunnel.update_status(TunnelStatus.CLOSING, f"Keep-alive timeout from {timeout_reason}")
# Send close messages to both sides
try:
close_msg = TUNNEL_CLOSE_MSG % request_id
if debug: print(f"[DEBUG] [WebSocket] Sending tunnel close due to timeout: {close_msg}")
# Send to client (wssshc)
if tunnel.client_ws:
tunnel.client_ws.send(close_msg)
# Send to wsssh/wsscp
if tunnel.wsssh_ws:
tunnel.wsssh_ws.send(close_msg)
except Exception as e:
if debug: print(f"[DEBUG] [WebSocket] Failed to send timeout close message: {e}")
# Clean up tunnel
tunnel.update_status(TunnelStatus.CLOSED)
del active_tunnels[request_id]
if not debug:
print(f"[EVENT] Tunnel {request_id} closed due to keep-alive timeout from {timeout_reason}")
def print_status():
"""Print minimal status information when not in debug mode"""
if debug_flag:
......@@ -218,6 +280,20 @@ async def handle_websocket(websocket, path=None, *, server_password=None, debug_
if tunnel.status == TunnelStatus.ACTIVE:
client_info = clients.get(tunnel.client_id)
if client_info and client_info['status'] == 'active':
# Track bytes sent for statistics
data_str = data['data']
if data_str:
# Approximate byte count (base64 encoded data)
import base64
try:
decoded = base64.b64decode(data_str)
tunnel.total_bytes_sent += len(decoded)
tunnel.bytes_last_period += len(decoded)
except:
# If decoding fails, estimate based on string length
tunnel.total_bytes_sent += len(data_str) * 3 // 4 # Rough base64 decode estimate
tunnel.bytes_last_period += len(data_str) * 3 // 4
# Use pre-formatted JSON template for better performance
try:
await tunnel.client_ws.send(TUNNEL_DATA_MSG % (request_id, data['data']))
......@@ -230,6 +306,20 @@ async def handle_websocket(websocket, path=None, *, server_password=None, debug_
request_id = data['request_id']
tunnel = active_tunnels.get(request_id)
if tunnel and tunnel.status == TunnelStatus.ACTIVE:
# Track bytes received for statistics
data_str = data['data']
if data_str:
# Approximate byte count (base64 encoded data)
import base64
try:
decoded = base64.b64decode(data_str)
tunnel.total_bytes_received += len(decoded)
tunnel.bytes_last_period += len(decoded)
except:
# If decoding fails, estimate based on string length
tunnel.total_bytes_received += len(data_str) * 3 // 4 # Rough base64 decode estimate
tunnel.bytes_last_period += len(data_str) * 3 // 4
try:
await tunnel.wsssh_ws.send(TUNNEL_DATA_MSG % (request_id, data['data']))
except Exception:
......@@ -262,6 +352,80 @@ async def handle_websocket(websocket, path=None, *, server_password=None, debug_
print(f"[DEBUG] Tunnel object: {tunnel}")
else:
print(f"[EVENT] Tunnel {request_id} closed")
elif data.get('type') == 'tunnel_keepalive':
request_id = data['request_id']
total_bytes = data.get('total_bytes', 0)
rate_bps = data.get('rate_bps', 0.0)
tunnel = active_tunnels.get(request_id)
if tunnel:
# Determine which endpoint sent the message
current_time = time.time()
if websocket == tunnel.client_ws:
# Message from wssshc (client)
tunnel.last_keepalive_from_client = current_time
endpoint_name = "client (wssshc)"
forward_ws = tunnel.wsssh_ws
forward_name = "wsssh"
elif websocket == tunnel.wsssh_ws:
# Message from wsssht/wsscp (tool)
tunnel.last_keepalive_from_tool = current_time
endpoint_name = "tool (wsssht/wsscp)"
forward_ws = tunnel.client_ws
forward_name = "client"
else:
if debug_flag: print(f"[DEBUG] [WebSocket] Keep-alive from unknown websocket for tunnel {request_id}")
continue
# Log keep-alive statistics
if debug_flag:
print(f"[DEBUG] [WebSocket] Keep-alive received from {endpoint_name} for tunnel {request_id}: total={total_bytes} bytes, rate={rate_bps:.2f} B/s")
# Forward keep-alive to the other side
try:
keepalive_msg = TUNNEL_KEEPALIVE_MSG % (request_id, total_bytes, rate_bps)
if debug_flag: print(f"[DEBUG] [WebSocket] Forwarding keep-alive to {forward_name}: {keepalive_msg}")
await forward_ws.send(keepalive_msg)
except Exception as e:
if debug_flag: print(f"[DEBUG] [WebSocket] Failed to forward keep-alive: {e}")
# Send ACK response back to sender
try:
ack_msg = TUNNEL_KEEPALIVE_ACK_MSG % request_id
if debug_flag: print(f"[DEBUG] [WebSocket] Sending keep-alive ACK: {ack_msg}")
await websocket.send(ack_msg)
except Exception as e:
if debug_flag: print(f"[DEBUG] [WebSocket] Failed to send keep-alive ACK: {e}")
else:
if debug_flag: print(f"[DEBUG] [WebSocket] Keep-alive received for unknown tunnel {request_id}")
elif data.get('type') == 'tunnel_keepalive_ack':
request_id = data['request_id']
tunnel = active_tunnels.get(request_id)
if tunnel:
# Determine which endpoint sent the ACK
if websocket == tunnel.client_ws:
endpoint_name = "client (wssshc)"
forward_ws = tunnel.wsssh_ws
forward_name = "wsssh"
elif websocket == tunnel.wsssh_ws:
endpoint_name = "tool (wsssht/wsscp)"
forward_ws = tunnel.client_ws
forward_name = "client"
else:
if debug_flag: print(f"[DEBUG] [WebSocket] Keep-alive ACK from unknown websocket for tunnel {request_id}")
continue
if debug_flag: print(f"[DEBUG] [WebSocket] Keep-alive ACK received from {endpoint_name} for tunnel {request_id}")
# Forward ACK to the other side
try:
ack_msg = TUNNEL_KEEPALIVE_ACK_MSG % request_id
if debug_flag: print(f"[DEBUG] [WebSocket] Forwarding keep-alive ACK to {forward_name}: {ack_msg}")
await forward_ws.send(ack_msg)
except Exception as e:
if debug_flag: print(f"[DEBUG] [WebSocket] Failed to forward keep-alive ACK: {e}")
else:
if debug_flag: print(f"[DEBUG] [WebSocket] Keep-alive ACK received for unknown tunnel {request_id}")
except websockets.exceptions.ConnectionClosed:
# Mark client as disconnected instead of removing immediately
disconnected_client = None
......@@ -296,6 +460,7 @@ async def cleanup_task(debug_flag=False):
# Use moderate sleep intervals for cleanup
await asyncio.sleep(5) # Run every 5 seconds
cleanup_expired_clients()
check_keepalive_timeouts()
# Print status every 60 seconds (12 iterations)
current_time = time.time()
......
......@@ -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->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
if (!add_tunnel(new_tunnel)) {
if (target_sock >= 0) close(target_sock);
......@@ -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->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
if (!add_tunnel(new_tunnel)) {
if (target_sock >= 0) close(target_sock);
......@@ -708,6 +726,16 @@ void *forward_tcp_to_ws(void *arg) {
}
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
size_t msg_size = strlen("{\"type\":\"tunnel_data\",\"request_id\":\"\",\"data\":\"\"}") + strlen(request_id) + actual_hex_len + 1;
char *message = malloc(msg_size);
......@@ -843,6 +871,16 @@ void *forward_ws_to_ssh_server(void *arg) {
}
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)
size_t msg_size = strlen("{\"type\":\"tunnel_response\",\"request_id\":\"\",\"data\":\"\"}") + strlen(request_id) + actual_hex_len + 1;
char *message = malloc(msg_size);
......@@ -890,6 +928,14 @@ void handle_tunnel_data(SSL *ssl __attribute__((unused)), const char *request_id
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
size_t hex_len = strlen(data_hex);
if (hex_len % 2 != 0) {
......@@ -1086,6 +1132,122 @@ void handle_tunnel_close(SSL *ssl __attribute__((unused)), const char *request_i
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) {
struct sockaddr_in server_addr;
struct hostent *he;
......
/*
* WebSocket SSH Library - Tunnel management
* WSSSH Library - Tunnel management
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
......@@ -42,6 +42,14 @@ typedef struct {
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
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;
// Service configuration structure
......@@ -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_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);
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);
......
/*
* WebSocket SSH Library - Shared utilities implementation
* WSSSH Library - Shared utilities implementation
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
*
......@@ -27,6 +27,7 @@
#include <linux/if.h>
#include <linux/route.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
......@@ -283,12 +284,12 @@ int find_available_port() {
}
void generate_request_id(char *request_id, size_t size) {
static int seeded = 0;
if (!seeded) {
// Seed the random number generator with current time and process ID
srand((unsigned int)time(NULL) ^ (unsigned int)getpid());
seeded = 1;
}
// Seed the random number generator with high-resolution time and process ID
// Include microseconds for better uniqueness
struct timeval tv;
gettimeofday(&tv, NULL);
unsigned int seed = (unsigned int)(tv.tv_sec ^ tv.tv_usec ^ getpid());
srand(seed);
const char charset[] = "0123456789abcdef";
for (size_t i = 0; i < size - 1; i++) {
......
......@@ -903,6 +903,14 @@ int connect_to_server(const wssshc_config_t *config) {
time_t last_ping_time = time(NULL);
const int ping_interval = 15; // Send ping every 15 seconds
// Keep-alive message variables
time_t last_keepalive_time = time(NULL);
const int keepalive_interval = 30; // Send keep-alive every 30 seconds
// Tunnel list debug output variables
time_t last_tunnel_list_time = time(NULL);
const int tunnel_list_interval = 60; // Show tunnel list every 60 seconds
while (1) {
// Check for SIGINT
if (sigint_received) {
......@@ -930,6 +938,54 @@ int connect_to_server(const wssshc_config_t *config) {
}
}
// Send tunnel keep-alive messages if needed
if (current_time - last_keepalive_time >= keepalive_interval) {
// Send keep-alive for all active tunnels
pthread_mutex_lock(&tunnel_mutex);
for (int i = 0; i < active_tunnels_count; i++) {
tunnel_t *tunnel = active_tunnels[i];
if (tunnel && tunnel->active) {
send_tunnel_keepalive(ssl, tunnel, config->debug);
}
}
pthread_mutex_unlock(&tunnel_mutex);
last_keepalive_time = current_time;
}
// Check for keep-alive timeouts
check_keepalive_timeouts(config->debug);
// Show tunnel list periodically in debug mode
if (config->debug && current_time - last_tunnel_list_time >= tunnel_list_interval) {
printf("[DEBUG - Tunnel] Active tunnels list:\n");
pthread_mutex_lock(&tunnel_mutex);
if (active_tunnels_count == 0) {
printf("[DEBUG - Tunnel] No active tunnels\n");
} else {
for (int i = 0; i < active_tunnels_count; i++) {
tunnel_t *tunnel = active_tunnels[i];
if (tunnel && tunnel->active) {
// 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);
}
printf("[DEBUG - Tunnel] Tunnel %s:\n", tunnel->request_id);
printf("[DEBUG - Tunnel] Total sent: %llu bytes\n", tunnel->total_bytes_sent);
printf("[DEBUG - Tunnel] Total received: %llu bytes\n", tunnel->total_bytes_received);
printf("[DEBUG - Tunnel] Current rate: %.2f B/s\n", rate_bps);
printf("[DEBUG - Tunnel] Last keep-alive sent: %ld seconds ago\n", current_time - tunnel->last_keepalive_sent);
printf("[DEBUG - Tunnel] Last keep-alive received: %ld seconds ago\n", current_time - tunnel->last_keepalive_received);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
printf("[DEBUG - Tunnel] End of tunnel list\n");
fflush(stdout);
last_tunnel_list_time = current_time;
}
// Always try to read more data if there's space, even if we have a complete frame
// This ensures we can accumulate data for very large frames
if ((size_t)frame_buffer_used < sizeof(frame_buffer)) {
......@@ -1090,11 +1146,30 @@ int connect_to_server(const wssshc_config_t *config) {
}
// Check if this is a tunnel_data message to suppress verbose logging
int is_tunnel_data = (strstr(buffer, "\"type\":\"tunnel_data\"") != NULL);
int is_tunnel_data = (strstr(buffer, "tunnel_data") != NULL);
if (config->debug) {
if (is_tunnel_data) {
// Extract data size for tunnel_data messages
// Extract request_id and data size for tunnel_data messages
char *request_id_start = strstr(buffer, "\"request_id\"");
char request_id[256] = "...";
if (request_id_start) {
char *colon = strchr(request_id_start, ':');
if (colon) {
char *quote = strchr(colon, '"');
if (quote) {
request_id_start = quote + 1;
char *end_quote = strchr(request_id_start, '"');
if (end_quote) {
size_t len = end_quote - request_id_start;
if (len < sizeof(request_id) - 1) {
memcpy(request_id, request_id_start, len);
request_id[len] = '\0';
}
}
}
}
}
char *data_start = strstr(buffer, "\"data\"");
size_t data_size = 0;
if (data_start) {
......@@ -1110,7 +1185,7 @@ int connect_to_server(const wssshc_config_t *config) {
}
}
}
printf("[DEBUG - WebSockets] Received message: {\"type\":\"tunnel_data\", \"request_id\":\"...\", \"data\":\"<size: %zu bytes>\"}\n", data_size);
printf("[DEBUG - WebSockets] Received message: {\"type\":\"tunnel_data\", \"request_id\":\"%s\", \"data\":\"<size: %zu bytes>\"}\n", request_id, data_size);
} else {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
}
......@@ -1120,7 +1195,26 @@ int connect_to_server(const wssshc_config_t *config) {
// Handle message
if (config->debug) {
if (is_tunnel_data) {
// Extract data size for tunnel_data messages
// Extract request_id and data size for tunnel_data messages
char *request_id_start = strstr(buffer, "\"request_id\"");
char request_id[256] = "...";
if (request_id_start) {
char *colon = strchr(request_id_start, ':');
if (colon) {
char *quote = strchr(colon, '"');
if (quote) {
request_id_start = quote + 1;
char *end_quote = strchr(request_id_start, '"');
if (end_quote) {
size_t len = end_quote - request_id_start;
if (len < sizeof(request_id) - 1) {
memcpy(request_id, request_id_start, len);
request_id[len] = '\0';
}
}
}
}
}
char *data_start = strstr(buffer, "\"data\"");
size_t data_size = 0;
if (data_start) {
......@@ -1136,7 +1230,7 @@ int connect_to_server(const wssshc_config_t *config) {
}
}
}
printf("[DEBUG - WebSockets] Processing message: {\"type\":\"tunnel_data\", \"request_id\":\"...\", \"data\":\"<size: %zu bytes>\"}\n", data_size);
printf("[DEBUG - WebSockets] Processing message: {\"type\":\"tunnel_data\", \"request_id\":\"%s\", \"data\":\"<size: %zu bytes>\"}\n", request_id, data_size);
} else {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
}
......@@ -1269,6 +1363,63 @@ int connect_to_server(const wssshc_config_t *config) {
}
}
}
} 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(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(ssl, id_start, config->debug);
}
}
}
}
}
}
......
/*
* WebSocket SSH Tunnel (wsssht) - Main Program
* WSSSH Tunnel (wsssht) - Main Program
* WebSocket tunnel setup tool for manual connections.
*
* Copyright (C) 2024 Stefy Lanza <stefy@nexlab.net> and SexHack.me
......@@ -384,6 +384,14 @@ int main(int argc, char *argv[]) {
static char frame_buffer[BUFFER_SIZE * 4];
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) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
......@@ -474,6 +482,24 @@ int main(int argc, char *argv[]) {
goto cleanup_and_exit;
}
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;
}
......@@ -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 {
if (config.debug) {
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