From 35871891db196dd5783b1e440e4525ddc3a4e10b Mon Sep 17 00:00:00 2001 From: Evgeny Date: Wed, 1 Jul 2026 12:19:50 +0300 Subject: [PATCH] action nodes: send BGP node dump as text response to GUI, display in popup - route_node_format_all(): format all BGP nodes to snprintf buffer - control_server: 'nodes' action sends ETCPMON_RSP_ACTION_RESULT (0x89) with text - etcpmon_protocol.h: new response type + struct, increase MAX_MSG_SIZE to 32K - etcpmon_client: on_action_result callback, send_action via send_request with seq_id - etcpmon_gui: ETCPMonActionResult popup window class with EDIT multiline, on_action_result creates popup (same size as main) or appends text + scrolls --- src/control_server.c | 20 ++++- src/route_node.c | 145 +++++++++++++++++++++++++++++++ src/route_node.h | 1 + tools/etcpmon/etcpmon_client.c | 66 +++++++------- tools/etcpmon/etcpmon_client.h | 2 + tools/etcpmon/etcpmon_gui.c | 77 ++++++++++++++++ tools/etcpmon/etcpmon_gui.h | 4 + tools/etcpmon/etcpmon_protocol.h | 13 ++- 8 files changed, 291 insertions(+), 37 deletions(-) diff --git a/src/control_server.c b/src/control_server.c index c5693242..23477dbb 100644 --- a/src/control_server.c +++ b/src/control_server.c @@ -767,7 +767,25 @@ static void handle_client_data(struct control_server* server, struct control_cli route_bgp_request_nat_check_all(server->instance->bgp); } else if (strncmp(cmd->action, "nodes", 5) == 0) { DEBUG_INFO(DEBUG_CATEGORY_CONTROL, "Action 'nodes' received, dumping all BGP nodes"); - route_node_dump_all(server->instance->bgp); + uint16_t text_max = (ETCPMON_MAX_MSG_SIZE > 1024) ? ETCPMON_MAX_MSG_SIZE - 512 : 1024; + char* text = (char*)u_malloc(text_max); + if (text) { + int text_len = route_node_format_all(server->instance->bgp, text, text_max); + if (text_len < 0) text_len = 0; + text[text_len] = '\0'; + uint16_t rsp_size = ETCPMON_ACTION_RESULT_SIZE(text_len); + uint8_t* buffer = (uint8_t*)u_malloc(rsp_size); + if (buffer) { + struct etcpmon_msg_header* rsp_hdr = (struct etcpmon_msg_header*)buffer; + etcpmon_build_header(rsp_hdr, sizeof(struct etcpmon_rsp_action_result) + text_len + 1, + ETCPMON_RSP_ACTION_RESULT, req_seq); + struct etcpmon_rsp_action_result* rsp = (struct etcpmon_rsp_action_result*)(buffer + sizeof(*rsp_hdr)); + rsp->code = 0; + memcpy(buffer + sizeof(*rsp_hdr) + sizeof(*rsp), text, text_len + 1); + control_client_send(server, client, buffer, rsp_size); + } + u_free(text); + } } else { DEBUG_WARN(DEBUG_CATEGORY_CONTROL, "Unknown action received: '%s'", cmd->action); } diff --git a/src/route_node.c b/src/route_node.c index a0d6b24e..2640bdd6 100644 --- a/src/route_node.c +++ b/src/route_node.c @@ -280,6 +280,151 @@ void route_node_dump_all(struct ROUTE_BGP* bgp) { } } +int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size) { + if (!bgp || !bgp->nodes || !buf || buf_size == 0) return 0; + int pos = 0; + + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + "=== BGP Nodes dump: %d nodes ===\n", queue_entry_count(bgp->nodes)); + + int idx = 0; + struct ll_entry* e = bgp->nodes->head; + while (e && (size_t)pos < buf_size) { + struct NODEINFO_Q* nq = (struct NODEINFO_Q*)e; + struct NODEINFO* ni = &nq->node; + int is_local = (bgp->local_node == nq); + const char* name_s = (ni->node_name_len > 0) ? (const char*)((const uint8_t*)ni + sizeof(struct NODEINFO)) : ""; + + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + "--- Node #%d: id=%016llx name=\"%.*s\" ver=%u dirty=%u alien=%u%s ---\n", + idx, (unsigned long long)ni->node_id, ni->node_name_len, name_s, + ni->ver, nq->dirty, nq->alien, is_local ? " [LOCAL]" : ""); + + if (nq->best_socket) + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + " best_socket=%p\n", (void*)nq->best_socket); + + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " pubkey: "); + for (int k = 0; k < SC_PUBKEY_SIZE; k++) + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->public_key[k]); + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n"); + + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, " ed25519: "); + for (int k = 0; k < SC_PUBKEY_SIZE; k++) + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "%02x", ni->ed25519_public_key[k]); + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "\n"); + +#define FMT_ADD(fmt, ...) \ + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, fmt, ##__VA_ARGS__) + + const struct NODEINFO_IPV4_SOCKET_META* v4sm; + int v4sm_cnt = get_node_v4_sockets_meta(nq, &v4sm); + for (int i = 0; i < v4sm_cnt; i++) + FMT_ADD(" v4sock[%d]: id=%u type=%u\n", i, v4sm[i].id, v4sm[i].type); + + const struct NODEINFO_IPV4_ADDR* v4a; + int v4a_cnt = get_node_v4_addrs(nq, &v4a); + if (v4a_cnt > 0) { + const char* atn[] = {"INTERFACE","NAT","REAL"}; + for (int i = 0; i < v4a_cnt; i++) + FMT_ADD(" v4addr[%d]: %s:%u %s sock=%u\n", + i, ip_to_str(v4a[i].addr, AF_INET).str, v4a[i].port, + (v4a[i].type<=2)?atn[v4a[i].type]:"?", v4a[i].socket_id); + } + + const struct NODEINFO_IPV6_SOCKET_META* v6sm; + int v6sm_cnt = get_node_v6_sockets_meta(nq, &v6sm); + for (int i = 0; i < v6sm_cnt; i++) + FMT_ADD(" v6sock[%d]: id=%u type=%u\n", i, v6sm[i].id, v6sm[i].type); + + const struct NODEINFO_IPV6_ADDR* v6a; + int v6a_cnt = get_node_v6_addrs(nq, &v6a); + for (int i = 0; i < v6a_cnt; i++) + FMT_ADD(" v6addr[%d]: %s:%u type=%u sock=%u\n", + i, ip_to_str(v6a[i].addr, AF_INET6).str, v6a[i].port, + v6a[i].type, v6a[i].socket_id); + + const struct NODEINFO_IPV4_SUBNET* sub4; + int sub4_cnt = get_node_routes(nq, &sub4); + for (int i = 0; i < sub4_cnt; i++) + FMT_ADD(" v4sub[%d]: %s/%d\n", i, + ip_to_str(sub4[i].addr, AF_INET).str, sub4[i].prefix_length); + + const struct NODEINFO_IPV6_SUBNET* sub6; + int sub6_cnt = get_node_v6_routes(nq, &sub6); + for (int i = 0; i < sub6_cnt; i++) + FMT_ADD(" v6sub[%d]: %s/%d\n", i, + ip_to_str(sub6[i].addr, AF_INET6).str, sub6[i].prefix_length); + + if (ni->tranzit_nodes > 0) { + const uint8_t* dyn = node_dyn_start(ni); + dyn = skip_name(dyn, ni->node_name_len); + dyn = skip_v4_sockets_meta(dyn, ni->local_v4_sockets); + dyn = skip_v4_addrs(dyn, ni->local_v4_addrs); + dyn = skip_v6_sockets_meta(dyn, ni->local_v6_sockets); + dyn = skip_v6_addrs(dyn, ni->local_v6_addrs); + dyn = skip_v4_subnets(dyn, ni->local_v4_subnets); + dyn += ni->local_v6_subnets * sizeof(struct NODEINFO_IPV6_SUBNET); + const struct NODEINFO_TRANZIT_NODE* tr = (const struct NODEINFO_TRANZIT_NODE*)dyn; + for (uint8_t i = 0; i < ni->tranzit_nodes; i++) + FMT_ADD(" tranzit[%d]: node=%016llx rtt=%u linkq=%u\n", + i, (unsigned long long)tr[i].node_id, tr[i].rtt, tr[i].link_q); + } + + if (nq->paths) { + int path_idx = 0; + struct ll_entry* pe = nq->paths->head; + while (pe) { + struct NODEINFO_PATH* path = (struct NODEINFO_PATH*)pe; + uint64_t* hop = (uint64_t*)((uint8_t*)path + sizeof(struct NODEINFO_PATH)); + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + " path[%d]: conn=%s hop_count=%u hops=[", + path_idx, path->conn ? path->conn->log_name : "NULL", path->hop_count); + for (uint8_t hi = 0; hi < path->hop_count; hi++) + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + "%s%016llx", hi ? "->" : "", (unsigned long long)hop[hi]); + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n"); + path_idx++; + pe = pe->next; + } + } + + struct NODE_CONNECTIVITY* c = &nq->connectivity; + if (is_node_connectivity_active(c)) { + FMT_ADD(" connectivity: probe=%s iface=%s(%u) nat=%s(%u) real=%s(%u) pend=%u iface_rtt=%u nat_rtt=%u real_rtt=%u start_tb=%llu ping_req_tb=%llu\n", + probe_status_str(c->probe_status), + probe_result_str(c->interface_status), c->interface_min_rtt, + probe_result_str(c->nat_status), c->nat_min_rtt, + probe_result_str(c->real_status), c->real_min_rtt, + c->pending_count, c->interface_min_rtt, c->nat_min_rtt, c->real_min_rtt, + (unsigned long long)c->probe_start_time, (unsigned long long)c->ping_req_time); + } + + if (nq->conn_mgr_type != CONN_TYPE_NONE) { + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + " conn_mgr: type=%s intermediates=[", + conn_mgr_type_str(nq->conn_mgr_type)); + for (uint8_t i = 0; i < nq->conn_mgr_intermediariy_count; i++) + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, + "%s%016llx", i ? "," : "", + (unsigned long long)nq->conn_mgr_intermediaries[i]); + pos += snprintf(buf + pos, buf_size > (size_t)pos ? buf_size - pos : 0, "]\n"); + } + +#undef FMT_ADD + e = e->next; + idx++; + } + + if ((size_t)pos >= buf_size) { + pos = (int)buf_size - 14; + if (pos < 0) pos = 0; + pos += snprintf(buf + pos, buf_size - pos, "\n[TRUNCATED]\n"); + } + + return pos; +} + int route_bgp_update_my_nodeinfo(struct UTUN_INSTANCE* instance, struct ROUTE_BGP* bgp) { if (!instance || !bgp) { DEBUG_ERROR(DEBUG_CATEGORY_BGP, "route_bgp_update_my_nodeinfo: invalid args"); diff --git a/src/route_node.h b/src/route_node.h index eebb5185..9048e9c6 100644 --- a/src/route_node.h +++ b/src/route_node.h @@ -188,5 +188,6 @@ int get_node_v6_addrs(struct NODEINFO_Q *node, const struct NODEINFO_IPV6_ADDR * struct NODEINFO_Q* nodeinfo_find_by_id(struct ROUTE_BGP* bgp, uint64_t node_id); void route_node_dump_all(struct ROUTE_BGP* bgp); +int route_node_format_all(struct ROUTE_BGP* bgp, char* buf, size_t buf_size); #endif // ROUTE_NODE_H diff --git a/tools/etcpmon/etcpmon_client.c b/tools/etcpmon/etcpmon_client.c index 6c890eeb..9531f9de 100644 --- a/tools/etcpmon/etcpmon_client.c +++ b/tools/etcpmon/etcpmon_client.c @@ -355,6 +355,11 @@ int etcpmon_client_request_metrics(struct etcpmon_client* client) { return 0; } +static void action_auto_callback(void* arg, uint8_t msg_type, const uint8_t* payload, uint16_t payload_size) { + (void)arg; (void)msg_type; (void)payload; (void)payload_size; + /* legacy callback is called via on_action_result in handle_response */ +} + int etcpmon_client_send_action(struct etcpmon_client* client, const char* action) { if (!client || client->sock == INVALID_SOCKET || !action) return -1; @@ -363,37 +368,8 @@ int etcpmon_client_send_action(struct etcpmon_client* client, const char* action strncpy(cmd.action, action, sizeof(cmd.action) - 1); cmd.action[sizeof(cmd.action) - 1] = '\0'; - struct etcpmon_msg_header hdr; - etcpmon_build_header(&hdr, sizeof(cmd), ETCPMON_CMD_ACTION, 0); - - if (client->log_file) { - log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr)); - fprintf(client->log_file, "%llu: [LOG] CMD_ACTION '%s'\n", - (unsigned long long)get_timestamp_ms(), cmd.action); - fflush(client->log_file); - } - - int sent = send(client->sock, (const char*)&hdr, sizeof(hdr), 0); - if (sent != sizeof(hdr)) { - if (client->log_file) { - fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_ACTION header: %d\n", - (unsigned long long)get_timestamp_ms(), WSAGetLastError()); - fflush(client->log_file); - } - return -1; - } - - sent = send(client->sock, (const char*)&cmd, sizeof(cmd), 0); - if (sent != sizeof(cmd)) { - if (client->log_file) { - fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_ACTION payload: %d\n", - (unsigned long long)get_timestamp_ms(), WSAGetLastError()); - fflush(client->log_file); - } - return -1; - } - - return 0; + return etcpmon_client_send_request(client, ETCPMON_CMD_ACTION, &cmd, sizeof(cmd), + action_auto_callback, NULL); } /* Send request with callback - queued version */ @@ -589,13 +565,13 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type, if (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)); - + if (client->log_file) { - fprintf(client->log_file, "%llu: [LOG] RSP_ERROR code=%d msg='%s'\n", + fprintf(client->log_file, "%llu: [LOG] RSP_ERROR code=%d msg='%s'\n", (unsigned long long)get_timestamp_ms(), rsp->error_code, msg); fflush(client->log_file); } - + if (client->on_error) { client->on_error(msg, client->user_data); } @@ -603,6 +579,23 @@ static void handle_response(struct etcpmon_client* client, uint8_t msg_type, break; } + case ETCPMON_RSP_ACTION_RESULT: { + if (payload_size >= sizeof(struct etcpmon_rsp_action_result)) { + const char* text = (const char*)(payload + sizeof(struct etcpmon_rsp_action_result)); + + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] RSP_ACTION_RESULT len=%u\n", + (unsigned long long)get_timestamp_ms(), payload_size); + fflush(client->log_file); + } + + if (client->on_action_result) { + client->on_action_result(text, client->user_data); + } + } + break; + } + case ETCPMON_RSP_DEBUG_CONFIG: { if (payload_size >= 2) { client->debug_global_level = payload[0]; @@ -692,7 +685,8 @@ static void handle_response_with_callback(struct etcpmon_client* client, uint8_t * Это позволяет использовать и старый код, и новый асинхронный send_request одновременно */ if (seq_id == 0 || msg_type == ETCPMON_RSP_CONN_LIST || msg_type == ETCPMON_RSP_SOCKET_LIST || msg_type == ETCPMON_RSP_DEBUG_CONFIG || - msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR) { + msg_type == ETCPMON_RSP_METRICS || msg_type == ETCPMON_RSP_ERROR || + msg_type == ETCPMON_RSP_ACTION_RESULT) { handle_response(client, msg_type, payload, payload_size); } } @@ -856,6 +850,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client, struct etcpmon_link_metrics*, uint8_t, void*), void (*on_error)(const char*, void*), + void (*on_action_result)(const char*, void*), void* user_data) { if (!client) return; @@ -865,6 +860,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client, client->on_socket_list = on_socket_list; client->on_metrics = on_metrics; client->on_error = on_error; + client->on_action_result = on_action_result; client->user_data = user_data; } diff --git a/tools/etcpmon/etcpmon_client.h b/tools/etcpmon/etcpmon_client.h index 1df6f9ee..36e83945 100644 --- a/tools/etcpmon/etcpmon_client.h +++ b/tools/etcpmon/etcpmon_client.h @@ -105,6 +105,7 @@ struct etcpmon_client { struct etcpmon_link_metrics* links, uint8_t links_count, void* user_data); void (*on_error)(const char* msg, void* user_data); + void (*on_action_result)(const char* text, void* user_data); void* user_data; uint64_t last_metrics_request_ms; @@ -182,6 +183,7 @@ void etcpmon_client_set_callbacks(struct etcpmon_client* client, struct etcpmon_link_metrics*, uint8_t, void*), void (*on_error)(const char*, void*), + void (*on_action_result)(const char*, void*), void* user_data); /* Get connection name by peer_id */ diff --git a/tools/etcpmon/etcpmon_gui.c b/tools/etcpmon/etcpmon_gui.c index a1dc992f..af3f6cbf 100644 --- a/tools/etcpmon/etcpmon_gui.c +++ b/tools/etcpmon/etcpmon_gui.c @@ -27,6 +27,7 @@ static struct etcpmon_app* g_app = NULL; /* Forward declarations */ static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); +static LRESULT CALLBACK ActionResultWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); static void CreateControls(struct etcpmon_app* app); static void OnConnect(struct etcpmon_app* app); static void OnDisconnect(struct etcpmon_app* app); @@ -47,6 +48,7 @@ static void on_metrics(struct etcpmon_rsp_metrics* metrics, struct etcpmon_link_metrics* links, uint8_t links_count, void* user_data); static void on_error(const char* msg, void* user_data); +static void on_action_result(const char* text, void* user_data); int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { if (!app) return -1; @@ -97,6 +99,20 @@ int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { wcexGraph.hIconSm = NULL; RegisterClassExA(&wcexGraph); + + /* Register ActionResult window class */ + WNDCLASSEXA wcexAR; + memset(&wcexAR, 0, sizeof(wcexAR)); + wcexAR.cbSize = sizeof(wcexAR); + wcexAR.style = CS_HREDRAW | CS_VREDRAW; + wcexAR.lpfnWndProc = ActionResultWndProc; + wcexAR.hInstance = hInstance; + wcexAR.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wcexAR.hCursor = LoadCursor(NULL, IDC_ARROW); + wcexAR.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wcexAR.lpszClassName = "ETCPMonActionResult"; + wcexAR.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + RegisterClassExA(&wcexAR); /* Initialize Winsock */ WSADATA wsaData; @@ -141,6 +157,7 @@ HWND etcpmon_gui_create_window(struct etcpmon_app* app) { etcpmon_client_set_callbacks(app->client, on_connected, on_disconnected, on_conn_list, on_socket_list, on_metrics, on_error, + on_action_result, app); } @@ -1302,6 +1319,60 @@ static void on_error(const char* msg, void* user_data) { struct etcpmon_app* app = (struct etcpmon_app*)user_data; etcpmon_gui_set_status(app, msg); } + +static LRESULT CALLBACK ActionResultWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + switch (message) { + case WM_CREATE: { + HWND hEdit = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_MULTILINE | ES_READONLY | WS_VSCROLL | ES_AUTOVSCROLL, + 0, 0, 0, 0, hWnd, (HMENU)1, ((CREATESTRUCTA*)lParam)->hInstance, NULL); + HFONT hFont = (HFONT)GetStockObject(ANSI_FIXED_FONT); + SendMessage(hEdit, WM_SETFONT, (WPARAM)hFont, TRUE); + SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)hEdit); + break; + } + case WM_SIZE: + if (GetWindowLongPtrA(hWnd, GWLP_USERDATA)) + MoveWindow((HWND)GetWindowLongPtrA(hWnd, GWLP_USERDATA), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE); + break; + case WM_CLOSE: + ShowWindow(hWnd, SW_HIDE); + return 0; + default: + return DefWindowProcA(hWnd, message, wParam, lParam); + } + return 0; +} + +static void on_action_result(const char* text, void* user_data) { + struct etcpmon_app* app = (struct etcpmon_app*)user_data; + if (!app || !app->hWndMain || !text || !text[0]) return; + + if (!IsWindow(app->hActionResultWnd)) { + RECT rc; + GetWindowRect(app->hWndMain, &rc); + int w = rc.right - rc.left; + int h = rc.bottom - rc.top; + + app->hActionResultWnd = CreateWindowExA(0, "ETCPMonActionResult", "Action Result", + WS_OVERLAPPEDWINDOW | WS_SIZEBOX, + rc.left, rc.top, w, h, + NULL, NULL, app->hInstance, NULL); + + app->hActionResultEdit = (HWND)GetWindowLongPtrA(app->hActionResultWnd, GWLP_USERDATA); + + ShowWindow(app->hActionResultWnd, SW_SHOW); + UpdateWindow(app->hActionResultWnd); + } + + if (IsWindow(app->hActionResultEdit)) { + SendMessageA(app->hActionResultEdit, EM_SETSEL, -1, -1); + SendMessageA(app->hActionResultEdit, EM_REPLACESEL, FALSE, (LPARAM)text); + + int lines = (int)SendMessage(app->hActionResultEdit, EM_GETLINECOUNT, 0, 0); + SendMessage(app->hActionResultEdit, EM_LINESCROLL, 0, lines); + } +} int etcpmon_gui_run(struct etcpmon_app* app) { if (!app || !app->hWndMain) return -1; ShowWindow(app->hWndMain, SW_SHOW); @@ -1332,6 +1403,12 @@ int etcpmon_gui_run(struct etcpmon_app* app) { continue; } } + if (hFocus == app->hEditAction) { + if (msg.wParam == VK_RETURN) { + OnAction(app); + continue; + } + } } TranslateMessage(&msg); DispatchMessageA(&msg); diff --git a/tools/etcpmon/etcpmon_gui.h b/tools/etcpmon/etcpmon_gui.h index 1eada938..a4cdf1f3 100644 --- a/tools/etcpmon/etcpmon_gui.h +++ b/tools/etcpmon/etcpmon_gui.h @@ -212,6 +212,10 @@ struct etcpmon_app { HWND hEditAction; HWND hBtnAction; + /* Action result popup */ + HWND hActionResultWnd; + HWND hActionResultEdit; + /* ETCP metrics controls */ HWND hEditEtcpName; HWND hEditEtcpRttLast; diff --git a/tools/etcpmon/etcpmon_protocol.h b/tools/etcpmon/etcpmon_protocol.h index 185a8c04..eb9408f4 100644 --- a/tools/etcpmon/etcpmon_protocol.h +++ b/tools/etcpmon/etcpmon_protocol.h @@ -31,7 +31,7 @@ extern "C" { #define NAT_VERIFIED_EIM 5 /* EIM NAT */ #define NAT_VERIFIED_STRICT 6 /* strict NAT */ #define NAT_VERIFIED_DIRECT 7 -#define ETCPMON_MAX_MSG_SIZE 4096 +#define ETCPMON_MAX_MSG_SIZE 32768 #define ETCPMON_MAX_CONN_NAME 32 #define ETCPMON_MAX_CONNECTIONS 250 #define ETCPMON_MAX_LINKS 16 @@ -63,6 +63,7 @@ extern "C" { #define ETCPMON_RSP_NODE_INFO 0x85 /* Single node info (payload: raw BGP_NODEINFO_PACKET) */ #define ETCPMON_RSP_NODE_INFO_END 0x86 /* End of node list (no payload) */ #define ETCPMON_RSP_NODE_REMOVED 0x88 /* Node removed (payload: uint64_t node_id) */ +#define ETCPMON_RSP_ACTION_RESULT 0x89 /* Action result text response */ #define ETCPMON_RSP_ERROR 0xFF /* Error response */ /* Error codes for RSP_ERROR */ @@ -309,6 +310,12 @@ struct etcpmon_cmd_set_debug { uint8_t levels[ETCPMON_MAX_DEBUG_CATEGORIES]; /* Actual length = category_count */ }; +/* RSP_ACTION_RESULT: Text result of an action command */ +struct etcpmon_rsp_action_result { + uint8_t code; /* 0=success, 1=error */ + /* Followed by null-terminated UTF-8 text string */ +}; + /* RSP_ERROR: Error response */ struct etcpmon_rsp_error { uint8_t error_code; /* Error code */ @@ -340,6 +347,10 @@ struct etcpmon_rsp_error { #define ETCPMON_ERROR_SIZE(msg_len) \ (sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_error) + (msg_len) + 1) +/* Calculate size of action result response */ +#define ETCPMON_ACTION_RESULT_SIZE(text_len) \ + (sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_action_result) + (text_len) + 1) + /* ============================================================================ * Utility Functions (optional, for both client and server) * ============================================================================ */