You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
463 lines
15 KiB
463 lines
15 KiB
/** |
|
* @file control_socket.c |
|
* @brief Control socket implementation for statistics and diagnostics |
|
*/ |
|
|
|
#include "control_socket.h" |
|
#include "connection.h" |
|
#include "etcp.h" |
|
#include "pkt_normalizer.h" |
|
#include "routing.h" |
|
#include "tun_if.h" |
|
#include "config_parser.h" |
|
#include "utun_state.h" |
|
|
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <unistd.h> |
|
#include <errno.h> |
|
#include <time.h> |
|
#include <sys/socket.h> |
|
#include <netinet/in.h> |
|
#include <arpa/inet.h> |
|
#include <fcntl.h> |
|
#include <sys/time.h> |
|
|
|
// Internal control socket structure |
|
struct control_socket { |
|
int sockfd; |
|
struct sockaddr_in addr; |
|
uint16_t port; |
|
void *app_state; // Pointer to utun_state_t |
|
uint16_t sequence_counter; |
|
|
|
// Statistics counters (for cumulative stats) |
|
uint64_t total_requests; |
|
uint64_t total_errors; |
|
|
|
// Timestamps |
|
struct timeval start_time; |
|
}; |
|
|
|
// Forward declarations |
|
static int collect_statistics(control_socket_t *cs, control_stats_packet_t *stats); |
|
static int collect_system_status(control_socket_t *cs, system_status_t *status); |
|
static void get_etcp_stats(epkt_t *etcp, etcp_stats_t *etcp_stats); |
|
static void get_pkt_normalizer_stats(pkt_normalizer_pair *pair, pkt_normalizer_stats_t *pn_stats); |
|
static int handle_stats_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence); |
|
static int handle_status_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence); |
|
static int handle_reset_request(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence); |
|
static int send_error_response(control_socket_t *cs, struct sockaddr_in *client, uint16_t sequence, control_err_t error); |
|
static int send_response(control_socket_t *cs, struct sockaddr_in *client, void *data, size_t len); |
|
|
|
// Create UDP control socket |
|
control_socket_t* control_socket_create(const char *ip, uint16_t port) { |
|
control_socket_t *cs = calloc(1, sizeof(control_socket_t)); |
|
if (!cs) { |
|
return NULL; |
|
} |
|
|
|
// Create UDP socket |
|
cs->sockfd = socket(AF_INET, SOCK_DGRAM, 0); |
|
if (cs->sockfd < 0) { |
|
perror("control_socket: socket"); |
|
free(cs); |
|
return NULL; |
|
} |
|
|
|
// Set non-blocking |
|
int flags = fcntl(cs->sockfd, F_GETFL, 0); |
|
if (flags < 0) { |
|
perror("control_socket: fcntl GETFL"); |
|
close(cs->sockfd); |
|
free(cs); |
|
return NULL; |
|
} |
|
if (fcntl(cs->sockfd, F_SETFL, flags | O_NONBLOCK) < 0) { |
|
perror("control_socket: fcntl SETFL"); |
|
close(cs->sockfd); |
|
free(cs); |
|
return NULL; |
|
} |
|
|
|
// Enable address reuse |
|
int opt = 1; |
|
if (setsockopt(cs->sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { |
|
perror("control_socket: setsockopt SO_REUSEADDR"); |
|
// Continue anyway |
|
} |
|
|
|
// Bind to address |
|
memset(&cs->addr, 0, sizeof(cs->addr)); |
|
cs->addr.sin_family = AF_INET; |
|
cs->addr.sin_port = htons(port); |
|
|
|
if (ip && ip[0] != '\0') { |
|
if (inet_pton(AF_INET, ip, &cs->addr.sin_addr) != 1) { |
|
fprintf(stderr, "control_socket: invalid IP address: %s\n", ip); |
|
close(cs->sockfd); |
|
free(cs); |
|
return NULL; |
|
} |
|
} else { |
|
cs->addr.sin_addr.s_addr = INADDR_ANY; |
|
} |
|
|
|
if (bind(cs->sockfd, (struct sockaddr*)&cs->addr, sizeof(cs->addr)) < 0) { |
|
perror("control_socket: bind"); |
|
close(cs->sockfd); |
|
free(cs); |
|
return NULL; |
|
} |
|
|
|
cs->port = port; |
|
cs->sequence_counter = 0; |
|
cs->total_requests = 0; |
|
cs->total_errors = 0; |
|
gettimeofday(&cs->start_time, NULL); |
|
|
|
printf("[CONTROL] Control socket listening on %s:%u\n", |
|
ip ? ip : "0.0.0.0", port); |
|
|
|
return cs; |
|
} |
|
|
|
// Destroy control socket |
|
void control_socket_destroy(control_socket_t *cs) { |
|
if (!cs) return; |
|
|
|
if (cs->sockfd >= 0) { |
|
close(cs->sockfd); |
|
} |
|
|
|
free(cs); |
|
} |
|
|
|
// Get socket file descriptor for polling |
|
int control_socket_get_fd(const control_socket_t *cs) { |
|
return cs ? cs->sockfd : -1; |
|
} |
|
|
|
// Set application state pointer |
|
void control_socket_set_state(control_socket_t *cs, void *state) { |
|
if (cs) { |
|
cs->app_state = state; |
|
} |
|
} |
|
|
|
// Process incoming requests |
|
int control_socket_process(control_socket_t *cs, void *state) { |
|
if (!cs || !state) return -1; |
|
|
|
// Use provided state or stored state |
|
if (state) { |
|
cs->app_state = state; |
|
} |
|
|
|
struct sockaddr_in client_addr; |
|
socklen_t addr_len = sizeof(client_addr); |
|
control_request_packet_t request; |
|
|
|
// Receive request |
|
ssize_t n = recvfrom(cs->sockfd, &request, sizeof(request), 0, |
|
(struct sockaddr*)&client_addr, &addr_len); |
|
|
|
if (n < 0) { |
|
if (errno == EAGAIN || errno == EWOULDBLOCK) { |
|
return 0; // No data available |
|
} |
|
perror("control_socket: recvfrom"); |
|
return -1; |
|
} |
|
|
|
if (n != sizeof(request)) { |
|
fprintf(stderr, "control_socket: invalid request size: %zd\n", n); |
|
return 0; |
|
} |
|
|
|
cs->total_requests++; |
|
|
|
// Handle request based on command |
|
switch (request.command) { |
|
case CONTROL_CMD_GET_STATS: |
|
return handle_stats_request(cs, &client_addr, request.sequence); |
|
|
|
case CONTROL_CMD_GET_STATUS: |
|
return handle_status_request(cs, &client_addr, request.sequence); |
|
|
|
case CONTROL_CMD_RESET_STATS: |
|
return handle_reset_request(cs, &client_addr, request.sequence); |
|
|
|
default: |
|
return send_error_response(cs, &client_addr, request.sequence, |
|
CONTROL_ERR_INVALID_CMD); |
|
} |
|
} |
|
|
|
// Handle statistics request |
|
static int handle_stats_request(control_socket_t *cs, struct sockaddr_in *client, |
|
uint16_t sequence) { |
|
control_stats_packet_t stats_packet = {0}; |
|
|
|
// Fill response header |
|
stats_packet.response_type = CONTROL_RESP_STATS; |
|
stats_packet.error_code = CONTROL_ERR_NONE; |
|
stats_packet.sequence = sequence; |
|
|
|
// Get current timestamp |
|
struct timeval tv; |
|
gettimeofday(&tv, NULL); |
|
stats_packet.timestamp = (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec; |
|
|
|
// Collect statistics from all modules |
|
if (collect_statistics(cs, &stats_packet) < 0) { |
|
return send_error_response(cs, client, sequence, CONTROL_ERR_INTERNAL); |
|
} |
|
|
|
// Send response |
|
return send_response(cs, client, &stats_packet, sizeof(stats_packet)); |
|
} |
|
|
|
// Handle status request |
|
static int handle_status_request(control_socket_t *cs, struct sockaddr_in *client, |
|
uint16_t sequence) { |
|
control_status_packet_t status_packet = {0}; |
|
|
|
status_packet.response_type = CONTROL_RESP_STATUS; |
|
status_packet.error_code = CONTROL_ERR_NONE; |
|
status_packet.sequence = sequence; |
|
|
|
// Collect system status |
|
if (collect_system_status(cs, &status_packet.system) < 0) { |
|
status_packet.system.uptime_seconds = 0; |
|
} |
|
|
|
// Set running flag |
|
if (cs->app_state) { |
|
// Assume utun_state_t has 'running' field at same offset |
|
// We'll fix this when integrating with utun.c |
|
status_packet.running = 1; |
|
} |
|
|
|
snprintf(status_packet.status_msg, sizeof(status_packet.status_msg), |
|
"utun control socket active on port %u", cs->port); |
|
|
|
return send_response(cs, client, &status_packet, sizeof(status_packet)); |
|
} |
|
|
|
// Handle reset statistics request |
|
static int handle_reset_request(control_socket_t *cs, struct sockaddr_in *client, |
|
uint16_t sequence) { |
|
// TODO: Implement statistics reset |
|
// For now, just send success response |
|
|
|
control_status_packet_t response = {0}; |
|
response.response_type = CONTROL_RESP_STATUS; |
|
response.error_code = CONTROL_ERR_NONE; |
|
response.sequence = sequence; |
|
|
|
// Get basic system status |
|
collect_system_status(cs, &response.system); |
|
|
|
snprintf(response.status_msg, sizeof(response.status_msg), |
|
"Statistics reset requested (not implemented)"); |
|
|
|
return send_response(cs, client, &response, sizeof(response)); |
|
} |
|
|
|
// Send error response |
|
static int send_error_response(control_socket_t *cs, struct sockaddr_in *client, |
|
uint16_t sequence, control_err_t error) { |
|
control_status_packet_t response = {0}; |
|
|
|
response.response_type = CONTROL_RESP_ERROR; |
|
response.error_code = error; |
|
response.sequence = sequence; |
|
|
|
const char *error_msg = "Unknown error"; |
|
switch (error) { |
|
case CONTROL_ERR_INVALID_CMD: |
|
error_msg = "Invalid command"; |
|
break; |
|
case CONTROL_ERR_INTERNAL: |
|
error_msg = "Internal error"; |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
snprintf(response.status_msg, sizeof(response.status_msg), "%s", error_msg); |
|
|
|
cs->total_errors++; |
|
|
|
return send_response(cs, client, &response, sizeof(response)); |
|
} |
|
|
|
// Send response to client |
|
static int send_response(control_socket_t *cs, struct sockaddr_in *client, |
|
void *data, size_t len) { |
|
ssize_t sent = sendto(cs->sockfd, data, len, 0, |
|
(struct sockaddr*)client, sizeof(*client)); |
|
|
|
if (sent != (ssize_t)len) { |
|
perror("control_socket: sendto"); |
|
return -1; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
// Collect statistics from all modules |
|
static int collect_statistics(control_socket_t *cs, control_stats_packet_t *stats) { |
|
if (!cs->app_state) { |
|
return -1; |
|
} |
|
|
|
utun_state_t *state = (utun_state_t*)cs->app_state; |
|
|
|
// Zero out stats |
|
memset(&stats->pkt_normalizer, 0, sizeof(stats->pkt_normalizer)); |
|
memset(&stats->etcp, 0, sizeof(stats->etcp)); |
|
memset(&stats->routing, 0, sizeof(stats->routing)); |
|
memset(&stats->tun, 0, sizeof(stats->tun)); |
|
stats->conn_count = 0; |
|
|
|
// Collect connection statistics |
|
if (state->connections && state->connection_count > 0) { |
|
int max_conn = state->connection_count; |
|
if (max_conn > MAX_CONN_STATS) max_conn = MAX_CONN_STATS; |
|
|
|
for (int i = 0; i < max_conn; i++) { |
|
conn_handle_t *conn = state->connections[i]; |
|
if (!conn) continue; |
|
|
|
conn_stats_t conn_stats; |
|
if (conn_get_stats(conn, &conn_stats) == 0) { |
|
// Aggregate connection stats |
|
stats->etcp.bytes_sent_total += conn_stats.bytes_sent; |
|
stats->etcp.bytes_received_total += conn_stats.bytes_received; |
|
stats->etcp.retransmissions += conn_stats.retransmissions; |
|
stats->pkt_normalizer.fragments_assembled += conn_stats.fragments_assembled; |
|
// Note: etcp and pkt_normalizer stats are aggregated across connections |
|
// Additional per-connection stats could be stored in conn_stats_entry_t |
|
} |
|
|
|
// Fill connection entry (if we have array) |
|
if (i < MAX_CONN_STATS) { |
|
if (state->config && state->config->connections) { |
|
strncpy(stats->connections[i].name, state->config->connections[i].name, |
|
sizeof(stats->connections[i].name) - 1); |
|
stats->connections[i].name[sizeof(stats->connections[i].name) - 1] = '\0'; |
|
} else { |
|
snprintf(stats->connections[i].name, sizeof(stats->connections[i].name), "conn%d", i); |
|
} |
|
stats->connections[i].bytes_sent = conn_stats.bytes_sent; |
|
stats->connections[i].bytes_received = conn_stats.bytes_received; |
|
stats->connections[i].packets_sent = conn_stats.packets_sent; |
|
stats->connections[i].packets_received = conn_stats.packets_received; |
|
stats->connections[i].retransmissions = conn_stats.retransmissions; |
|
stats->connections[i].fragments_assembled = conn_stats.fragments_assembled; |
|
stats->connections[i].current_rtt_ms = conn_stats.current_rtt_ms; |
|
stats->connections[i].jitter_ms = conn_stats.jitter_ms; |
|
stats->connections[i].is_active = 1; |
|
} |
|
} |
|
stats->conn_count = max_conn; |
|
} |
|
|
|
// Collect routing statistics |
|
if (state->routing_table) { |
|
routing_get_stats(state->routing_table, &stats->routing); |
|
} |
|
|
|
// Collect TUN statistics |
|
stats->tun.bytes_read = state->tun.bytes_read; |
|
stats->tun.bytes_written = state->tun.bytes_written; |
|
stats->tun.packets_read = state->tun.packets_read; |
|
stats->tun.packets_written = state->tun.packets_written; |
|
stats->tun.read_errors = state->tun.read_errors; |
|
stats->tun.write_errors = state->tun.write_errors; |
|
strncpy(stats->tun.ifname, state->tun.ifname, sizeof(stats->tun.ifname) - 1); |
|
stats->tun.ifname[sizeof(stats->tun.ifname) - 1] = '\0'; |
|
strncpy(stats->tun.ip_addr, state->tun.ip_addr, sizeof(stats->tun.ip_addr) - 1); |
|
stats->tun.ip_addr[sizeof(stats->tun.ip_addr) - 1] = '\0'; |
|
|
|
// Collect system status |
|
return collect_system_status(cs, &stats->system); |
|
} |
|
|
|
// Collect system status |
|
static int collect_system_status(control_socket_t *cs, system_status_t *status) { |
|
if (!cs) return -1; |
|
|
|
memset(status, 0, sizeof(*status)); |
|
|
|
// Calculate uptime |
|
struct timeval now; |
|
gettimeofday(&now, NULL); |
|
status->uptime_seconds = now.tv_sec - cs->start_time.tv_sec; |
|
|
|
// Get memory usage (simplified) |
|
FILE *meminfo = fopen("/proc/self/statm", "r"); |
|
if (meminfo) { |
|
unsigned long pages; |
|
if (fscanf(meminfo, "%lu", &pages) == 1) { |
|
status->memory_usage_kb = pages * 4; // Assume 4KB pages |
|
} |
|
fclose(meminfo); |
|
} |
|
|
|
// CPU usage (placeholder) |
|
status->cpu_usage_percent = 0; |
|
|
|
// Version info |
|
snprintf(status->version, sizeof(status->version), "utun v0.1"); |
|
|
|
// Control port |
|
status->control_port = cs->port; |
|
|
|
// Running flag and connection count will be set by caller |
|
|
|
return 0; |
|
} |
|
|
|
// Get ETCP statistics |
|
static void get_etcp_stats(epkt_t *etcp, etcp_stats_t *etcp_stats) { |
|
if (!etcp || !etcp_stats) return; |
|
|
|
memset(etcp_stats, 0, sizeof(*etcp_stats)); |
|
|
|
// Use etcp_get_stats if available |
|
// etcp_get_stats(etcp, &retransmissions, ...); |
|
|
|
// Direct access to epkt fields (assuming struct is visible) |
|
etcp_stats->retransmissions = etcp->retransmissions_count; |
|
etcp_stats->total_packets_sent = etcp->total_packets_sent; |
|
etcp_stats->unique_packets_sent = etcp->unique_packets_sent; |
|
etcp_stats->bytes_sent_total = etcp->bytes_sent_total; |
|
etcp_stats->bytes_received_total = etcp->bytes_received_total; |
|
etcp_stats->ack_packets_count = etcp->ack_packets_count; |
|
etcp_stats->control_packets_count = etcp->control_packets_count; |
|
etcp_stats->current_rtt = etcp->rtt_avg_10; |
|
etcp_stats->jitter = etcp->jitter; |
|
etcp_stats->window_size = etcp->window_size; |
|
etcp_stats->unacked_bytes = etcp->unacked_bytes; |
|
} |
|
|
|
// Get packet normalizer statistics |
|
static void get_pkt_normalizer_stats(pkt_normalizer_pair *pair, |
|
pkt_normalizer_stats_t *pn_stats) { |
|
if (!pair || !pn_stats) return; |
|
|
|
memset(pn_stats, 0, sizeof(*pn_stats)); |
|
|
|
if (pair->packer) { |
|
pn_stats->packer_errors = pkt_normalizer_get_error_count(pair->packer); |
|
} |
|
|
|
if (pair->unpacker) { |
|
pn_stats->unpacker_errors = pkt_normalizer_get_error_count(pair->unpacker); |
|
// Fragments assembled count might need to be tracked separately |
|
} |
|
} |