Apply control TCP backpressure and stream node snapshots by ID

This commit is contained in:
evgeny committed 2026-10-27 13:46:01 +01:00
1 parent ce06fe242b
commit 488d8a601f
3 files changed
+319 -120

No files matched your search

+287 -113
View File
@@ -91,9 +91,12 @@ static void accept_callback(socket_t fd, void* arg);
static void client_read_callback(socket_t fd, void* arg);
static void client_write_callback(socket_t fd, void* arg);
static void client_except_callback(socket_t fd, void* arg);
static void handle_client_data(struct control_server* server, struct control_client* client);
static int handle_client_data(struct control_server* server, struct control_client* client);
static void close_client(struct control_server* server, struct control_client* client);
static void control_client_idle_cb(void* arg);
static void control_output_ready(struct ll_queue* queue, void* arg);
static int control_wait_output(struct control_client* client);
static int control_client_arm_timer(struct control_client* client, int tx);
static void send_conn_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
static void send_socket_list(struct control_server* server, struct control_client* client, uint8_t seq_id);
@@ -430,6 +433,9 @@ static void accept_callback(socket_t fd, void* arg) {
}
client->subscribed_nodes = 0;
client->write_registered = 0;
queue_set_size_limit(client->output_queue, 1);
queue_set_threshold(client->output_queue, 0, 0);
queue_set_waiter_defer(client->output_queue, 1);
/* Register with uasync */
client->socket_id = uasync_add_socket_t(server->ua, client_fd,
@@ -490,6 +496,7 @@ static void accept_callback(socket_t fd, void* arg) {
static void client_read_callback(socket_t fd, void* arg) {
struct control_client* client = (struct control_client*)arg;
struct control_server* server = client->server;
if (client->read_paused) return;
if (client->recv_len >= ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control recv buffer overflow (pre-check)");
@@ -586,57 +593,87 @@ static void client_read_callback(socket_t fd, void* arg) {
}
}
/* Один таймер: ожидание команд в покое, отсутствие положительного send во время TX. */
static int control_client_arm_timer(struct control_client* client, int tx) {
if (client->idle_timer) uasync_cancel_timeout(client->server->ua, client->idle_timer);
client->timer_is_tx = tx != 0;
client->idle_timer = uasync_set_timeout(client->server->ua, 300000, client, control_client_idle_cb, tx ? "ctrl_tx" : "ctrl_idle");
if (!client->idle_timer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control timer failed fd=%d mode=%s", (int)client->fd, tx ? "TX" : "idle");
client->failed = 1;
return -1;
}
return 0;
}
/* Останавливает входных producers до пустой очереди. Waiter всегда отложен, повторных регистраций нет. */
static int control_wait_output(struct control_client* client) {
if (!client->read_paused) {
if (uasync_set_socket_read(client->server->ua, client->socket_id, 0) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to pause control reads fd=%d", (int)client->fd);
client->failed = 1;
return -1;
}
client->read_paused = 1;
DEBUG_DEBUG(DEBUG_CATEGORY_CONTROL, "Control reads paused fd=%d queued=%zu snapshot=%zu/%zu dirty=%zu",
(int)client->fd, queue_total_bytes(client->output_queue), client->snapshot_index,
client->snapshot_count, client->dirty_count);
}
if (!client->timer_is_tx && control_client_arm_timer(client, 1) < 0) return -1;
if (!client->output_waiter.internal && !client->output_waiter.call_soon_id &&
queue_waiter_wait(client->output_queue, &client->output_waiter, control_output_ready, client) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to wait for control output fd=%d", (int)client->fd);
client->failed = 1;
return -1;
}
return 0;
}
static void client_write_callback(socket_t fd, void* arg) {
struct control_client* client = (struct control_client*)arg;
struct control_client* client = arg;
struct control_server* server = client->server;
struct ll_queue* q = client->output_queue;
while (q->head) {
struct ll_entry* entry = q->head;
struct ll_queue* queue = client->output_queue;
struct ll_entry* entry = queue->head;
if (client->failed) { close_client(server, client); return; }
if (entry) {
#ifdef _WIN32
int sent = send(fd, (const char*)entry->dgram + client->output_offset, entry->len - client->output_offset, 0);
if (sent == SOCKET_ERROR) {
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %d, closing", err);
close_client(server, client);
return;
}
return;
}
int error = WSAGetLastError();
if (error == WSAEWOULDBLOCK) return;
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control send failed fd=%d error=%d", (int)fd, error);
#else
ssize_t sent = send(fd, entry->dgram + client->output_offset, entry->len - client->output_offset, 0);
if (sent < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Client send error: %s, closing", strerror(errno));
close_client(server, client);
return;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) return;
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control send failed fd=%d: %s", (int)fd, strerror(errno));
#endif
close_client(server, client);
return;
}
#endif
if (sent == 0) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control client send made no progress fd=%d, closing", (int)fd);
if (!sent) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control send made no progress fd=%d", (int)fd);
close_client(server, client);
return;
}
client->output_offset += (uint16_t)sent;
if (client->output_offset < entry->len) {
DEBUG_DEBUG(DEBUG_CATEGORY_CONTROL, "Control partial send fd=%d offset=%u size=%u queued_bytes=%zu",
(int)fd, client->output_offset, entry->len, queue_total_bytes(q));
DEBUG_DEBUG(DEBUG_CATEGORY_CONTROL, "Control partial send fd=%d offset=%u/%u",
(int)fd, client->output_offset, entry->len);
if (control_client_arm_timer(client, 1) < 0) close_client(server, client);
return;
}
struct ll_entry* e = queue_data_get(q);
queue_dgram_free(e); queue_entry_free(e);
entry = queue_data_get(queue);
queue_dgram_free(entry); queue_entry_free(entry);
client->output_offset = 0;
}
uasync_set_socket_write(server->ua, client->socket_id, 0);
if (uasync_set_socket_write(server->ua, client->socket_id, 0) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to disable control writes fd=%d", (int)fd);
close_client(server, client);
return;
}
client->write_registered = 0;
if (q->count > 128) DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "ctrl_out: large output queue count=%d fd=%d", q->count, (int)client->fd);
(void)fd;
if (control_client_arm_timer(client, client->snapshot_active || client->dirty_count) < 0) close_client(server, client);
}
static void client_except_callback(socket_t fd, void* arg) {
@@ -652,33 +689,48 @@ static void client_except_callback(socket_t fd, void* arg) {
/* Принимает владение буфером, включая ветки ошибки постановки в очередь. */
static int control_client_send(struct control_server* server, struct control_client* client, uint8_t* dgram, uint16_t len) {
(void)server;
if (!client || !client->output_queue || !dgram || len == 0 || len > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid control output message client=%p size=%u", (void*)client, len);
if (!client || !client->output_queue || !dgram || !len || len > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid control output client=%p size=%u", (void*)client, len);
if (client) client->failed = 1;
u_free(dgram);
return -1;
}
if (client->output_queue->head || client->failed) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Control producer violated TX backpressure fd=%d queued=%zu incoming=%u",
(int)client->fd, queue_total_bytes(client->output_queue), len);
client->failed = 1; u_free(dgram);
return -1;
}
struct ll_entry* entry = queue_entry_new(0);
if (!entry) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate output queue entry size=%u", len);
u_free(dgram);
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate control output entry fd=%d size=%u", (int)client->fd, len);
client->failed = 1; u_free(dgram);
return -1;
}
entry->dgram = dgram; entry->len = len;
if (queue_data_put(client->output_queue, entry) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to enqueue control output fd=%d size=%u", (int)client->fd, len);
client->failed = 1;
return -1;
}
entry->dgram = dgram;
entry->len = len;
queue_data_put(client->output_queue, entry);
if (!client->write_registered) {
uasync_set_socket_write(client->server->ua, client->socket_id, 1);
if (uasync_set_socket_write(server->ua, client->socket_id, 1) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to enable control writes fd=%d", (int)client->fd);
client->failed = 1;
return -1;
}
client->write_registered = 1;
}
return 0;
return control_wait_output(client);
}
static void control_client_idle_cb(void* arg) {
struct control_client* client = (struct control_client*)arg;
struct control_client* client = arg;
if (!client || !client->server) return;
client->idle_timer = NULL;
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control client idle timeout fd=%d, closing", client->fd);
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control %s timeout fd=%d queued_bytes=%zu snapshot=%zu/%zu dirty=%zu",
client->timer_is_tx ? "TX progress" : "idle", (int)client->fd, queue_total_bytes(client->output_queue),
client->snapshot_index, client->snapshot_count, client->dirty_count);
close_client(client->server, client);
}
@@ -690,6 +742,8 @@ static void close_client(struct control_server* server, struct control_client* c
client->output_offset, client->idle_timer != NULL);
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); client->idle_timer = NULL; }
if (client->output_queue) queue_waiter_cancel(client->output_queue, &client->output_waiter);
u_free(client->snapshot_ids); client->snapshot_ids = NULL;
/* Log disconnection */
if (server->log_file) {
@@ -742,19 +796,24 @@ static void close_client(struct control_server* server, struct control_client* c
* Message Handling
* ============================================================================ */
static void handle_client_data(struct control_server* server, struct control_client* client) {
static int handle_client_data(struct control_server* server, struct control_client* client) {
if (client->failed) { close_client(server, client); return -1; }
if (client->output_queue->head || client->snapshot_active || client->dirty_count) {
if (control_wait_output(client) < 0) { close_client(server, client); return -1; }
return 0;
}
while (client->recv_len >= sizeof(struct etcpmon_msg_header)) {
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer;
if (hdr->size == 0 || hdr->size > ETCPMON_MAX_MSG_SIZE) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid message size from client: %u", hdr->size);
close_client(server, client);
return;
return -1;
}
if (hdr->type < ETCPMON_CMD_LIST_CONN || hdr->type > ETCPMON_CMD_SUBSCRIBE_NODES) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command type from client: 0x%02X", hdr->type);
close_client(server, client);
return;
return -1;
}
/* Validate header */
@@ -766,7 +825,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid message header from client");
close_client(server, client);
return;
return -1;
}
/* Check if full message received */
@@ -779,6 +838,14 @@ static void handle_client_data(struct control_server* server, struct control_cli
uint16_t payload_size = hdr->size - sizeof(struct etcpmon_msg_header);
uint8_t req_seq = hdr->seq_id;
if ((hdr->type == ETCPMON_CMD_SELECT_CONN && payload_size != sizeof(struct etcpmon_cmd_select)) ||
(hdr->type != ETCPMON_CMD_SELECT_CONN && hdr->type != ETCPMON_CMD_ACTION &&
hdr->type != ETCPMON_CMD_SET_DEBUG_CONFIG && payload_size)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Invalid command payload fd=%d type=0x%02X size=%u",
(int)client->fd, hdr->type, payload_size);
close_client(server, client);
return -1;
}
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Received command type=0x%02X seq=%d size=%d\n",
@@ -805,7 +872,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
} else {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Bad SELECT_CONN payload size %u", payload_size);
close_client(server, client);
return;
return -1;
}
break;
@@ -825,7 +892,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
case ETCPMON_CMD_ACTION:
if (handle_action(server, client, payload, payload_size, req_seq) < 0) {
close_client(server, client);
return;
return -1;
}
break;
@@ -838,7 +905,7 @@ static void handle_client_data(struct control_server* server, struct control_cli
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Bad SET_DEBUG_CONFIG payload size=%u categories=%d",
payload_size, payload_size >= 2 ? payload[1] : -1);
close_client(server, client);
return;
return -1;
}
uint8_t global_lvl = payload[0];
uint8_t cat_count = payload[1];
@@ -858,13 +925,6 @@ static void handle_client_data(struct control_server* server, struct control_cli
}
case ETCPMON_CMD_SUBSCRIBE_NODES:
client->subscribed_nodes = 1;
if (server->log_file) {
fprintf(server->log_file, "%llu: [LOG] Client subscribed to node updates\n",
(unsigned long long)get_timestamp_ms());
fflush(server->log_file);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Client subscribed to node updates");
control_server_send_node_list(server, client, req_seq);
break;
@@ -875,22 +935,15 @@ static void handle_client_data(struct control_server* server, struct control_cli
fflush(server->log_file);
}
close_client(server, client);
return;
return -1;
default:
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Unknown command from client: 0x%02X", hdr->type);
close_client(server, client);
return;
return -1;
}
/* Reset idle timer on each valid command */
if (client->idle_timer) { uasync_cancel_timeout(server->ua, client->idle_timer); }
client->idle_timer = uasync_set_timeout(server->ua, 300000, client, control_client_idle_cb, "ctrl_idle");
if (!client->idle_timer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to reset control client idle timer fd=%d", (int)client->fd);
close_client(server, client);
return;
}
if (client->failed) { close_client(server, client); return -1; }
/* Remove processed message from buffer */
uint16_t msg_size = hdr->size;
@@ -899,7 +952,13 @@ static void handle_client_data(struct control_server* server, struct control_cli
client->recv_len - msg_size);
}
client->recv_len -= msg_size;
if (client->output_queue->head || client->snapshot_active || client->dirty_count) {
if (control_wait_output(client) < 0) { close_client(server, client); return -1; }
return 0;
}
if (control_client_arm_timer(client, 0) < 0) { close_client(server, client); return -1; }
}
return 0;
}
/* ============================================================================
@@ -973,6 +1032,7 @@ static void send_conn_list(struct control_server* server, struct control_client*
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate connection list buffer");
client->failed = 1;
return;
}
@@ -1012,7 +1072,11 @@ static void send_socket_list(struct control_server* server, struct control_clien
if (!instance || !instance->etcp_sockets) {
uint16_t rsp_size = ETCPMON_SOCKET_LIST_SIZE(0);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) return;
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate empty socket list fd=%d", (int)client->fd);
client->failed = 1;
return;
}
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_socket_list),
@@ -1044,6 +1108,7 @@ static void send_socket_list(struct control_server* server, struct control_clien
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate socket list buffer");
client->failed = 1;
return;
}
@@ -1135,6 +1200,7 @@ static void send_metrics(struct control_server* server, struct control_client* c
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate metrics buffer");
client->failed = 1;
return;
}
@@ -1387,7 +1453,11 @@ static void send_error(struct control_client* client, uint8_t error_code, const
uint16_t rsp_size = ETCPMON_ERROR_SIZE(msg_len);
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) return;
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate control error fd=%d seq=%u", (int)client->fd, seq_id);
client->failed = 1;
return;
}
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(struct etcpmon_rsp_error) + msg_len + 1,
@@ -1428,7 +1498,11 @@ static void send_debug_config(struct control_server* server, struct control_clie
uint16_t payload_size = 2 + names_len + cat_count;
uint16_t rsp_size = sizeof(struct etcpmon_msg_header) + payload_size;
uint8_t* buffer = (uint8_t*)u_malloc(rsp_size);
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer"); return; }
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate debug config buffer");
client->failed = 1;
return;
}
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, payload_size, ETCPMON_RSP_DEBUG_CONFIG, seq_id);
@@ -1468,16 +1542,29 @@ static struct ETCP_CONN* find_connection_by_peer_id(struct UTUN_INSTANCE* instan
static void send_single_node_info(struct control_server* server, struct control_client* client, struct TOPO_GROUP_NODE* nq) {
if (!server || !nq) return;
struct TOPO_NODE* ni = topo_node_registry_find(server->instance->topo_groups, nq->node_id);
if (!ni) return;
uint8_t buf[8192];
if (!ni) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Node snapshot has no registry entry fd=%d node=%016llx", (int)client->fd,
(unsigned long long)nq->node_id);
client->failed = 1;
return;
}
uint8_t buf[TOPO_NODE_WIRE_MAX_SIZE + 2];
int ser_len = topo_node_serialize(ni, nq, TOPO_GROUP_UTUN, TOPO_FLAG_SEND_SUBNETS, buf + 2, sizeof(buf) - 2, 0);
if (ser_len < 0) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to serialize node info"); return; }
if (ser_len < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to serialize node info node=%016llx", (unsigned long long)nq->node_id);
client->failed = 1;
return;
}
buf[0] = CTRL_BGP_CMD; buf[1] = CTRL_BGP_SUBCMD;
size_t data_size = 2 + (size_t)ser_len;
size_t np_size = sizeof(struct etcpmon_msg_header) + data_size;
uint8_t* buffer = (uint8_t*)u_malloc(np_size);
if (!buffer) { DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node info buffer"); return; }
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node info buffer");
client->failed = 1;
return;
}
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, (uint16_t)data_size, ETCPMON_RSP_NODE_INFO, 0);
@@ -1488,61 +1575,148 @@ static void send_single_node_info(struct control_server* server, struct control_
static void control_server_send_node_list(struct control_server* server, struct control_client* client, uint8_t seq_id) {
struct TOPO_GROUP* group = topo_groups_get_default(server->instance->topo_groups);
if (!group) { send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id); return; }
/* Send local node first */
if (group->local_node) send_single_node_info(server, client, group->local_node);
/* Send all known remote nodes */
struct ll_entry* e = group->nodes ? group->nodes->head : NULL;
while (e) {
struct TOPO_GROUP_NODE* nq = (struct TOPO_GROUP_NODE*)e;
send_single_node_info(server, client, nq);
e = e->next;
if (!group) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Node subscription unavailable fd=%d seq=%u", (int)client->fd, seq_id);
send_error(client, ETCPMON_ERR_SERVER_BUSY, "BGP not available", seq_id);
return;
}
size_t count = (group->nodes ? queue_entry_count(group->nodes) : 0) + (group->local_node != NULL);
if (count > UINT32_MAX / sizeof(uint64_t)) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Node snapshot too large fd=%d nodes=%zu", (int)client->fd, count);
client->failed = 1;
return;
}
uint64_t* ids = count ? u_malloc(count * sizeof(*ids)) : NULL;
if (count && !ids) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Node snapshot allocation failed fd=%d nodes=%zu", (int)client->fd, count);
client->failed = 1;
return;
}
size_t used = 0;
if (group->local_node) ids[used++] = group->local_node->node_id;
for (struct ll_entry* e = group->nodes ? group->nodes->head : NULL; e; e = e->next) {
if (used >= count) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Node snapshot count mismatch fd=%d count=%zu", (int)client->fd, count);
u_free(ids); client->failed = 1;
return;
}
ids[used++] = ((struct TOPO_GROUP_NODE*)e)->node_id;
}
u_free(client->snapshot_ids);
client->snapshot_ids = ids; client->snapshot_count = used; client->snapshot_index = 0;
client->snapshot_active = 1; client->snapshot_seq = seq_id; client->subscribed_nodes = 1;
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Node snapshot started fd=%d seq=%u nodes=%zu id_bytes=%zu",
(int)client->fd, seq_id, used, used * sizeof(*ids));
}
/* Send end-of-list marker */
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header));
if (!buffer) return;
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, 0, ETCPMON_RSP_NODE_INFO_END, seq_id);
control_client_send(server, client, buffer, sizeof(struct etcpmon_msg_header));
/* Продолжение producer после опустошения TX; за один вызов создаётся максимум один кадр. */
static void control_output_ready(struct ll_queue* queue, void* arg) {
struct control_client* client = arg;
struct control_server* server = client->server;
if (client->failed) { close_client(server, client); return; }
if (queue->head) {
if (control_wait_output(client) < 0) close_client(server, client);
return;
}
struct TOPO_GROUP* group = topo_groups_get_default(server->instance->topo_groups);
if ((client->snapshot_active || client->dirty_count) && !group) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Subscribed UTUN group disappeared fd=%d", (int)client->fd);
close_client(server, client);
return;
}
while (client->snapshot_active && client->snapshot_index < client->snapshot_count && !queue->head && !client->failed) {
uint64_t id = client->snapshot_ids[client->snapshot_index++];
struct TOPO_GROUP_NODE* node = group->local_node && group->local_node->node_id == id ?
group->local_node : topo_node_find_by_id(group, id);
if (node) send_single_node_info(server, client, node);
else DEBUG_DEBUG(DEBUG_CATEGORY_CONTROL, "Snapshot skips removed node fd=%d node=%016llx", (int)client->fd,
(unsigned long long)id);
}
if (client->snapshot_active && client->snapshot_index == client->snapshot_count && !queue->head && !client->failed) {
uint8_t* buffer = u_malloc(sizeof(struct etcpmon_msg_header));
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node list end fd=%d", (int)client->fd);
client->failed = 1;
} else {
etcpmon_build_header((void*)buffer, 0, ETCPMON_RSP_NODE_INFO_END, client->snapshot_seq);
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Node snapshot queued fd=%d seq=%u nodes=%zu",
(int)client->fd, client->snapshot_seq, client->snapshot_count);
client->snapshot_active = 0;
u_free(client->snapshot_ids); client->snapshot_ids = NULL;
client->snapshot_count = 0; client->snapshot_index = 0;
control_client_send(server, client, buffer, sizeof(struct etcpmon_msg_header));
}
}
while (!client->snapshot_active && client->dirty_count && !queue->head && !client->failed) {
uint64_t id = client->dirty_ids[0];
int removed = client->dirty_removed[0];
client->dirty_count--;
memmove(client->dirty_ids, client->dirty_ids + 1, client->dirty_count * sizeof(*client->dirty_ids));
memmove(client->dirty_removed, client->dirty_removed + 1, client->dirty_count * sizeof(*client->dirty_removed));
struct TOPO_GROUP_NODE* node = group->local_node && group->local_node->node_id == id ?
group->local_node : topo_node_find_by_id(group, id);
if (!removed && node) send_single_node_info(server, client, node);
else {
size_t size = sizeof(struct etcpmon_msg_header) + sizeof(id);
uint8_t* buffer = u_malloc(size);
if (!buffer) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to allocate node removal fd=%d", (int)client->fd);
client->failed = 1;
break;
}
etcpmon_build_header((void*)buffer, sizeof(id), ETCPMON_RSP_NODE_REMOVED, 0);
memcpy(buffer + sizeof(struct etcpmon_msg_header), &id, sizeof(id));
control_client_send(server, client, buffer, (uint16_t)size);
}
}
if (client->failed) { close_client(server, client); return; }
if (queue->head) return;
if (handle_client_data(server, client) < 0) return;
if (queue->head || client->snapshot_active || client->dirty_count) return;
if (client->read_paused) {
if (uasync_set_socket_read(server->ua, client->socket_id, 1) < 0) {
DEBUG_ERROR(DEBUG_CATEGORY_CONTROL, "Failed to resume control reads fd=%d", (int)client->fd);
close_client(server, client);
return;
}
client->read_paused = 0;
DEBUG_DEBUG(DEBUG_CATEGORY_CONTROL, "Control reads resumed fd=%d buffered=%u", (int)client->fd, client->recv_len);
}
if (client->timer_is_tx && control_client_arm_timer(client, 0) < 0) close_client(server, client);
}
DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Sent node list to client (seq=%d)", seq_id);
/* Сохраняет только последнее событие узла. Ограниченный набор не растёт от повторов одного ID. */
static int control_mark_node(struct control_client* client, uint64_t id, int removed) {
size_t index = 0;
while (index < client->dirty_count && client->dirty_ids[index] != id) index++;
if (index == CONTROL_NODE_DIRTY_MAX) {
DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Control subscriber overwhelmed fd=%d dirty_nodes=%zu queued_bytes=%zu",
(int)client->fd, client->dirty_count, queue_total_bytes(client->output_queue));
return -1;
}
if (index == client->dirty_count) client->dirty_count++;
client->dirty_ids[index] = id; client->dirty_removed[index] = removed != 0;
return control_wait_output(client);
}
void control_server_notify_node_change(struct control_server* server, struct TOPO_GROUP_NODE* node) {
if (!server || !node) return;
struct control_client* client = server->clients;
while (client) {
if (client->subscribed_nodes) send_single_node_info(server, client, node);
client = client->next;
struct control_client* next = client->next;
if (client->subscribed_nodes && control_mark_node(client, node->node_id, 0) < 0) close_client(server, client);
client = next;
}
}
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id) {
if (!server) return;
uint8_t* buffer = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
if (!buffer) return;
struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer;
etcpmon_build_header(hdr, sizeof(uint64_t), ETCPMON_RSP_NODE_REMOVED, 0);
*(uint64_t*)(buffer + sizeof(*hdr)) = node_id;
struct control_client* client = server->clients;
while (client) {
if (client->subscribed_nodes) {
/* Every client needs its own copy since ownership transfers to the queue */
uint8_t* copy = (uint8_t*)u_malloc(sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
if (copy) {
memcpy(copy, buffer, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
control_client_send(server, client, copy, sizeof(struct etcpmon_msg_header) + sizeof(uint64_t));
}
}
client = client->next;
struct control_client* next = client->next;
if (client->subscribed_nodes && control_mark_node(client, node_id, 1) < 0) close_client(server, client);
client = next;
}
u_free(buffer);
}
/* ============================================================================
+28 -5
View File
@@ -2,7 +2,9 @@
* control_server.h - Control Socket Server for ETCP Monitoring
*
* TCP-мониторинг ETCP: init → обработка команд в uasync-потоке → shutdown.
* Метрики отправляются по запросу; доступ — по IPv4 control_allow, idle timeout — 30 секунд.
* Метрики отправляются по запросу; доступ — по IPv4 [control] allow, idle timeout — 30 секунд без команд.
* Шифрования/аутентификации протокола нет. Общий wire-контракт — etcpmon_protocol.h.
* Client владеет своим сокетом, таймером и очередью TX; очередь сейчас без лимита/backpressure.
*/
#ifndef CONTROL_SERVER_H
@@ -10,6 +12,7 @@
#include "../tools/etcpmon/etcpmon_protocol.h"
#include "../lib/socket_compat.h"
#include "../lib/ll_queue.h"
#include <stdint.h>
#include <stdio.h>
@@ -26,6 +29,8 @@ struct ll_queue;
/* Forward declaration */
struct control_server;
#define CONTROL_NODE_DIRTY_MAX 256
/* Client connection state */
struct control_client {
struct control_client* next; /* Linked list */
@@ -40,11 +45,23 @@ struct control_client {
/* Selected connection */
uint64_t selected_peer_id; /* 0 = none selected */
struct ll_queue* output_queue; /* Output queue for async sends */
struct ll_queue* output_queue; /* Владеет TX-буферами; сейчас без ограничения объёма/числа сообщений */
uint16_t output_offset; /* Отправленные байты головы очереди; dgram/len не меняются */
void* idle_timer; /* Client idle timeout */
uint8_t subscribed_nodes; /* 1 = subscribed to node updates */
uint8_t write_registered; /* 1 = write events registered in uasync */
uint8_t read_paused; /* Чтение/разбор ждут пустой TX-очереди */
uint8_t failed; /* Ошибка producer; владелец callback закроет client после возврата */
uint8_t timer_is_tx; /* Таймер контролирует прогресс TX, иначе отсутствие команд */
struct queue_waiter_handle output_waiter; /* Отложенное продолжение; отменить до освобождения client */
uint64_t* snapshot_ids; /* ID начального списка; никаких заимствованных TOPO_GROUP_NODE* */
size_t snapshot_count;
size_t snapshot_index; /* Следующий ID для сериализации */
uint8_t snapshot_active; /* 1 до постановки NODE_INFO_END */
uint8_t snapshot_seq; /* seq запроса для NODE_INFO_END */
uint64_t dirty_ids[CONTROL_NODE_DIRTY_MAX]; /* Последние изменения объединяются по ID */
uint8_t dirty_removed[CONTROL_NODE_DIRTY_MAX]; /* Последнее событие: 1 = удаление, 0 = обновление */
size_t dirty_count; /* При переполнении подписчик отключается, события не теряются молча */
};
/* Control server state */
@@ -79,10 +96,11 @@ struct control_server {
* ua - uasync context for event handling
* instance - Parent utun instance
* bind_addr - Address to bind (from config control_sock)
* max_clients - Maximum concurrent client connections
* max_clients - Maximum concurrent client connections; 0 selects 8
*
* Returns:
* 0 on success, -1 on error
* 0 on success, -1 on error. Server copies bind_addr; caller retains ua/instance/server.
* With valid arguments, shutdown also cleans resources left by a failed initialization.
*/
int control_server_init(struct control_server* server,
struct UASYNC* ua,
@@ -100,7 +118,8 @@ int control_server_init(struct control_server* server,
void control_server_shutdown(struct control_server* server);
/* Разобрать накопленные команды всех клиентов в uasync-потоке.
* Чтение сокета также запускает разбор; метрики отправляются только по CMD_GET_METRICS.
* Чтение сокета само запускает разбор; отдельный периодический вызов не требуется.
* Функция лишь повторно разбирает уже принятые данные, не читает сокеты и не рассылает метрики по таймеру.
*
* Parameters:
* server - Server state
@@ -117,7 +136,11 @@ void control_server_process_updates(struct control_server* server);
*/
uint32_t control_server_get_client_count(const struct control_server* server);
/* В uasync-потоке поставить подписанным клиентам снимок node (заимствован на время вызова).
* NODE_INFO содержит 01 04 + topo_node_serialize для UTUN с подсетями; seq=0.
* Неизвестный в registry узел не отправляется. Буферы ответов принадлежат очередям клиентов. */
void control_server_notify_node_change(struct control_server* server, struct TOPO_GROUP_NODE* node);
/* В uasync-потоке поставить подписанным клиентам NODE_REMOVED с node_id и seq=0. */
void control_server_notify_node_removed(struct control_server* server, uint64_t node_id);
#ifdef __cplusplus
+4 -2
View File
@@ -1200,9 +1200,11 @@ static int topo_group_routes_changed(struct TOPO_GROUP* group, struct TOPO_GROUP
topo_fire_nodeinfo_cbk(group->instance, group, node);
if (!node->paths) {
event = TOPO_NODE_EVENT_REMOVE;
if (group->instance->control_srv) control_server_notify_node_removed(group->instance->control_srv, id);
if (group->group_id == TOPO_GROUP_UTUN && group->instance->control_srv)
control_server_notify_node_removed(group->instance->control_srv, id);
topo_nodeq_remove_node(group, node);
} else if (group->instance->control_srv) control_server_notify_node_change(group->instance->control_srv, node);
} else if (group->group_id == TOPO_GROUP_UTUN && group->instance->control_srv)
control_server_notify_node_change(group->instance->control_srv, node);
for (struct topo_node_cbk_entry* c = group->node_cbks; c; c = c->next) c->fn(group, id, event, c->arg);
topo_group_publish_route(group, id);
topo_recovery_changed(group);