Fix concurrent connection issues in wsssht daemon mode

- Implement thread-based concurrent connection handling
- Fix segmentation fault caused by shared static variables
- Each connection now gets its own WebSocket tunnel and resources
- Replace global tunnel management with per-thread instances
- Add proper cleanup for individual connection threads
- Allow multiple SSH sessions to connect simultaneously to the same port
parent 91fe8573
......@@ -607,292 +607,319 @@ int run_script_mode(wsssh_config_t *config, const char *client_id, const char *w
return 0;
}
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Daemon mode: Lazy tunnel establishment - bind port immediately, establish tunnel on connection
typedef struct {
wsssh_config_t *config;
const char *client_id;
const char *wssshd_host;
int wssshd_port;
int accepted_sock;
int local_port;
} connection_handler_args_t;
// Connection handler function for each accepted connection
void *handle_connection(void *arg) {
connection_handler_args_t *args = (connection_handler_args_t *)arg;
wsssh_config_t *config = args->config;
const char *client_id = args->client_id;
const char *wssshd_host = args->wssshd_host;
int wssshd_port = args->wssshd_port;
int accepted_sock = args->accepted_sock;
int local_port = args->local_port;
// Free the args structure
free(args);
if (config->debug) {
printf("[DEBUG] Starting daemon mode with lazy tunnel establishment\n");
printf("[DEBUG] [Thread %ld] Handling connection on port %d\n", pthread_self(), local_port);
fflush(stdout);
}
// Find available local port
int local_port = config->local_port ? atoi(config->local_port) : find_available_port();
if (local_port == 0) {
fprintf(stderr, "Error: Could not find available local port\n");
return 1;
// Create a new tunnel instance for this connection
tunnel_t *new_tunnel = malloc(sizeof(tunnel_t));
if (!new_tunnel) {
perror("Memory allocation failed for tunnel");
close(accepted_sock);
return NULL;
}
if (config->debug) {
printf("[DEBUG] Using local port: %d\n", local_port);
fflush(stdout);
// Initialize tunnel structure
generate_request_id(new_tunnel->request_id, sizeof(new_tunnel->request_id));
new_tunnel->sock = -1; // wsssh doesn't connect to remote server
new_tunnel->local_sock = accepted_sock;
new_tunnel->active = 1;
new_tunnel->broken = 0;
new_tunnel->ssl = NULL;
new_tunnel->outgoing_buffer = NULL;
new_tunnel->incoming_buffer = frame_buffer_init();
new_tunnel->server_version_sent = 0;
if (!new_tunnel->incoming_buffer) {
perror("Failed to initialize incoming buffer");
free(new_tunnel);
close(accepted_sock);
return NULL;
}
// Create listening socket
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
return 1;
// Establish WebSocket connection for this tunnel
struct sockaddr_in server_addr;
struct hostent *he;
int ws_sock;
SSL_CTX *ssl_ctx;
SSL *ws_ssl;
// Resolve hostname
if ((he = gethostbyname(wssshd_host)) == NULL) {
herror("gethostbyname");
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
return NULL;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
// Use specified tunnel_host or default to 127.0.0.1
if (config->tunnel_host && strcmp(config->tunnel_host, "127.0.0.1") != 0) {
struct hostent *tunnel_he;
if ((tunnel_he = gethostbyname(config->tunnel_host)) == NULL) {
if (config->debug) {
fprintf(stderr, "[DEBUG] Failed to resolve tunnel_host '%s', using 127.0.0.1\n", config->tunnel_host);
}
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
} else {
local_addr.sin_addr = *((struct in_addr *)tunnel_he->h_addr);
if (config->debug) {
printf("[DEBUG] Binding tunnel to %s:%d\n", config->tunnel_host, local_port);
fflush(stdout);
}
}
} else {
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (config->debug) {
printf("[DEBUG] Binding tunnel to 127.0.0.1:%d\n", local_port);
fflush(stdout);
}
// Create socket
if ((ws_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("WebSocket socket creation failed");
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
return NULL;
}
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
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(ws_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("WebSocket connection failed");
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
return 1;
// Create SSL context and connection
ssl_ctx = create_ssl_context();
if (!ssl_ctx) {
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
if (config->mode != MODE_SILENT) {
printf("Daemon mode: Listening on port %d, waiting for connection...\n", local_port);
printf("Tunnel will be established on first connection attempt.\n");
fflush(stdout);
ws_ssl = create_ssl_connection(ssl_ctx, ws_sock, config->debug);
if (!ws_ssl) {
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
// Daemon mode main loop - accept connections and establish tunnels
while (1) {
// Check for SIGINT
if (sigint_received) {
if (config->debug) {
printf("[DEBUG] SIGINT received in daemon mode, exiting\n");
fflush(stdout);
}
break;
}
// Wait for connection with timeout
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(listen_sock, &readfds);
tv.tv_sec = 1; // 1 second timeout to check for signals
tv.tv_usec = 0;
int retval = select(listen_sock + 1, &readfds, NULL, NULL, &tv);
if (retval < 0) {
if (errno == EINTR) {
// Interrupted by signal, continue to check sigint_received
continue;
}
perror("Select failed in daemon mode");
// In daemon mode, don't exit on select errors, wait a bit and retry
sleep(1);
continue;
} else if (retval == 0) {
// Timeout, continue loop to check signals
continue;
}
// Perform WebSocket handshake
if (!websocket_handshake(ws_ssl, wssshd_host, wssshd_port, "/", config->debug)) {
fprintf(stderr, "WebSocket handshake failed\n");
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
// Accept connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int accepted_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len);
if (accepted_sock < 0) {
if (errno == EINTR) {
continue;
}
perror("Local accept failed");
continue; // Don't exit on accept failure, keep listening
}
// Send tunnel request
char tunnel_request_msg[1024];
char *expanded_tunnel = expand_transport_list("any", 0);
char *expanded_tunnel_control = expand_transport_list("any", 1);
char *best_tunnel = select_best_transport(expanded_tunnel);
char *best_tunnel_control = select_best_transport(expanded_tunnel_control);
snprintf(tunnel_request_msg, sizeof(tunnel_request_msg),
"{\"type\":\"tunnel_request\",\"client_id\":\"%s\",\"request_id\":\"%s\",\"tunnel\":\"%s\",\"tunnel_control\":\"%s\",\"service\":\"ssh\"}",
client_id, new_tunnel->request_id,
best_tunnel ? best_tunnel : expanded_tunnel,
best_tunnel_control ? best_tunnel_control : expanded_tunnel_control);
if (!send_websocket_frame(ws_ssl, tunnel_request_msg)) {
free(expanded_tunnel);
free(expanded_tunnel_control);
if (best_tunnel) free(best_tunnel);
if (best_tunnel_control) free(best_tunnel_control);
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
if (config->debug) {
printf("[DEBUG] Connection received on port %d, establishing tunnel...\n", local_port);
fflush(stdout);
}
free(expanded_tunnel);
free(expanded_tunnel_control);
if (best_tunnel) free(best_tunnel);
if (best_tunnel_control) free(best_tunnel_control);
// Read acknowledgment
char ack_buffer[BUFFER_SIZE];
int ack_bytes_read = SSL_read(ws_ssl, ack_buffer, sizeof(ack_buffer));
if (ack_bytes_read <= 0) {
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
// Now establish the tunnel for this connection
// In daemon mode, use port 0 to let setup_tunnel find an available port for WebSocket setup,
// but we'll use our already accepted socket for the local connection
int tunnel_sock = setup_tunnel(wssshd_host, wssshd_port, client_id, 0, config->debug, 0, config->tunnel_host);
if (tunnel_sock < 0) {
fprintf(stderr, "Failed to establish tunnel for connection\n");
close(accepted_sock);
continue; // Don't exit, keep listening for new connections
}
// Close the dummy listening socket created by setup_tunnel
close(tunnel_sock);
// Parse WebSocket frame
char *payload;
int payload_len;
if (!parse_websocket_frame(ack_buffer, ack_bytes_read, &payload, &payload_len)) {
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
// Print tunnel information (unless silent mode)
if (config->mode != MODE_SILENT) {
printf("\n");
printf("========================================\n");
printf(" WEBSSH TUNNEL READY\n");
printf("========================================\n");
printf("Tunnel established successfully!\n");
printf("Local port: %d\n", local_port);
printf("Target: %s@%s\n", client_id, wssshd_host);
printf("\n");
printf("Connect manually using one of these commands:\n");
printf("\n");
printf(" Telnet:\n");
printf(" telnet localhost %d\n", local_port);
printf("\n");
printf(" Netcat:\n");
printf(" nc localhost %d\n", local_port);
printf("\n");
printf(" SSH (if connecting to SSH server):\n");
printf(" ssh -p %d user@localhost\n", local_port);
printf("\n");
printf(" SCP (if connecting to SSH server):\n");
printf(" scp -P %d user@localhost:/remote/path ./local/path\n", local_port);
printf("\n");
printf(" Any TCP client:\n");
printf(" Connect to localhost:%d\n", local_port);
printf("\n");
printf("Press Ctrl+C to close the tunnel and exit.\n");
printf("========================================\n");
printf("\n");
// Check for tunnel acknowledgment
payload[payload_len] = '\0';
if (strstr(payload, "tunnel_ack") == NULL) {
fprintf(stderr, "Tunnel request denied: %s\n", payload);
SSL_free(ws_ssl);
SSL_CTX_free(ssl_ctx);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
close(accepted_sock);
close(ws_sock);
return NULL;
}
// Continue with normal tunnel operation
// Set the accepted socket with mutex protection
// Success - set up the tunnel
new_tunnel->ssl = ws_ssl;
SSL_CTX_free(ssl_ctx); // SSL context no longer needed
// Add tunnel to the global array
pthread_mutex_lock(&tunnel_mutex);
active_tunnel->local_sock = accepted_sock;
if (!add_tunnel(new_tunnel)) {
SSL_free(ws_ssl);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
pthread_mutex_unlock(&tunnel_mutex);
close(accepted_sock);
close(ws_sock);
return NULL;
}
pthread_mutex_unlock(&tunnel_mutex);
// Send any buffered data to the client immediately
if (active_tunnel->incoming_buffer && active_tunnel->incoming_buffer->used > 0) {
if (new_tunnel->incoming_buffer && new_tunnel->incoming_buffer->used > 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Sending %zu bytes of buffered server response to client\n", active_tunnel->incoming_buffer->used);
printf("[DEBUG - Tunnel] [Thread %ld] Sending %zu bytes of buffered server response to client\n", pthread_self(), new_tunnel->incoming_buffer->used);
fflush(stdout);
}
ssize_t sent = send(accepted_sock, active_tunnel->incoming_buffer->buffer, active_tunnel->incoming_buffer->used, 0);
ssize_t sent = send(accepted_sock, new_tunnel->incoming_buffer->buffer, new_tunnel->incoming_buffer->used, 0);
if (sent > 0) {
frame_buffer_consume(active_tunnel->incoming_buffer, sent);
frame_buffer_consume(new_tunnel->incoming_buffer, sent);
if (config->debug) {
printf("[DEBUG] Sent %zd bytes of buffered server response to client\n", sent);
printf("[DEBUG] [Thread %ld] Sent %zd bytes of buffered server response to client\n", pthread_self(), sent);
fflush(stdout);
}
}
}
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Local connection accepted! Starting data forwarding...\n");
printf("[DEBUG - Tunnel] [Thread %ld] Local connection accepted! Starting data forwarding...\n", pthread_self());
fflush(stdout);
}
// Get initial SSL connection for thread
pthread_mutex_lock(&tunnel_mutex);
SSL *current_ssl = active_tunnel ? active_tunnel->ssl : NULL;
pthread_mutex_unlock(&tunnel_mutex);
SSL *current_ssl = new_tunnel->ssl;
// Start forwarding thread
thread_args_t *thread_args = malloc(sizeof(thread_args_t));
if (!thread_args) {
perror("Memory allocation failed for thread args");
close(active_tunnel->local_sock);
free(active_tunnel);
active_tunnel = NULL;
pthread_mutex_destroy(&tunnel_mutex);
return 1;
close(new_tunnel->local_sock);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
return NULL;
}
thread_args->ssl = current_ssl;
thread_args->tunnel = active_tunnel;
thread_args->tunnel = new_tunnel;
thread_args->debug = config->debug;
pthread_t thread;
pthread_create(&thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(thread);
pthread_t forward_thread;
pthread_create(&forward_thread, NULL, forward_tcp_to_ws, thread_args);
pthread_detach(forward_thread);
// Main tunnel loop - handle WebSocket messages
char buffer[BUFFER_SIZE];
int bytes_read;
char ws_buffer[BUFFER_SIZE];
int ws_bytes_read;
fd_set tunnel_readfds;
struct timeval tunnel_tv;
int tunnel_broken = 0;
// Frame accumulation buffer for handling partial WebSocket frames
static char frame_buffer[BUFFER_SIZE * 4];
static int frame_buffer_used = 0;
char frame_buffer[BUFFER_SIZE * 4];
int frame_buffer_used = 0;
while (1) {
// Get SSL fd with mutex protection
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
if (active_tunnel && active_tunnel->broken) {
pthread_mutex_unlock(&tunnel_mutex);
// Check if tunnel is still active (no mutex needed since this is our private tunnel)
if (!new_tunnel || !new_tunnel->active) {
if (new_tunnel && new_tunnel->broken) {
goto cleanup_and_exit;
} else {
// normal closure
pthread_mutex_unlock(&tunnel_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel is no longer active, exiting main loop\n");
printf("[DEBUG - Tunnel] [Thread %ld] Tunnel is no longer active, exiting main loop\n", pthread_self());
fflush(stdout);
}
break;
}
}
int ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
int ssl_fd = SSL_get_fd(new_tunnel->ssl);
current_ssl = new_tunnel->ssl;
// Check if local socket is still valid
if (active_tunnel->local_sock < 0) {
if (new_tunnel->local_sock < 0) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket is invalid, tunnel broken\n");
printf("[DEBUG - Tunnel] [Thread %ld] Local socket is invalid, tunnel broken\n", pthread_self());
fflush(stdout);
}
active_tunnel->broken = 1;
new_tunnel->broken = 1;
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to invalid local socket...\n");
printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to invalid local socket...\n", pthread_self());
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit;
}
// Check if the local socket connection is broken
char test_buf[1];
int result = recv(active_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
int result = recv(new_tunnel->local_sock, test_buf, 1, MSG_PEEK | MSG_DONTWAIT);
if (result == 0 || (result < 0 && (errno == ECONNRESET || errno == EPIPE || errno == EBADF))) {
if (config->debug) {
printf("[DEBUG - Tunnel] Local socket connection is broken (errno=%d), sending tunnel_close\n", errno);
printf("[DEBUG - Tunnel] [Thread %ld] Local socket connection is broken (errno=%d), sending tunnel_close\n", pthread_self(), errno);
fflush(stdout);
}
active_tunnel->broken = 1;
new_tunnel->broken = 1;
// Send tunnel_close notification
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
pthread_mutex_unlock(&tunnel_mutex);
send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
// Use select to wait for data on SSL socket with timeout
FD_ZERO(&tunnel_readfds);
FD_SET(ssl_fd, &tunnel_readfds);
......@@ -908,24 +935,21 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to select failure...\n");
printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to select failure...\n", pthread_self());
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (retval == 0) {
// Timeout, check if tunnel became inactive
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel || !active_tunnel->active) {
pthread_mutex_unlock(&tunnel_mutex);
if (!new_tunnel || !new_tunnel->active) {
if (config->debug) {
printf("[DEBUG - Tunnel] Tunnel became inactive during timeout, exiting\n");
printf("[DEBUG - Tunnel] [Thread %ld] Tunnel became inactive during timeout, exiting\n", pthread_self());
fflush(stdout);
}
goto cleanup_and_exit;
}
pthread_mutex_unlock(&tunnel_mutex);
continue;
}
......@@ -935,10 +959,10 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
// Validate SSL connection state
if (SSL_get_shutdown(current_ssl) & SSL_RECEIVED_SHUTDOWN) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL connection has received shutdown\n");
printf("[DEBUG - WebSockets] [Thread %ld] SSL connection has received shutdown\n", pthread_self());
fflush(stdout);
}
cleanup_tunnel(config->debug);
new_tunnel->active = 0;
break;
}
......@@ -958,29 +982,29 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
perror("[DEBUG - WebSockets] select failed");
fflush(stdout);
}
cleanup_tunnel(config->debug);
new_tunnel->active = 0;
break;
} else if (select_result == 0) {
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read timeout\n");
printf("[DEBUG - WebSockets] [Thread %ld] SSL read timeout\n", pthread_self());
fflush(stdout);
}
continue;
}
bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (bytes_read <= 0) {
if (bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, bytes_read);
ws_bytes_read = SSL_read(current_ssl, frame_buffer + frame_buffer_used, sizeof(frame_buffer) - frame_buffer_used);
if (ws_bytes_read <= 0) {
if (ws_bytes_read < 0) {
int ssl_error = SSL_get_error(current_ssl, ws_bytes_read);
if (config->debug) {
printf("[DEBUG - WebSockets] SSL read error: %d\n", ssl_error);
printf("[DEBUG - WebSockets] [Thread %ld] SSL read error: %d\n", pthread_self(), ssl_error);
fflush(stdout);
}
// Handle transient SSL errors
if (ssl_error == SSL_ERROR_WANT_READ || ssl_error == SSL_ERROR_WANT_WRITE) {
if (config->debug) {
printf("[DEBUG - WebSockets] Transient SSL error, retrying...\n");
printf("[DEBUG - WebSockets] [Thread %ld] Transient SSL error, retrying...\n", pthread_self());
fflush(stdout);
}
usleep(10000);
......@@ -991,19 +1015,19 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
char error_buf[256];
ERR_error_string_n(ssl_error, error_buf, sizeof(error_buf));
if (config->debug) {
printf("[DEBUG - WebSockets] SSL error details: %s\n", error_buf);
printf("[DEBUG - WebSockets] [Thread %ld] SSL error details: %s\n", pthread_self(), error_buf);
fflush(stdout);
}
fprintf(stderr, "SSL read error (%d): %s\n", ssl_error, error_buf);
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Connection closed by server (EOF)\n");
printf("[DEBUG - WebSockets] [Thread %ld] Connection closed by server (EOF)\n", pthread_self());
fflush(stdout);
}
}
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket connection lost, attempting reconnection...\n");
printf("[DEBUG - WebSockets] [Thread %ld] WebSocket connection lost, attempting reconnection...\n", pthread_self());
fflush(stdout);
}
......@@ -1014,32 +1038,29 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
while (reconnect_attempts < max_reconnect_attempts && !reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection attempt %d/%d\n", reconnect_attempts + 1, max_reconnect_attempts);
printf("[DEBUG - WebSockets] [Thread %ld] WebSocket reconnection attempt %d/%d\n", pthread_self(), reconnect_attempts + 1, max_reconnect_attempts);
fflush(stdout);
}
pthread_mutex_lock(&tunnel_mutex);
if (!active_tunnel) {
pthread_mutex_unlock(&tunnel_mutex);
if (!new_tunnel) {
break;
}
if (reconnect_websocket(active_tunnel, wssshd_host, wssshd_port, client_id, active_tunnel->request_id, config->debug) == 0) {
if (reconnect_websocket(new_tunnel, wssshd_host, wssshd_port, client_id, new_tunnel->request_id, config->debug) == 0) {
reconnected = 1;
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection successful, continuing tunnel\n");
printf("[DEBUG - WebSockets] [Thread %ld] WebSocket reconnection successful, continuing tunnel\n", pthread_self());
fflush(stdout);
}
// Update ssl_fd for select
ssl_fd = SSL_get_fd(active_tunnel->ssl);
current_ssl = active_tunnel->ssl;
ssl_fd = SSL_get_fd(new_tunnel->ssl);
current_ssl = new_tunnel->ssl;
}
pthread_mutex_unlock(&tunnel_mutex);
if (!reconnected) {
reconnect_attempts++;
if (reconnect_attempts < max_reconnect_attempts) {
if (config->debug) {
printf("[DEBUG - WebSockets] WebSocket reconnection failed, waiting 1 second...\n");
printf("[DEBUG - WebSockets] [Thread %ld] WebSocket reconnection failed, waiting 1 second...\n", pthread_self());
fflush(stdout);
}
sleep(1);
......@@ -1049,16 +1070,16 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
if (!reconnected) {
if (config->debug) {
printf("[DEBUG - WebSockets] All reconnection attempts failed, exiting\n");
printf("[DEBUG - WebSockets] [Thread %ld] All reconnection attempts failed, exiting\n", pthread_self());
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to connection failure...\n");
printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to connection failure...\n", pthread_self());
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit;
}
......@@ -1066,10 +1087,10 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
continue;
}
frame_buffer_used += bytes_read;
frame_buffer_used += ws_bytes_read;
if (config->debug) {
printf("[DEBUG - WebSockets] Accumulated %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", frame_buffer_used, frame_buffer[0], frame_buffer[1], frame_buffer[2], frame_buffer[3]);
printf("[DEBUG - WebSockets] [Thread %ld] Accumulated %d bytes, frame: 0x%02x 0x%02x 0x%02x 0x%02x\n", pthread_self(), frame_buffer_used, frame_buffer[0], frame_buffer[1], frame_buffer[2], frame_buffer[3]);
fflush(stdout);
}
}
......@@ -1083,39 +1104,39 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
if (frame_type == 0x88) { // Close frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received close frame from server\n");
printf("[DEBUG - WebSockets] [Thread %ld] Received close frame from server\n", pthread_self());
fflush(stdout);
}
// Send tunnel_close notification
if (config->debug) {
printf("[DEBUG - Tunnel] Sending tunnel_close notification due to server close frame...\n");
printf("[DEBUG - Tunnel] [Thread %ld] Sending tunnel_close notification due to server close frame...\n", pthread_self());
fflush(stdout);
}
send_tunnel_close(current_ssl, active_tunnel->request_id, config->debug);
send_tunnel_close(current_ssl, new_tunnel->request_id, config->debug);
goto cleanup_and_exit;
} else if (frame_type == 0x89) { // Ping frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received ping frame, sending pong\n");
printf("[DEBUG - WebSockets] [Thread %ld] Received ping frame, sending pong\n", pthread_self());
fflush(stdout);
}
// Send pong
if (!send_pong_frame(current_ssl, payload, payload_len)) {
if (config->debug) {
printf("[DEBUG - WebSockets] Failed to send pong frame\n");
printf("[DEBUG - WebSockets] [Thread %ld] Failed to send pong frame\n", pthread_self());
fflush(stdout);
}
}
} else if (frame_type == 0x8A) { // Pong frame
if (config->debug) {
printf("[DEBUG - WebSockets] Received pong frame\n");
printf("[DEBUG - WebSockets] [Thread %ld] Received pong frame\n", pthread_self());
fflush(stdout);
}
} else if (frame_type == 0x81 || frame_type == 0x82) { // Text or binary frame
// Copy payload to buffer
if ((size_t)payload_len < sizeof(buffer)) {
memcpy(buffer, payload, payload_len);
buffer[payload_len] = '\0';
if ((size_t)payload_len < sizeof(ws_buffer)) {
memcpy(ws_buffer, payload, payload_len);
ws_buffer[payload_len] = '\0';
} else {
fprintf(stderr, "Payload too large for processing buffer\n");
frame_buffer_used = 0;
......@@ -1123,32 +1144,32 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
}
// Check if this is a data message to suppress verbose logging
int is_data_message = (strstr(buffer, "\"type\": \"tunnel_data\"") != NULL ||
strstr(buffer, "\"type\": \"tunnel_response\"") != NULL);
int is_data_message = (strstr(ws_buffer, "\"type\": \"tunnel_data\"") != NULL ||
strstr(ws_buffer, "\"type\": \"tunnel_response\"") != NULL);
if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Received message: %.*s\n", payload_len, payload);
printf("[DEBUG - WebSockets] [Thread %ld] Received message: %.*s\n", pthread_self(), payload_len, payload);
fflush(stdout);
}
// Handle message
if (config->debug && !is_data_message) {
printf("[DEBUG - WebSockets] Processing message: %s\n", buffer);
printf("[DEBUG - WebSockets] [Thread %ld] Processing message: %s\n", pthread_self(), ws_buffer);
fflush(stdout);
}
// Handle tunnel messages
if (strstr(buffer, "tunnel_data") || strstr(buffer, "tunnel_response")) {
if (strstr(ws_buffer, "tunnel_data") || strstr(ws_buffer, "tunnel_response")) {
if (config->debug) {
// Suppress tunnel_data debug messages in debug mode
if (!strstr(buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] Received tunnel_response message\n");
if (!strstr(ws_buffer, "tunnel_data")) {
printf("[DEBUG - Tunnel] [Thread %ld] Received tunnel_response message\n", pthread_self());
fflush(stdout);
}
}
// Extract request_id and data
char *id_start = strstr(buffer, "\"request_id\"");
char *data_start = strstr(buffer, "\"data\"");
char *id_start = strstr(ws_buffer, "\"request_id\"");
char *data_start = strstr(ws_buffer, "\"data\"");
if (id_start && data_start) {
char *colon = strchr(id_start, ':');
if (colon) {
......@@ -1174,12 +1195,12 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
}
}
}
} else if (strstr(buffer, "tunnel_close")) {
} else if (strstr(ws_buffer, "tunnel_close")) {
if (config->debug) {
printf("[DEBUG - Tunnel] Received tunnel_close message\n");
printf("[DEBUG - Tunnel] [Thread %ld] Received tunnel_close message\n", pthread_self());
fflush(stdout);
}
char *id_start = strstr(buffer, "\"request_id\"");
char *id_start = strstr(ws_buffer, "\"request_id\"");
if (id_start) {
char *colon = strchr(id_start, ':');
if (colon) {
......@@ -1196,7 +1217,7 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
}
} else {
if (config->debug) {
printf("[DEBUG - WebSockets] Received unknown message type: %s\n", buffer);
printf("[DEBUG - WebSockets] [Thread %ld] Received unknown message type: %s\n", pthread_self(), ws_buffer);
fflush(stdout);
}
}
......@@ -1220,42 +1241,189 @@ int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *w
cleanup_and_exit:
// Cleanup section
if (config->debug) {
printf("[DEBUG - Tunnel] Performing cleanup and exiting\n");
printf("[DEBUG - Tunnel] [Thread %ld] Performing cleanup and exiting\n", pthread_self());
fflush(stdout);
}
// Cleanup
if (active_tunnel) {
if (active_tunnel->local_sock >= 0) {
close(active_tunnel->local_sock);
if (new_tunnel) {
if (new_tunnel->local_sock >= 0) {
close(new_tunnel->local_sock);
}
if (active_tunnel->ssl) {
SSL_free(active_tunnel->ssl);
if (new_tunnel->ssl) {
SSL_free(new_tunnel->ssl);
}
free(active_tunnel);
active_tunnel = NULL;
// Remove tunnel from global array
pthread_mutex_lock(&tunnel_mutex);
remove_tunnel(new_tunnel->request_id);
pthread_mutex_unlock(&tunnel_mutex);
frame_buffer_free(new_tunnel->incoming_buffer);
free(new_tunnel);
}
free(config->local_port);
free(config->tunnel);
free(config->tunnel_control);
free(config->service);
// Set to NULL to prevent double-free in main()
config->local_port = NULL;
config->tunnel = NULL;
config->tunnel_control = NULL;
config->service = NULL;
// Note: config_domain, config_clientid, etc. are freed in main()
pthread_mutex_destroy(&tunnel_mutex);
pthread_mutex_destroy(&ssl_mutex);
if (config->debug) {
printf("[DEBUG - Tunnel] [Thread %ld] Cleanup complete, thread exiting\n", pthread_self());
fflush(stdout);
}
return NULL;
}
int run_daemon_mode(wsssh_config_t *config, const char *client_id, const char *wssshd_host, int wssshd_port) {
// Daemon mode: Lazy tunnel establishment - bind port immediately, establish tunnel on connection
if (config->debug) {
printf("[DEBUG - Tunnel] Cleanup complete, exiting with code %d\n", tunnel_broken ? 1 : 0);
printf("[DEBUG] Starting daemon mode with lazy tunnel establishment\n");
fflush(stdout);
}
// Find available local port
int local_port = config->local_port ? atoi(config->local_port) : find_available_port();
if (local_port == 0) {
fprintf(stderr, "Error: Could not find available local port\n");
return 1;
}
if (config->debug) {
printf("[DEBUG] Using local port: %d\n", local_port);
fflush(stdout);
}
// Create listening socket
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0) {
perror("Local socket creation failed");
return 1;
}
struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(local_port);
// Use specified tunnel_host or default to 127.0.0.1
if (config->tunnel_host && strcmp(config->tunnel_host, "127.0.0.1") != 0) {
struct hostent *tunnel_he;
if ((tunnel_he = gethostbyname(config->tunnel_host)) == NULL) {
if (config->debug) {
fprintf(stderr, "[DEBUG] Failed to resolve tunnel_host '%s', using 127.0.0.1\n", config->tunnel_host);
}
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
} else {
local_addr.sin_addr = *((struct in_addr *)tunnel_he->h_addr);
if (config->debug) {
printf("[DEBUG] Binding tunnel to %s:%d\n", config->tunnel_host, local_port);
fflush(stdout);
}
}
} else {
local_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (config->debug) {
printf("[DEBUG] Binding tunnel to 127.0.0.1:%d\n", local_port);
fflush(stdout);
}
}
if (bind(listen_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) {
perror("Local bind failed");
close(listen_sock);
return 1;
}
if (listen(listen_sock, 1) < 0) {
perror("Local listen failed");
close(listen_sock);
return 1;
}
if (config->mode != MODE_SILENT) {
printf("Daemon mode: Listening on port %d, waiting for connection...\n", local_port);
printf("Tunnel will be established on first connection attempt.\n");
fflush(stdout);
}
// In daemon mode, continue to accept new connections
// Continue the while(1) loop for next connection
// Daemon mode main loop - accept connections and establish tunnels
while (1) {
// Check for SIGINT
if (sigint_received) {
if (config->debug) {
printf("[DEBUG] SIGINT received in daemon mode, exiting\n");
fflush(stdout);
}
break;
}
// Wait for connection with timeout
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds);
FD_SET(listen_sock, &readfds);
tv.tv_sec = 1; // 1 second timeout to check for signals
tv.tv_usec = 0;
int retval = select(listen_sock + 1, &readfds, NULL, NULL, &tv);
if (retval < 0) {
if (errno == EINTR) {
// Interrupted by signal, continue to check sigint_received
continue;
}
perror("Select failed in daemon mode");
// In daemon mode, don't exit on select errors, wait a bit and retry
sleep(1);
continue;
} else if (retval == 0) {
// Timeout, continue loop to check signals
continue;
}
// Accept connection
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int accepted_sock = accept(listen_sock, (struct sockaddr *)&client_addr, &client_len);
if (accepted_sock < 0) {
if (errno == EINTR) {
continue;
}
perror("Local accept failed");
continue; // Don't exit on accept failure, keep listening
}
if (config->debug) {
printf("[DEBUG] Connection received on port %d, spawning handler thread...\n", local_port);
fflush(stdout);
}
// Create thread arguments
connection_handler_args_t *thread_args = malloc(sizeof(connection_handler_args_t));
if (!thread_args) {
perror("Memory allocation failed for connection handler args");
close(accepted_sock);
continue;
}
thread_args->config = config;
thread_args->client_id = client_id;
thread_args->wssshd_host = wssshd_host;
thread_args->wssshd_port = wssshd_port;
thread_args->accepted_sock = accepted_sock;
thread_args->local_port = local_port;
// Spawn a new thread to handle this connection
pthread_t connection_thread;
if (pthread_create(&connection_thread, NULL, handle_connection, thread_args) != 0) {
perror("Failed to create connection handler thread");
free(thread_args);
close(accepted_sock);
continue;
}
// Detach the thread so it can clean up automatically when done
pthread_detach(connection_thread);
if (config->debug) {
printf("[DEBUG] Connection handler thread spawned (ID: %ld)\n", connection_thread);
fflush(stdout);
}
}
// This should never be reached, but just in case
......
......@@ -66,6 +66,11 @@ int frame_buffer_resize(frame_buffer_t *fb, size_t new_size);
int frame_buffer_append(frame_buffer_t *fb, const char *data, size_t len);
int frame_buffer_consume(frame_buffer_t *fb, size_t len);
// Helper functions for managing multiple tunnels
tunnel_t *find_tunnel_by_request_id(const char *request_id);
int add_tunnel(tunnel_t *tunnel);
void remove_tunnel(const char *request_id);
void *forward_tcp_to_ws(void *arg);
void *forward_ws_to_local(void *arg);
void *forward_ws_to_ssh_server(void *arg);
......
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