diff --git a/tools/etcpmon/Makefile b/tools/etcpmon/Makefile index 7f40e719..524904d3 100644 --- a/tools/etcpmon/Makefile +++ b/tools/etcpmon/Makefile @@ -24,6 +24,8 @@ $(TARGET): $(OBJS) @echo Linking $@ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +$(OBJS): $(wildcard *.h) + %.o: %.c @echo Compiling $< $(CC) $(CFLAGS) -c -o $@ $< diff --git a/tools/etcpmon/etcpmon_client.c b/tools/etcpmon/etcpmon_client.c index 5060dfd5..2967fdfd 100644 --- a/tools/etcpmon/etcpmon_client.c +++ b/tools/etcpmon/etcpmon_client.c @@ -468,6 +468,9 @@ int etcpmon_client_send_request(struct etcpmon_client* client, (unsigned long long)get_timestamp_ms(), type, sent, total_size, sent == SOCKET_ERROR ? WSAGetLastError() : 0); fflush(client->log_file); } + /* После частичной отправки следующий пакет уже нельзя писать в этот поток. */ + etcpmon_client_disconnect(client); + if (client->on_disconnected) client->on_disconnected(client->user_data); return -1; } @@ -626,7 +629,8 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type, } case ETCPMON_RSP_ERROR: { - if (payload_size >= sizeof(struct etcpmon_rsp_error)) { + if (payload_size > sizeof(struct etcpmon_rsp_error) && + memchr(payload + sizeof(struct etcpmon_rsp_error), 0, payload_size - sizeof(struct etcpmon_rsp_error))) { struct etcpmon_rsp_error* rsp = (struct etcpmon_rsp_error*)payload; const char* msg = (const char*)(payload + sizeof(*rsp)); @@ -639,12 +643,15 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type, if (client->on_error) { client->on_error(msg, client->user_data); } + } else if (client->log_file) { + fprintf(client->log_file, "[ERROR] Invalid RSP_ERROR string: size=%u\n", payload_size); } break; } case ETCPMON_RSP_ACTION_RESULT: { - if (payload_size >= sizeof(struct etcpmon_rsp_action_result)) { + if (payload_size > sizeof(struct etcpmon_rsp_action_result) && + memchr(payload + sizeof(struct etcpmon_rsp_action_result), 0, payload_size - sizeof(struct etcpmon_rsp_action_result))) { const char* text = (const char*)(payload + sizeof(struct etcpmon_rsp_action_result)); if (client->log_file) { @@ -656,15 +663,23 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type, if (client->on_action_result) { client->on_action_result(text, client->user_data); } + } else if (client->log_file) { + fprintf(client->log_file, "[ERROR] Invalid RSP_ACTION_RESULT string: size=%u\n", payload_size); } break; } case ETCPMON_RSP_DEBUG_CONFIG: { - if (payload_size >= 2) { + const uint8_t* names_end = payload_size > 2 ? memchr(payload + 2, 0, payload_size - 2) : NULL; + if (!names_end || payload[1] > ETCPMON_MAX_DEBUG_CATEGORIES || + (size_t)(names_end - payload) + 1 + payload[1] != payload_size) { + if (client->log_file) fprintf(client->log_file, "[ERROR] Invalid RSP_DEBUG_CONFIG: size=%u\n", payload_size); + break; + } + { client->debug_global_level = payload[0]; client->debug_category_count = payload[1]; - uint16_t names_len = (uint16_t)strlen((const char*)(payload + 2)) + 1; + uint16_t names_len = (uint16_t)(names_end - payload - 2) + 1; if (2 + names_len <= payload_size) { size_t copy_len = names_len < sizeof(client->debug_category_names) ? names_len : sizeof(client->debug_category_names) - 1; memcpy(client->debug_category_names, payload + 2, copy_len); @@ -1000,8 +1015,10 @@ int etcpmon_client_send_debug_config(struct etcpmon_client* client, memcpy(payload + 2, levels, count); uint16_t payload_size = 2 + count; - struct etcpmon_msg_header hdr; - etcpmon_build_header(&hdr, payload_size, ETCPMON_CMD_SET_DEBUG_CONFIG, 0); + uint8_t buffer[sizeof(struct etcpmon_msg_header) + sizeof(payload)]; + struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer; + etcpmon_build_header(hdr, payload_size, ETCPMON_CMD_SET_DEBUG_CONFIG, 0); + memcpy(buffer + sizeof(*hdr), payload, payload_size); if (client->log_file) { fprintf(client->log_file, "%llu: [LOG] CMD_SET_DEBUG_CONFIG global=%d count=%d\n", @@ -1009,10 +1026,18 @@ int etcpmon_client_send_debug_config(struct etcpmon_client* client, fflush(client->log_file); } - int sent = send(client->sock, (const char*)&hdr, sizeof(hdr), 0); - if (sent != sizeof(hdr)) return -1; - sent = send(client->sock, (const char*)payload, payload_size, 0); - if (sent != payload_size) return -1; + int total_size = sizeof(*hdr) + payload_size; + int sent = send(client->sock, (const char*)buffer, total_size, 0); + if (sent != total_size) { + if (client->log_file) { + fprintf(client->log_file, "[ERROR] Failed to send debug config: sent=%d/%d error=%d\n", + sent, total_size, sent == SOCKET_ERROR ? WSAGetLastError() : 0); + fflush(client->log_file); + } + etcpmon_client_disconnect(client); + if (client->on_disconnected) client->on_disconnected(client->user_data); + return -1; + } client->debug_global_level = global_level; for (uint8_t i = 0; i < count; i++) client->debug_category_levels[i] = levels[i]; return 0; diff --git a/tools/etcpmon/test_etcpmon.c b/tools/etcpmon/test_etcpmon.c index b3b2367d..7a8cfe06 100644 --- a/tools/etcpmon/test_etcpmon.c +++ b/tools/etcpmon/test_etcpmon.c @@ -129,6 +129,7 @@ int main(void) { SendMessage(app.hListConnections, LB_SETCURSEL, 1, 0); OnConnectionSelect(&app); assert(client.selected_peer_id == list[1].peer_node_id); + assert(drain_commands(ETCPMON_CMD_SELECT_CONN) == 2); struct etcpmon_conn_info reordered[3] = {list[1], list[2], list[0]}; strcpy(reordered[0].name, "renamed"); deliver_list(reordered, 3); @@ -202,6 +203,34 @@ int main(void) { assert(client.pending_head == NULL && !client.connected && app.need_initial_request); assert(SendMessage(app.hListConnections, LB_GETCOUNT, 0, 0) == 0); + puts("Malformed debug responses are rejected and socket snapshots are not capped at 16"); + client.debug_config_dirty = 0; + const uint8_t invalid_names[] = {1, 0, 'x'}; + handle_response(&client, ETCPMON_RSP_DEBUG_CONFIG, invalid_names, sizeof(invalid_names)); + assert(!client.debug_config_dirty); + const uint8_t missing_levels[] = {1, 2, 'a', ',', 'b', 0, 1}; + handle_response(&client, ETCPMON_RSP_DEBUG_CONFIG, missing_levels, sizeof(missing_levels)); + assert(!client.debug_config_dirty); + const uint8_t valid_debug[] = {1, 2, 'a', ',', 'b', 0, 1, 2}; + handle_response(&client, ETCPMON_RSP_DEBUG_CONFIG, valid_debug, sizeof(valid_debug)); + assert(client.debug_config_dirty && client.debug_category_levels[1] == 2); + uint8_t socket_payload[1 + 17 * sizeof(struct etcpmon_socket_info)] = {17}; + struct etcpmon_socket_info* sockets = (struct etcpmon_socket_info*)(socket_payload + 1); + for (int i = 0; i < 17; i++) { + strcpy(sockets[i].name, "socket"); + strcpy(sockets[i].ip, "127.0.0.1"); + } + handle_response(&client, ETCPMON_RSP_SOCKET_LIST, socket_payload, sizeof(socket_payload)); + assert(client.socket_count == 17 && SendMessage(app.hListSockets, LB_GETCOUNT, 0, 0) == 17); + + puts("A send failure clears pending requests and allows another connection"); + closesocket(server_sock); + connect_client(); + assert(etcpmon_client_send_request(&client, ETCPMON_CMD_ACTION, NULL, 0, request_callback, NULL) == 0); + assert(shutdown(client.sock, SD_SEND) == 0); + assert(etcpmon_client_send_request(&client, ETCPMON_CMD_ACTION, NULL, 0, request_callback, NULL) == -1); + assert(!client.connected && client.pending_head == NULL && client.pending_tail == NULL && app.need_initial_request); + etcpmon_client_cleanup(&client); closesocket(server_sock); closesocket(listener);