diff --git a/tools/etcpmon/IMPLEMENTATION.md b/tools/etcpmon/IMPLEMENTATION.md new file mode 100644 index 00000000..2204178f --- /dev/null +++ b/tools/etcpmon/IMPLEMENTATION.md @@ -0,0 +1,171 @@ +# ETCP Monitor - Implementation Summary + +## Overview +ETCP Monitor is a Windows GUI utility for real-time monitoring of ETCP connections in uTun. + +## Architecture + +### Protocol (Binary) +- **Format**: `[2 bytes: size] [1 byte: type] [payload...]` +- **Period**: 100ms updates +- **Transport**: TCP socket + +### Message Types +**Client → Server:** +- `0x01` - Request connection list +- `0x02` - Select connection by peer_node_id +- `0x03` - Request metrics +- `0x04` - Disconnect + +**Server → Client:** +- `0x81` - Connection list response +- `0x82` - Metrics response (ETCP + TUN + Links) +- `0xFF` - Error response + +## Files Created + +### Protocol (Shared) +- `tools/etcpmon/etcpmon_protocol.h` - Protocol definitions and structures + +### Server (uTun) +- `src/control_server.h` - Control server interface +- `src/control_server.c` - TCP server implementation +- Modified `src/utun_instance.c` - Integration with uTun lifecycle +- Modified `src/Makefile.am` - Added control_server.c to build +- Modified `lib/debug_config.h` - Added DEBUG_CATEGORY_CONTROL + +### Client (Windows GUI) +- `tools/etcpmon/etcpmon_client.h/c` - Network layer (Winsock) +- `tools/etcpmon/etcpmon_gui.h/c` - WinAPI GUI implementation +- `tools/etcpmon/etcpmon_main.c` - Entry point +- `tools/etcpmon/Makefile.mingw` - MinGW build +- `tools/etcpmon/build.bat` - Windows build script +- `tools/etcpmon/build.sh` - Unix build script +- `tools/etcpmon/clean.bat` - Clean script +- `tools/etcpmon/README.md` - Documentation + +### Test +- `tests/test_control_server.c` - Integration test for control server protocol + +### Library Fix +- `lib/u_async.c` - Modified to use select() on Windows instead of WSAPoll + +## Build Instructions + +### Windows (Recommended) +```cmd +cd tools\etcpmon +build.bat +``` + +### Unix/MinGW +```bash +cd tools/etcpmon +bash build.sh +``` + +## Usage + +### 1. Configure uTun +Add to `utun.conf`: +```ini +control_ip=127.0.0.1 +control_port=9090 +``` + +### 2. Start uTun +```bash +./utun +``` + +### 3. Start ETCP Monitor +```cmd +etcpmon.exe +``` + +### 4. Connect +- Enter server IP:port (default: 127.0.0.1:9090) +- Click "Connect" +- Select connection from list +- View real-time metrics + +## Metrics Displayed + +### ETCP Connection +- RTT (last, avg 10, avg 100) +- Jitter +- Bytes sent total +- Retransmission count +- ACK count +- Unacked bytes +- Links count + +### TUN Interface +- Bytes read/written +- Packets read/written +- Read/write errors + +### Links (per link) +- Status (UP/DOWN) +- Encrypt/decrypt errors +- Send/receive errors +- Total encrypted/decrypted bytes +- Bandwidth (Kbps) +- NAT changes count + +## Technical Details + +### Windows select() Implementation +On Windows, the uasync library now uses `select()` instead of `WSAPoll()` for socket monitoring. This resolves issues with accepted TCP sockets that caused WSAPoll to fail with error 10022 (WSAEINVAL). + +**Changes in `lib/u_async.c`:** +- Added Windows-specific code path using `select()` +- Maintains compatibility with Linux (uses `poll()`) +- Properly handles both regular FDs and socket_t types + +### Binary Protocol +The protocol uses fixed-size binary structures for efficient communication: +- Header: 3 bytes (2 size + 1 type) +- Connection list item: 40 bytes (8 peer_id + 32 name) +- ETCP metrics: 40 bytes +- Link metrics: 32 bytes per link +- TUN metrics: 24 bytes + +## Testing + +### Unit Test +```bash +cd tests +./test_control_server +``` + +Expected output: +``` +================================ +Control Server Protocol Test +================================ + +[TEST] Setting up mock data... +[TEST] Mock data ready (1 conn, 2 links) +[TEST] Starting control server on port 19090... +[TEST] Control server started ✓ +[TEST] Starting client thread... +[TEST] Running event loop... + +[CLIENT] Connecting to 127.0.0.1:19090... +[CLIENT] Connected +[CLIENT] Sending CMD_LIST_CONN... +[CLIENT] Waiting for response... +[CLIENT] Received connection list: 1 connection(s) +[CLIENT] Connection verified: peer_id=0x1234567890ABCDEF ✓ +[CLIENT] All tests passed ✓ + +[TEST] Cleaning up... + +================================ +[PASS] All tests passed! +================================ +``` + +## License +See main project LICENSE file. diff --git a/tools/etcpmon/Makefile.mingw b/tools/etcpmon/Makefile.mingw new file mode 100644 index 00000000..a2358577 --- /dev/null +++ b/tools/etcpmon/Makefile.mingw @@ -0,0 +1,31 @@ +# ETCP Monitor Makefile for MinGW/MSYS2 +# Build: make -f Makefile.mingw +# Clean: make -f Makefile.mingw clean + +CC = gcc +CFLAGS = -Wall -O2 -DWIN32_LEAN_AND_MEAN +LDFLAGS = -mwindows +LIBS = -lws2_32 -lcomctl32 -luser32 -lgdi32 + +TARGET = etcpmon.exe +SRCS = etcpmon_main.c etcpmon_gui.c etcpmon_client.c +OBJS = $(SRCS:.c=.o) + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) + @echo "Build complete: $(TARGET)" + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f $(OBJS) $(TARGET) + @echo "Clean complete" + +# Run the application +run: $(TARGET) + ./$(TARGET) diff --git a/tools/etcpmon/README.md b/tools/etcpmon/README.md new file mode 100644 index 00000000..7711a318 --- /dev/null +++ b/tools/etcpmon/README.md @@ -0,0 +1,137 @@ +# ETCP Monitor + +Windows GUI утилита для мониторинга ETCP подключений uTun в реальном времени. + +## Описание + +ETCP Monitor - это Windows GUI приложение, которое подключается к работающему экземпляру uTun и отображает статистику ETCP подключений в реальном времени с периодом обновления 100мс. + +## Архитектура + +### Протокол управления (бинарный) + +Формат сообщения: `[2 байта: размер] [1 байт: тип] [payload...]` + +**Команды клиента:** +- `0x01` - запрос списка подключений +- `0x02` - выбор подключения по `peer_node_id` +- `0x03` - запрос метрик выбранного подключения +- `0x04` - отключение + +**Ответы сервера:** +- `0x81` - список подключений +- `0x82` - метрики ETCP + линки + TUN +- `0xFF` - ошибка + +### Файлы + +**Общие (протокол):** +- `etcpmon_protocol.h` - определения структур и констант протокола + +**Сервер (utun):** +- `src/control_server.h` - интерфейс control server +- `src/control_server.c` - реализация TCP сервера + +**Клиент (Windows GUI):** +- `etcpmon_client.h/c` - сетевая часть (Winsock) +- `etcpmon_gui.h/c` - GUI (WinAPI) +- `etcpmon_main.c` - точка входа + +## Сборка + +### Клиент (Windows) - Рекомендуемый способ + +**Используя MSYS2 UCRT64 (рекомендуется):** + +```cmd +cd tools\etcpmon +build.bat +``` + +Это автоматически запустит MSYS2 UCRT64 shell и выполнит сборку. + +Требования: +- Установленный MSYS2 (обычно в `C:\msys64`) +- Или переменная окружения `MSYS2_ROOT` с путем к MSYS2 + +**Альтернативные способы:** + +```bash +# Через MSYS2 напрямую +cd tools/etcpmon +bash build.sh + +# Через Makefile (если установлен make для Windows) +make -f Makefile.mingw +``` + +**Очистка:** +```cmd +clean.bat +``` + +### Сервер (utun) + +Control server интегрирован в utun. Сборка стандартная: + +```bash +./configure +make +``` + +## Настройка + +### Конфигурация uTun + +Добавьте в `utun.conf`: + +```ini +control_ip=127.0.0.1 +control_port=9090 +``` + +### Использование + +1. Запустите uTun с настроенным control socket +2. Запустите etcpmon.exe +3. Введите IP и порт control сервера +4. Нажмите "Connect" +5. Выберите подключение из списка для просмотра метрик + +## Отображаемые метрики + +### ETCP Connection: +- RTT Last/Avg10/Avg100 (микросекунды) +- Jitter (микросекунды) +- Bytes Sent (всего) +- Retransmissions (число) +- ACK received (число) +- Unacked bytes (текущие) +- Links count (число линков) + +### TUN Interface: +- Bytes Read/Written +- Packets Read/Written +- Read/Write Errors + +### Links: +- Status (UP/DOWN) +- Encrypt/Decrypt Errors +- Send/Receive Errors +- Total Encrypted/Decrypted Bytes +- Bandwidth (Kbps) +- NAT Changes Count + +## Период обновления + +100 мс (10 обновлений в секунду) + +## Безопасность + +- Поддержка нескольких одновременных клиентов +- Отдельный TCP сокет (не путать с ETCP UDP) +- Рекомендуется использовать localhost (127.0.0.1) для локального мониторинга + +## Лицензия + +См. основной LICENSE файл проекта uTun. diff --git a/tools/etcpmon/build.bat b/tools/etcpmon/build.bat new file mode 100644 index 00000000..24e6912e --- /dev/null +++ b/tools/etcpmon/build.bat @@ -0,0 +1,58 @@ +@echo off +pushd "%~dp0\..\.." + + +echo ETCP Monitor Build Script for MSYS2 UCRT64 +echo =========================================== +echo. + +REM Default MSYS2 path +set MSYS2_PATH=C:\msys64 + +REM Use environment variable if set +if not "%MSYS2_ROOT%"=="" set MSYS2_PATH=%MSYS2_ROOT% +if not "%MSYS2%"=="" set MSYS2_PATH=%MSYS2% + +REM Check if msys2_shell.cmd exists +if not exist "%MSYS2_PATH%\msys2_shell.cmd" ( + echo [ERROR] MSYS2 not found at %MSYS2_PATH% + echo Please install MSYS2 from https://www.msys2.org/ + echo or set MSYS2_ROOT environment variable. + pause + exit /b 1 +) + +echo [INFO] MSYS2 found at: %MSYS2_PATH% +echo [INFO] Starting build process... +echo [INFO] Log file: tools\etcpmon\build.log +echo. + +REM Get current directory (for use in MSYS2) +set "CURRENT_DIR=%CD%" + +REM Launch MSYS2 UCRT64 shell and run build script with real-time logging +REM Using tee to show output in console AND save to log +call "%MSYS2_PATH%\msys2_shell.cmd" -ucrt64 -here -defterm -no-start -c "cd tools/etcpmon && bash build.sh 2>&1 | tee build.log" + +set BUILD_RESULT=%ERRORLEVEL% + +echo. +echo =========================================== +if %BUILD_RESULT% == 0 ( + echo [SUCCESS] Build completed successfully! + echo [INFO] Executable: tools\etcpmon\etcpmon.exe + + REM Check if exe exists and show size + if exist "tools\etcpmon\etcpmon.exe" ( + for %%F in ("tools\etcpmon\etcpmon.exe") do ( + echo [INFO] Size: %%~zF bytes + ) + ) +) else ( + echo [FAILED] Build failed with error code: %BUILD_RESULT% + echo [INFO] Check tools\etcpmon\build.log for full details +) +echo =========================================== +echo. + +pause diff --git a/tools/etcpmon/build.sh b/tools/etcpmon/build.sh new file mode 100644 index 00000000..b42b6ec5 --- /dev/null +++ b/tools/etcpmon/build.sh @@ -0,0 +1,104 @@ +#!/bin/bash +# ETCP Monitor Build Script for MSYS2 UCRT64 / MinGW +# Usage: ./build.sh +# Log is automatically saved to build.log + +set -e + +# Capture all output to log file +LOG_FILE="build.log" + +# Function to log and print +log() { + echo "$1" + echo "$1" >> "$LOG_FILE" +} + +# Clear or create log file +> "$LOG_FILE" + +log "================================" +log "ETCP Monitor Build Script" +log "================================" +log "" + +# Compiler settings +CC=${CC:-gcc} +CFLAGS="-Wall -O2 -DWIN32_LEAN_AND_MEAN" +LDFLAGS="-mwindows" +LIBS="-lws2_32 -lcomctl32 -luser32 -lgdi32" + +TARGET="etcpmon.exe" +SRCS="etcpmon_main.c etcpmon_gui.c etcpmon_client.c" +OBJ_FILES="" + +log "Compiler: $CC" +log "Target: $TARGET" +log "Sources: $SRCS" +log "" + +# Clean previous build +log "[1/5] Cleaning previous build..." +rm -f *.o $TARGET + +# Compile each source file +log "" +log "[2/5] Compiling source files..." +for src in $SRCS; do + obj="${src%.c}.o" + OBJ_FILES="$OBJ_FILES $obj" + log " CC $src -> $obj" + $CC $CFLAGS -c -o "$obj" "$src" 2>&1 | tee -a "$LOG_FILE" || { + log "[ERROR] Failed to compile $src" + exit 1 + } +done + +# Link +log "" +log "[3/5] Linking..." +log " LD $OBJ_FILES -> $TARGET" +$CC $LDFLAGS -o "$TARGET" $OBJ_FILES $LIBS 2>&1 | tee -a "$LOG_FILE" || { + log "[ERROR] Failed to link $TARGET" + exit 1 +} + +# Strip debug symbols (optional, for smaller size) +log "" +log "[4/5] Stripping debug symbols..." +strip "$TARGET" 2>/dev/null | tee -a "$LOG_FILE" || true + +# Verify build +log "" +log "[5/5] Verifying build..." +if [ -f "$TARGET" ]; then + FILE_SIZE=$(du -h "$TARGET" | cut -f1) + log " OK: $TARGET created ($FILE_SIZE)" + + # Check dependencies + log "" + log "Checking dependencies..." + if command -v objdump >/dev/null 2>&1; then + objdump -p "$TARGET" | grep "DLL Name:" | head -10 | tee -a "$LOG_FILE" || true + fi + + log "" + log "================================" + log "Build SUCCESS!" + log "================================" + log "" + log "Executable: $TARGET" + log "Size: $FILE_SIZE" + log "Location: $(pwd)/$TARGET" + log "" + log "To run:" + log " cd $(pwd)" + log " ./$TARGET" + log "" +else + log "" + log "================================" + log "Build FAILED!" + log "================================" + exit 1 +fi diff --git a/tools/etcpmon/clean.bat b/tools/etcpmon/clean.bat new file mode 100644 index 00000000..46d99b7d --- /dev/null +++ b/tools/etcpmon/clean.bat @@ -0,0 +1,9 @@ +@echo off +echo Cleaning ETCP Monitor build files... + +del /Q *.o 2>nul +del /Q *.exe 2>nul +del /Q build.log 2>nul + +echo Clean complete! +pause diff --git a/tools/etcpmon/etcpmon_client.c b/tools/etcpmon/etcpmon_client.c new file mode 100644 index 00000000..18aa92e3 --- /dev/null +++ b/tools/etcpmon/etcpmon_client.c @@ -0,0 +1,581 @@ +/* + * etcpmon_client.c - ETCP Monitor Client Network Implementation + */ + +#include "etcpmon_client.h" +#include +#include +#include +#include + +//#pragma comment(lib, "ws2_32.lib") + +/* Log file name */ +#define LOG_FILENAME "etcpmon.log" + +/* Get current timestamp in milliseconds (Unix epoch) */ +static uint64_t get_timestamp_ms(void) { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + /* Convert FILETIME (100-nanosecond intervals since 1601) to milliseconds */ + uint64_t time = ((uint64_t)ft.dwHighDateTime << 32) | ft.dwLowDateTime; + /* Convert from 1601 to 1970 epoch (11644473600000 milliseconds) */ + return (time / 10000) - 11644473600000ULL; +} + +/* Log hex data to file */ +static void log_hex_data(FILE* log, const char* prefix, const uint8_t* data, size_t len) { + if (!log || !data || len == 0) return; + + fprintf(log, "%llu: [%s] ", (unsigned long long)get_timestamp_ms(), prefix); + for (size_t i = 0; i < len && i < 256; i++) { /* Limit to 256 bytes per line */ + fprintf(log, "%02X ", data[i]); + } + if (len > 256) { + fprintf(log, "... (%llu bytes total)", (unsigned long long)len); + } + fprintf(log, "\n"); + fflush(log); +} + +void etcpmon_client_init(struct etcpmon_client* client) { + if (!client) return; + memset(client, 0, sizeof(*client)); + client->sock = INVALID_SOCKET; + client->connected = 0; + client->fully_connected = 0; + client->log_file = NULL; +} + +void etcpmon_client_cleanup(struct etcpmon_client* client) { + if (!client) return; + + etcpmon_client_disconnect(client); + + /* Close log file */ + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Log closed\n", + (unsigned long long)get_timestamp_ms()); + fclose(client->log_file); + client->log_file = NULL; + } + + /* Free cached data */ + if (client->conn_list) { + free(client->conn_list); + client->conn_list = NULL; + } + if (client->last_metrics) { + free(client->last_metrics); + client->last_metrics = NULL; + } + if (client->last_links) { + free(client->last_links); + client->last_links = NULL; + } +} + +int etcpmon_client_connect(struct etcpmon_client* client, const char* addr, uint16_t port) { + if (!client || !addr) return -1; + + if (client->connected) { + etcpmon_client_disconnect(client); + } + + /* Open log file (truncate on start) */ + if (!client->log_file) { + client->log_file = fopen(LOG_FILENAME, "w"); + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] ETCP Monitor communication log started\n", + (unsigned long long)get_timestamp_ms()); + fprintf(client->log_file, "%llu: [LOG] Connecting to %s:%d\n", + (unsigned long long)get_timestamp_ms(), addr, port); + fflush(client->log_file); + } + } + + /* Create socket */ + client->sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (client->sock == INVALID_SOCKET) { + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] Failed to create socket\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + if (client->on_error) { + client->on_error("Failed to create socket", client->user_data); + } + return -1; + } + + /* Set non-blocking mode */ + u_long nonblock = 1; + ioctlsocket(client->sock, FIONBIO, &nonblock); + + /* Setup address */ + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(port); + + if (inet_pton(AF_INET, addr, &server_addr.sin_addr) != 1) { + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] Invalid address: %s\n", + (unsigned long long)get_timestamp_ms(), addr); + fflush(client->log_file); + } + closesocket(client->sock); + client->sock = INVALID_SOCKET; + if (client->on_error) { + client->on_error("Invalid address", client->user_data); + } + return -1; + } + + /* Connect (non-blocking) */ + int result = connect(client->sock, (struct sockaddr*)&server_addr, sizeof(server_addr)); + if (result == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) { + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] Connection failed: %d\n", + (unsigned long long)get_timestamp_ms(), err); + fflush(client->log_file); + } + closesocket(client->sock); + client->sock = INVALID_SOCKET; + if (client->on_error) { + char msg[256]; + snprintf(msg, sizeof(msg), "Connection failed: %d", err); + client->on_error(msg, client->user_data); + } + return -1; + } + /* Connection in progress, will complete asynchronously */ + } + + strncpy(client->server_addr, addr, sizeof(client->server_addr) - 1); + client->server_port = port; + client->connected = 1; + client->fully_connected = 0; + + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Socket created, connection in progress\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + + return 0; +} + +void etcpmon_client_disconnect(struct etcpmon_client* client) { + if (!client) return; + + if (client->sock != INVALID_SOCKET) { + /* Send disconnect command */ + if (client->connected) { + struct etcpmon_msg_header hdr; + etcpmon_build_header(&hdr, 0, ETCPMON_CMD_DISCONNECT); + + if (client->log_file) { + log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr)); + fprintf(client->log_file, "%llu: [LOG] Sending CMD_DISCONNECT\n", + (unsigned long long)get_timestamp_ms()); + } + + send(client->sock, (const char*)&hdr, sizeof(hdr), 0); + } + + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Connection closed\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + + closesocket(client->sock); + client->sock = INVALID_SOCKET; + } + + client->connected = 0; + client->fully_connected = 0; + client->recv_len = 0; + client->selected_peer_id = 0; +} + +int etcpmon_client_is_connected(const struct etcpmon_client* client) { + return client ? client->connected : 0; +} + +int etcpmon_client_request_list(struct etcpmon_client* client) { + if (!client || client->sock == INVALID_SOCKET) return -1; + + struct etcpmon_msg_header hdr; + etcpmon_build_header(&hdr, 0, ETCPMON_CMD_LIST_CONN); + + if (client->log_file) { + log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr)); + fprintf(client->log_file, "%llu: [LOG] CMD_LIST_CONN\n", + (unsigned long long)get_timestamp_ms()); + 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_LIST_CONN: %d\n", + (unsigned long long)get_timestamp_ms(), WSAGetLastError()); + fflush(client->log_file); + } + return -1; + } + + return 0; +} + +int etcpmon_client_select_connection(struct etcpmon_client* client, uint64_t peer_node_id) { + if (!client || client->sock == INVALID_SOCKET) return -1; + + uint8_t buffer[sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_cmd_select)]; + struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)buffer; + struct etcpmon_cmd_select* cmd = (struct etcpmon_cmd_select*)(buffer + sizeof(*hdr)); + + etcpmon_build_header(hdr, sizeof(*cmd), ETCPMON_CMD_SELECT_CONN); + cmd->peer_node_id = peer_node_id; + + if (client->log_file) { + log_hex_data(client->log_file, "TX", buffer, sizeof(buffer)); + fprintf(client->log_file, "%llu: [LOG] CMD_SELECT_CONN peer_id=%016llX\n", + (unsigned long long)get_timestamp_ms(), (unsigned long long)peer_node_id); + fflush(client->log_file); + } + + int sent = send(client->sock, (const char*)buffer, sizeof(buffer), 0); + if (sent != sizeof(buffer)) { + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] Failed to send CMD_SELECT_CONN: %d\n", + (unsigned long long)get_timestamp_ms(), WSAGetLastError()); + fflush(client->log_file); + } + return -1; + } + + client->selected_peer_id = peer_node_id; + return 0; +} + +int etcpmon_client_request_metrics(struct etcpmon_client* client) { + if (!client || client->sock == INVALID_SOCKET) return -1; + + struct etcpmon_msg_header hdr; + etcpmon_build_header(&hdr, 0, ETCPMON_CMD_GET_METRICS); + + if (client->log_file) { + log_hex_data(client->log_file, "TX", (const uint8_t*)&hdr, sizeof(hdr)); + fprintf(client->log_file, "%llu: [LOG] CMD_GET_METRICS\n", + (unsigned long long)get_timestamp_ms()); + 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_GET_METRICS: %d\n", + (unsigned long long)get_timestamp_ms(), WSAGetLastError()); + fflush(client->log_file); + } + return -1; + } + + return 0; +} + +static void handle_response(struct etcpmon_client* client, uint8_t msg_type, + const uint8_t* payload, uint16_t payload_size) { + switch (msg_type) { + case ETCPMON_RSP_CONN_LIST: { + if (payload_size < sizeof(struct etcpmon_rsp_conn_list)) break; + + struct etcpmon_rsp_conn_list* rsp = (struct etcpmon_rsp_conn_list*)payload; + uint8_t count = rsp->count; + + if (payload_size < sizeof(*rsp) + count * sizeof(struct etcpmon_conn_info)) break; + + /* Update cached list */ + if (client->conn_list) { + free(client->conn_list); + } + client->conn_list = (struct etcpmon_conn_info*)malloc(count * sizeof(struct etcpmon_conn_info)); + if (client->conn_list) { + memcpy(client->conn_list, payload + sizeof(*rsp), count * sizeof(struct etcpmon_conn_info)); + client->conn_count = count; + + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] RSP_CONN_LIST count=%d\n", + (unsigned long long)get_timestamp_ms(), count); + for (uint8_t i = 0; i < count; i++) { + fprintf(client->log_file, "%llu: [LOG] [%d] peer_id=%016llX name='%s'\n", + (unsigned long long)get_timestamp_ms(), i, + (unsigned long long)client->conn_list[i].peer_node_id, + client->conn_list[i].name); + } + fflush(client->log_file); + } + + if (client->on_conn_list) { + client->on_conn_list(client->conn_list, count, client->user_data); + } + } + break; + } + + case ETCPMON_RSP_METRICS: { + if (payload_size < sizeof(struct etcpmon_rsp_metrics)) break; + + struct etcpmon_rsp_metrics* rsp = (struct etcpmon_rsp_metrics*)payload; + uint8_t links_count = rsp->etcp.links_count; + + if (payload_size < sizeof(*rsp) + links_count * sizeof(struct etcpmon_link_metrics)) break; + + /* Update cached metrics */ + if (client->last_metrics) { + free(client->last_metrics); + } + if (client->last_links) { + free(client->last_links); + } + + client->last_metrics = (struct etcpmon_rsp_metrics*)malloc(sizeof(*rsp)); + client->last_links = (struct etcpmon_link_metrics*)malloc(links_count * sizeof(struct etcpmon_link_metrics)); + + if (client->last_metrics && client->last_links) { + memcpy(client->last_metrics, rsp, sizeof(*rsp)); + memcpy(client->last_links, payload + sizeof(*rsp), + links_count * sizeof(struct etcpmon_link_metrics)); + client->last_links_count = links_count; + + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] RSP_METRICS rtt=%u bytes=%llu links=%d\n", + (unsigned long long)get_timestamp_ms(), + rsp->etcp.rtt_last, + (unsigned long long)rsp->etcp.bytes_sent_total, + links_count); + fflush(client->log_file); + } + + if (client->on_metrics) { + client->on_metrics(client->last_metrics, client->last_links, + links_count, client->user_data); + } + } + break; + } + + case ETCPMON_RSP_ERROR: { + 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", + (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); + } + } + break; + } + } +} + +int etcpmon_client_process(struct etcpmon_client* client) { + if (!client || client->sock == INVALID_SOCKET) return -1; + + fprintf(client->log_file, "%llu: [LOG] Client_process\n", (unsigned long long)get_timestamp_ms()); fflush(client->log_file); + +// Проверка завершения non-blocking connect + if (client->connected && !client->fully_connected) { + int error = 0; + int len = sizeof(error); + if (getsockopt(client->sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len) == 0) { + if (error == 0) { + // Connect успешно завершён + client->fully_connected = 1; + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Connection established\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + if (client->on_connected) { + client->on_connected(client->user_data); // Здесь вызов request_list + } + // Продолжаем к recv, т.к. подключены + } else if (error == WSAEINPROGRESS || error == WSAEALREADY) { + // Подключение ещё в процессе, ничего не делаем, ждём следующий вызов process + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Connect in progress (polling)...\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + return 0; // Важно: выходим, чтобы не идти в recv (оно провалится с ошибкой) + } else { + // Подключение провалилось по другой причине + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] Connect failed: %d\n", + (unsigned long long)get_timestamp_ms(), error); + fflush(client->log_file); + } + etcpmon_client_disconnect(client); + if (client->on_disconnected) { + client->on_disconnected(client->user_data); + } + if (client->on_error) { + char msg[256]; + snprintf(msg, sizeof(msg), "Connect failed: %d", error); + client->on_error(msg, client->user_data); + } + return -1; + } + } else { + // getsockopt провалился (редко, но на всякий случай) + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] getsockopt failed: %d\n", + (unsigned long long)get_timestamp_ms(), WSAGetLastError()); + fflush(client->log_file); + } + etcpmon_client_disconnect(client); + if (client->on_disconnected) { + client->on_disconnected(client->user_data); + } + return -1; + } + } + + // Если подключение в процессе, мы уже вышли выше. Здесь recv только для установленного соединения + uint8_t* buf = client->recv_buffer + client->recv_len; + int buf_space = ETCPMON_MAX_MSG_SIZE - client->recv_len; + + if (buf_space <= 0) { + client->recv_len = 0; + buf = client->recv_buffer; + buf_space = ETCPMON_MAX_MSG_SIZE; + } + + int received = recv(client->sock, (char*)buf, buf_space, 0); + + if (received == SOCKET_ERROR) { + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + return 0; /* No data available */ + } + /* Connection error */ + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] recv failed: %d\n", + (unsigned long long)get_timestamp_ms(), err); + fflush(client->log_file); + } + etcpmon_client_disconnect(client); + if (client->on_disconnected) { + client->on_disconnected(client->user_data); + } + return -1; + } + + if (received == 0) { + /* Connection closed by server */ + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Connection closed by server (recv returned 0)\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + etcpmon_client_disconnect(client); + if (client->on_disconnected) { + client->on_disconnected(client->user_data); + } + return -1; + } + + /* Log received data */ + if (client->log_file) { + log_hex_data(client->log_file, "RX", buf, received); + } + + client->recv_len += received; + + /* Process complete messages */ + int processed = 0; + while (client->recv_len >= sizeof(struct etcpmon_msg_header)) { + struct etcpmon_msg_header* hdr = (struct etcpmon_msg_header*)client->recv_buffer; + + if (etcpmon_validate_header(hdr) != 0) { + /* Invalid header, skip byte */ + if (client->log_file) { + fprintf(client->log_file, "%llu: [ERROR] Invalid header, skipping byte\n", + (unsigned long long)get_timestamp_ms()); + fflush(client->log_file); + } + memmove(client->recv_buffer, client->recv_buffer + 1, client->recv_len - 1); + client->recv_len--; + continue; + } + + if (client->recv_len < hdr->size) { + break; /* Wait for more data */ + } + + /* Handle message */ + uint8_t* payload = client->recv_buffer + sizeof(*hdr); + uint16_t payload_size = hdr->size - sizeof(*hdr); + + if (client->log_file) { + fprintf(client->log_file, "%llu: [LOG] Received message type=0x%02X size=%d\n", + (unsigned long long)get_timestamp_ms(), hdr->type, hdr->size); + fflush(client->log_file); + } + + handle_response(client, hdr->type, payload, payload_size); + processed++; + + /* Remove processed message */ + if (client->recv_len > hdr->size) { + memmove(client->recv_buffer, client->recv_buffer + hdr->size, + client->recv_len - hdr->size); + } + client->recv_len -= hdr->size; + } + + return processed; +} + +void etcpmon_client_set_callbacks(struct etcpmon_client* client, + void (*on_connected)(void*), + void (*on_disconnected)(void*), + void (*on_conn_list)(struct etcpmon_conn_info*, uint8_t, void*), + void (*on_metrics)(struct etcpmon_rsp_metrics*, + struct etcpmon_link_metrics*, + uint8_t, void*), + void (*on_error)(const char*, void*), + void* user_data) { + if (!client) return; + + client->on_connected = on_connected; + client->on_disconnected = on_disconnected; + client->on_conn_list = on_conn_list; + client->on_metrics = on_metrics; + client->on_error = on_error; + client->user_data = user_data; +} + +const char* etcpmon_client_get_conn_name(struct etcpmon_client* client, uint64_t peer_id) { + if (!client || !client->conn_list) return NULL; + + for (uint8_t i = 0; i < client->conn_count; i++) { + if (client->conn_list[i].peer_node_id == peer_id) { + return client->conn_list[i].name; + } + } + return NULL; +} diff --git a/tools/etcpmon/etcpmon_client.h b/tools/etcpmon/etcpmon_client.h new file mode 100644 index 00000000..fb93c093 --- /dev/null +++ b/tools/etcpmon/etcpmon_client.h @@ -0,0 +1,115 @@ +/* + * etcpmon_client.h - ETCP Monitor Client Network Layer + * + * Handles TCP connection to utun control server + */ + +#ifndef ETCPMON_CLIENT_H +#define ETCPMON_CLIENT_H + +#include "etcpmon_protocol.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Client state */ +struct etcpmon_client { + SOCKET sock; + int connected; + int fully_connected; + /* Receive buffer */ + uint8_t recv_buffer[ETCPMON_MAX_MSG_SIZE]; + uint16_t recv_len; + + /* Server address */ + char server_addr[64]; + uint16_t server_port; + + /* Selected connection */ + uint64_t selected_peer_id; + + /* Connection info cache */ + struct etcpmon_conn_info* conn_list; + uint8_t conn_count; + + /* Latest metrics */ + struct etcpmon_rsp_metrics* last_metrics; + struct etcpmon_link_metrics* last_links; + uint8_t last_links_count; + + /* Log file */ + FILE* log_file; + + /* Callbacks */ + void (*on_connected)(void* user_data); + void (*on_disconnected)(void* user_data); + void (*on_conn_list)(struct etcpmon_conn_info* list, uint8_t count, void* user_data); + void (*on_metrics)(struct etcpmon_rsp_metrics* metrics, + struct etcpmon_link_metrics* links, + uint8_t links_count, void* user_data); + void (*on_error)(const char* msg, void* user_data); + + void* user_data; +}; + +/* Initialize client structure */ +void etcpmon_client_init(struct etcpmon_client* client); + +/* Cleanup client resources */ +void etcpmon_client_cleanup(struct etcpmon_client* client); + +/* Connect to server + * Returns 0 on success, -1 on error + */ +int etcpmon_client_connect(struct etcpmon_client* client, const char* addr, uint16_t port); + +/* Disconnect from server */ +void etcpmon_client_disconnect(struct etcpmon_client* client); + +/* Check if connected */ +int etcpmon_client_is_connected(const struct etcpmon_client* client); + +/* Request connection list + * Returns 0 on success, -1 on error + */ +int etcpmon_client_request_list(struct etcpmon_client* client); + +/* Select connection to monitor + * Returns 0 on success, -1 on error + */ +int etcpmon_client_select_connection(struct etcpmon_client* client, uint64_t peer_node_id); + +/* Request metrics for selected connection + * Returns 0 on success, -1 on error + */ +int etcpmon_client_request_metrics(struct etcpmon_client* client); + +/* Process incoming data (non-blocking) + * Should be called periodically (e.g., from window message loop) + * Returns 1 if data was processed, 0 if no data, -1 on error + */ +int etcpmon_client_process(struct etcpmon_client* client); + +/* Set callbacks */ +void etcpmon_client_set_callbacks(struct etcpmon_client* client, + void (*on_connected)(void*), + void (*on_disconnected)(void*), + void (*on_conn_list)(struct etcpmon_conn_info*, uint8_t, void*), + void (*on_metrics)(struct etcpmon_rsp_metrics*, + struct etcpmon_link_metrics*, + uint8_t, void*), + void (*on_error)(const char*, void*), + void* user_data); + +/* Get connection name by peer_id */ +const char* etcpmon_client_get_conn_name(struct etcpmon_client* client, uint64_t peer_id); + +#ifdef __cplusplus +} +#endif + +#endif /* ETCPMON_CLIENT_H */ diff --git a/tools/etcpmon/etcpmon_gui.c b/tools/etcpmon/etcpmon_gui.c new file mode 100644 index 00000000..205ed1c1 --- /dev/null +++ b/tools/etcpmon/etcpmon_gui.c @@ -0,0 +1,663 @@ +/* + * etcpmon_gui.c - ETCP Monitor GUI Implementation + */ + +/* Must define this before any windows headers to exclude winsock.h */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +/* Must include winsock2.h before windows.h */ +#include +#include +#include + +#include "etcpmon_gui.h" +#include "etcpmon_client.h" +#include "etcpmon_protocol.h" +#include +#include + +//#pragma comment(lib, "comctl32.lib") +//#pragma comment(lib, "user32.lib") +//#pragma comment(lib, "gdi32.lib") + +#define WINDOW_WIDTH 900 +#define WINDOW_HEIGHT 700 +#define UPDATE_INTERVAL 100 /* 100ms */ + +/* Global app pointer for callbacks */ +static struct etcpmon_app* g_app = NULL; + +/* Forward declarations */ +static LRESULT CALLBACK WndProc(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); +static void OnConnectionSelect(struct etcpmon_app* app); +static void OnTimer(struct etcpmon_app* app); +static void UpdateUIState(struct etcpmon_app* app); + +/* Callback functions for client events */ +static void on_connected(void* user_data); +static void on_disconnected(void* user_data); +static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data); +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); + +/* Helper functions */ +static void SetDlgItemTextFmt(HWND hDlg, int nIDDlgItem, const char* fmt, ...) { + char buf[256]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, sizeof(buf), fmt, args); + va_end(args); + SetDlgItemTextA(hDlg, nIDDlgItem, buf); +} + +int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance) { + if (!app) return -1; + + memset(app, 0, sizeof(*app)); + app->hInstance = hInstance; + g_app = app; + + /* Initialize common controls */ + INITCOMMONCONTROLSEX iccex; + iccex.dwSize = sizeof(iccex); + iccex.dwICC = ICC_LISTVIEW_CLASSES | ICC_STANDARD_CLASSES; + InitCommonControlsEx(&iccex); + + /* Register window class */ + WNDCLASSEXA wcex; + memset(&wcex, 0, sizeof(wcex)); + wcex.cbSize = sizeof(wcex); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); + wcex.lpszClassName = ETCPMON_WINDOW_CLASS; + wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + + if (!RegisterClassExA(&wcex)) { + return -1; + } + + /* Initialize Winsock */ + WSADATA wsaData; + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { + return -1; + } + + return 0; +} + +HWND etcpmon_gui_create_window(struct etcpmon_app* app) { + if (!app) return NULL; + + /* Create main window */ + app->hWndMain = CreateWindowExA( + 0, + ETCPMON_WINDOW_CLASS, + "ETCP Monitor", + WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, + CW_USEDEFAULT, CW_USEDEFAULT, + WINDOW_WIDTH, WINDOW_HEIGHT, + NULL, NULL, app->hInstance, app + ); + + if (!app->hWndMain) { + return NULL; + } + + /* Create status bar */ + app->hWndStatus = CreateWindowExA( + 0, STATUSCLASSNAME, NULL, + WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, + 0, 0, 0, 0, + app->hWndMain, (HMENU)IDC_STATIC, app->hInstance, NULL + ); + + /* Create all controls */ + CreateControls(app); + + /* Set default values */ + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ADDR, "127.0.0.1"); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_PORT, "9090"); + + /* Initialize client */ + app->client = (struct etcpmon_client*)malloc(sizeof(struct etcpmon_client)); + if (app->client) { + etcpmon_client_init(app->client); + etcpmon_client_set_callbacks(app->client, + on_connected, on_disconnected, + on_conn_list, on_metrics, on_error, + app); + } + + UpdateUIState(app); + + return app->hWndMain; +} + +static void CreateControls(struct etcpmon_app* app) { + HWND hWnd = app->hWndMain; + HINSTANCE hInst = app->hInstance; + int x = 10, y = 10; + + /* Connection group */ + CreateWindowExA(0, "BUTTON", "Connection", + WS_CHILD | WS_VISIBLE | BS_GROUPBOX, + x, y, 880, 60, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + /* Address label and edit */ + CreateWindowExA(0, "STATIC", "Server:", + WS_CHILD | WS_VISIBLE, + x + 10, y + 20, 40, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + app->hEditAddr = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL, + x + 55, y + 18, 120, 22, hWnd, (HMENU)IDC_EDIT_ADDR, hInst, NULL); + + /* Port label and edit */ + CreateWindowExA(0, "STATIC", "Port:", + WS_CHILD | WS_VISIBLE, + x + 185, y + 20, 30, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + app->hEditPort = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_NUMBER, + x + 220, y + 18, 60, 22, hWnd, (HMENU)IDC_EDIT_PORT, hInst, NULL); + + /* Connect/Disconnect buttons */ + app->hBtnConnect = CreateWindowExA(0, "BUTTON", "Connect", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, + x + 290, y + 18, 80, 24, hWnd, (HMENU)IDC_BTN_CONNECT, hInst, NULL); + + app->hBtnDisconnect = CreateWindowExA(0, "BUTTON", "Disconnect", + WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_DISABLED, + x + 380, y + 18, 80, 24, hWnd, (HMENU)IDC_BTN_DISCONNECT, hInst, NULL); + + y += 70; + + /* Connections list */ + CreateWindowExA(0, "STATIC", "Connections:", + WS_CHILD | WS_VISIBLE, + x, y, 100, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + app->hListConnections = CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "", + WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, + x, y + 20, 300, 120, hWnd, (HMENU)IDC_LIST_CONNECTIONS, hInst, NULL); + + /* ETCP Metrics group */ + y += 150; + CreateWindowExA(0, "BUTTON", "ETCP Metrics", + WS_CHILD | WS_VISIBLE | BS_GROUPBOX, + x, y, 430, 220, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + int mx = x + 10, my = y + 20; + + CreateWindowExA(0, "STATIC", "Name:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpName = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY | ES_AUTOHSCROLL, + mx + 85, my, 330, 20, hWnd, (HMENU)IDC_EDIT_ETCP_NAME, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "RTT Last:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpRttLast = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RTT_LAST, hInst, NULL); + + CreateWindowExA(0, "STATIC", "RTT Avg 10:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpRttAvg10 = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RTT_AVG10, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "RTT Avg 100:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpRttAvg100 = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RTT_AVG100, hInst, NULL); + + CreateWindowExA(0, "STATIC", "Jitter:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpJitter = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_JITTER, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "Bytes Sent:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpBytesSent = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_BYTES_SENT, hInst, NULL); + + CreateWindowExA(0, "STATIC", "Retrans:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpRetrans = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_RETRANS, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "ACKs:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpAcks = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_ACKS, hInst, NULL); + + CreateWindowExA(0, "STATIC", "Inflight:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpInflight = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_INFLIGHT, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "Links:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditEtcpLinks = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_ETCP_LINKS, hInst, NULL); + + /* TUN Metrics group */ + x = 460; y = 240; + CreateWindowExA(0, "BUTTON", "TUN Metrics", + WS_CHILD | WS_VISIBLE | BS_GROUPBOX, + x, y, 430, 140, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + mx = x + 10; my = y + 20; + + CreateWindowExA(0, "STATIC", "Read Bytes:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditTunReadBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_READ_BYTES, hInst, NULL); + + CreateWindowExA(0, "STATIC", "Write Bytes:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditTunWriteBytes = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_WRITE_BYTES, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "Read Pkts:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditTunReadPkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_READ_PKTS, hInst, NULL); + + CreateWindowExA(0, "STATIC", "Write Pkts:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditTunWritePkts = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_WRITE_PKTS, hInst, NULL); + + my += 25; + CreateWindowExA(0, "STATIC", "Read Errs:", + WS_CHILD | WS_VISIBLE, mx, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditTunReadErrs = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 85, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_READ_ERRS, hInst, NULL); + + CreateWindowExA(0, "STATIC", "Write Errs:", + WS_CHILD | WS_VISIBLE, mx + 200, my, 80, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + app->hEditTunWriteErrs = CreateWindowExA(WS_EX_CLIENTEDGE, "EDIT", "", + WS_CHILD | WS_VISIBLE | ES_READONLY, + mx + 285, my, 100, 20, hWnd, (HMENU)IDC_EDIT_TUN_WRITE_ERRS, hInst, NULL); + + /* Links list */ + y = 470; + CreateWindowExA(0, "STATIC", "Links:", + WS_CHILD | WS_VISIBLE, + 10, y, 100, 20, hWnd, (HMENU)IDC_STATIC, hInst, NULL); + + app->hListLinks = CreateWindowExA(WS_EX_CLIENTEDGE, "LISTBOX", "", + WS_CHILD | WS_VISIBLE | LBS_NOTIFY | WS_VSCROLL | LBS_NOINTEGRALHEIGHT, + 10, y + 20, 880, 120, hWnd, (HMENU)IDC_LIST_LINKS, hInst, NULL); +} + +static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { + struct etcpmon_app* app = NULL; + + if (message == WM_CREATE) { + CREATESTRUCTA* cs = (CREATESTRUCTA*)lParam; + app = (struct etcpmon_app*)cs->lpCreateParams; + SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)app); + } else { + app = (struct etcpmon_app*)GetWindowLongPtrA(hWnd, GWLP_USERDATA); + } + + switch (message) { + case WM_COMMAND: + if (app) { + switch (LOWORD(wParam)) { + case IDC_BTN_CONNECT: + OnConnect(app); + break; + case IDC_BTN_DISCONNECT: + OnDisconnect(app); + break; + case IDC_LIST_CONNECTIONS: + if (HIWORD(wParam) == LBN_SELCHANGE) { + OnConnectionSelect(app); + } + break; + } + } + break; + + case WM_TIMER: + if (app && wParam == IDC_TIMER_UPDATE) { + OnTimer(app); + } + break; + + case WM_SIZE: + if (app && app->hWndStatus) { + SendMessage(app->hWndStatus, WM_SIZE, 0, 0); + } + break; + + case WM_DESTROY: + PostQuitMessage(0); + break; + + default: + return DefWindowProcA(hWnd, message, wParam, lParam); + } + + return 0; +} + +static void OnConnect(struct etcpmon_app* app) { + if (!app || !app->client) return; + + char addr[64]; + char port_str[16]; + + GetDlgItemTextA(app->hWndMain, IDC_EDIT_ADDR, addr, sizeof(addr)); + GetDlgItemTextA(app->hWndMain, IDC_EDIT_PORT, port_str, sizeof(port_str)); + + uint16_t port = (uint16_t)atoi(port_str); + if (port == 0) port = 9090; + + etcpmon_gui_set_status(app, "Connecting..."); + + if (etcpmon_client_connect(app->client, addr, port) == 0) { + /* Start update timer */ + app->updateTimer = SetTimer(app->hWndMain, IDC_TIMER_UPDATE, UPDATE_INTERVAL, NULL); + app->isConnected = 1; + UpdateUIState(app); + etcpmon_gui_set_status(app, "Connected"); + } else { + etcpmon_gui_set_status(app, "Connection failed"); + } +} + +static void OnDisconnect(struct etcpmon_app* app) { + if (!app || !app->client) return; + + if (app->updateTimer) { + KillTimer(app->hWndMain, app->updateTimer); + app->updateTimer = 0; + } + + etcpmon_client_disconnect(app->client); + app->isConnected = 0; + UpdateUIState(app); + etcpmon_gui_clear_metrics(app); + etcpmon_gui_set_status(app, "Disconnected"); +} + +static void OnConnectionSelect(struct etcpmon_app* app) { + if (!app || !app->client) return; + + int sel = (int)SendMessage(app->hListConnections, LB_GETCURSEL, 0, 0); + if (sel == LB_ERR) return; + + /* Get peer_id from item data */ + uint64_t peer_id = (uint64_t)SendMessage(app->hListConnections, LB_GETITEMDATA, sel, 0); + + /* Request metrics for this connection */ + etcpmon_client_select_connection(app->client, peer_id); +} + +static void OnTimer(struct etcpmon_app* app) { + if (!app || !app->client) return; + + /* Process any incoming data */ +// int result = + etcpmon_client_process(app->client); + + /* If newly connected, request connection list */ + static int first_connect = 1; + if (first_connect && app->isConnected) { + etcpmon_client_request_list(app->client); + first_connect = 0; + } + + /* Request metrics periodically if a connection is selected */ + if (app->client->selected_peer_id != 0) { + etcpmon_client_request_metrics(app->client); + } +} + +static void UpdateUIState(struct etcpmon_app* app) { + if (!app) return; + + EnableWindow(app->hEditAddr, !app->isConnected); + EnableWindow(app->hEditPort, !app->isConnected); + EnableWindow(app->hBtnConnect, !app->isConnected); + EnableWindow(app->hBtnDisconnect, app->isConnected); +} + +/* Client callbacks */ +//static void on_connected(void* user_data) { +// struct etcpmon_app* app = (struct etcpmon_app*)user_data; +// etcpmon_gui_set_status(app, "Connected"); +//} + +static void on_connected(void* user_data) { + struct etcpmon_app* app = (struct etcpmon_app*)user_data; + app->isConnected = 1; + UpdateUIState(app); + etcpmon_gui_set_status(app, "Connected"); + // Добавьте для теста + if (app->client->log_file) { + fprintf(app->client->log_file, "[DEBUG] on_connected called\n"); + fflush(app->client->log_file); + } + etcpmon_client_request_list(app->client); +} + + + +static void on_disconnected(void* user_data) { + struct etcpmon_app* app = (struct etcpmon_app*)user_data; + app->isConnected = 0; + UpdateUIState(app); + etcpmon_gui_set_status(app, "Disconnected"); +} + +//static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) { +// struct etcpmon_app* app = (struct etcpmon_app*)user_data; +// etcpmon_gui_update_conn_list(app, list, count); +//} +static void on_conn_list(struct etcpmon_conn_info* list, uint8_t count, void* user_data) { + struct etcpmon_app* app = (struct etcpmon_app*)user_data; + etcpmon_gui_update_conn_list(app, list, count); + etcpmon_gui_set_status(app, "Connection list updated"); + + // Automatically select the first connection if available + if (count > 0 && app->hListConnections) { + // Select the first item (index 0) + SendMessage(app->hListConnections, LB_SETCURSEL, 0, 0); + + // Trigger the selection handler to request metrics + OnConnectionSelect(app); + } +} + +static void on_metrics(struct etcpmon_rsp_metrics* metrics, + struct etcpmon_link_metrics* links, + uint8_t links_count, void* user_data) { + struct etcpmon_app* app = (struct etcpmon_app*)user_data; + etcpmon_gui_update_metrics(app, metrics, links, links_count); +} + +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); +} + +int etcpmon_gui_run(struct etcpmon_app* app) { + if (!app || !app->hWndMain) return -1; + + ShowWindow(app->hWndMain, SW_SHOW); + UpdateWindow(app->hWndMain); + + MSG msg; + while (GetMessageA(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + return (int)msg.wParam; +} + +void etcpmon_gui_cleanup(struct etcpmon_app* app) { + if (!app) return; + + if (app->updateTimer) { + KillTimer(app->hWndMain, app->updateTimer); + } + + if (app->client) { + etcpmon_client_cleanup(app->client); + free(app->client); + app->client = NULL; + } + + WSACleanup(); + g_app = NULL; +} + +void etcpmon_gui_set_status(struct etcpmon_app* app, const char* text) { + if (!app || !app->hWndStatus) return; + SendMessageA(app->hWndStatus, SB_SETTEXTA, 0, (LPARAM)text); +} + +void etcpmon_gui_update_conn_list(struct etcpmon_app* app, + struct etcpmon_conn_info* list, + uint8_t count) { + if (!app || !app->hListConnections) return; + + /* Clear existing items */ + SendMessage(app->hListConnections, LB_RESETCONTENT, 0, 0); + + /* Add new items */ + for (uint8_t i = 0; i < count; i++) { + char display[64]; + snprintf(display, sizeof(display), "%s (%016llX)", + list[i].name, (unsigned long long)list[i].peer_node_id); + + int idx = (int)SendMessageA(app->hListConnections, LB_ADDSTRING, 0, (LPARAM)display); + if (idx != LB_ERR) { + SendMessage(app->hListConnections, LB_SETITEMDATA, idx, (LPARAM)list[i].peer_node_id); + } + } +} + +void etcpmon_gui_update_metrics(struct etcpmon_app* app, + struct etcpmon_rsp_metrics* metrics, + struct etcpmon_link_metrics* links, + uint8_t links_count) { + if (!app || !metrics) return; + + /* Update ETCP metrics */ + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RTT_LAST, "%u us", + metrics->etcp.rtt_last * 100); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG10, "%u us", + metrics->etcp.rtt_avg_10 * 100); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG100, "%u us", + metrics->etcp.rtt_avg_100 * 100); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_JITTER, "%u us", + metrics->etcp.jitter * 100); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_BYTES_SENT, "%llu", + (unsigned long long)metrics->etcp.bytes_sent_total); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_RETRANS, "%u", + metrics->etcp.retrans_count); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_ACKS, "%u", + metrics->etcp.ack_count); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_INFLIGHT, "%u bytes", + metrics->etcp.unacked_bytes); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_ETCP_LINKS, "%u", + metrics->etcp.links_count); + + /* Update TUN metrics */ + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_READ_BYTES, "%llu", + (unsigned long long)metrics->tun.bytes_read); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_WRITE_BYTES, "%llu", + (unsigned long long)metrics->tun.bytes_written); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_READ_PKTS, "%u", + metrics->tun.packets_read); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_WRITE_PKTS, "%u", + metrics->tun.packets_written); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_READ_ERRS, "%u", + metrics->tun.read_errors); + SetDlgItemTextFmt(app->hWndMain, IDC_EDIT_TUN_WRITE_ERRS, "%u", + metrics->tun.write_errors); + + /* Update links list */ + SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); + for (uint8_t i = 0; i < links_count; i++) { + char display[256]; + snprintf(display, sizeof(display), + "Link %u: Status=%s, EncErrors=%u, DecErrors=%u, " + "SendErr=%u, RecvErr=%u, BytesEnc=%llu, BytesDec=%llu, " + "BW=%u Kbps, NATChg=%u", + links[i].local_link_id, + links[i].status ? "UP" : "DOWN", + links[i].encrypt_errors, + links[i].decrypt_errors, + links[i].send_errors, + links[i].recv_errors, + (unsigned long long)links[i].total_encrypted, + (unsigned long long)links[i].total_decrypted, + links[i].bandwidth, + links[i].nat_changes_count); + + SendMessageA(app->hListLinks, LB_ADDSTRING, 0, (LPARAM)display); + } +} + +void etcpmon_gui_clear_metrics(struct etcpmon_app* app) { + if (!app) return; + + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_NAME, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RTT_LAST, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG10, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RTT_AVG100, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_JITTER, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_BYTES_SENT, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_RETRANS, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_ACKS, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_INFLIGHT, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_ETCP_LINKS, ""); + + SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_BYTES, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_BYTES, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_PKTS, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_PKTS, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_READ_ERRS, ""); + SetDlgItemTextA(app->hWndMain, IDC_EDIT_TUN_WRITE_ERRS, ""); + + SendMessage(app->hListLinks, LB_RESETCONTENT, 0, 0); + SendMessage(app->hListConnections, LB_RESETCONTENT, 0, 0); +} diff --git a/tools/etcpmon/etcpmon_gui.h b/tools/etcpmon/etcpmon_gui.h new file mode 100644 index 00000000..85c485a1 --- /dev/null +++ b/tools/etcpmon/etcpmon_gui.h @@ -0,0 +1,139 @@ +/* + * etcpmon_gui.h - ETCP Monitor GUI Header + */ + +#ifndef ETCPMON_GUI_H +#define ETCPMON_GUI_H + +/* Exclude winsock from windows.h to avoid conflict with winsock2.h */ +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +/* Windows headers */ +#include +#include + +/* Client structure is defined in etcpmon_client.h */ +struct etcpmon_client; +struct etcpmon_conn_info; +struct etcpmon_rsp_metrics; +struct etcpmon_link_metrics; + +#ifdef __cplusplus +extern "C" { +#endif + +/* Window class name */ +#define ETCPMON_WINDOW_CLASS "ETCPMonMainWindow" + +/* Control IDs */ +#define IDC_STATIC -1 +#define IDC_EDIT_ADDR 100 +#define IDC_EDIT_PORT 101 +#define IDC_BTN_CONNECT 102 +#define IDC_BTN_DISCONNECT 103 +#define IDC_LIST_CONNECTIONS 104 +#define IDC_TIMER_UPDATE 105 + +/* ETCP Metrics control IDs */ +#define IDC_EDIT_ETCP_NAME 200 +#define IDC_EDIT_ETCP_RTT_LAST 201 +#define IDC_EDIT_ETCP_RTT_AVG10 202 +#define IDC_EDIT_ETCP_RTT_AVG100 203 +#define IDC_EDIT_ETCP_JITTER 204 +#define IDC_EDIT_ETCP_BYTES_SENT 205 +#define IDC_EDIT_ETCP_RETRANS 206 +#define IDC_EDIT_ETCP_ACKS 207 +#define IDC_EDIT_ETCP_INFLIGHT 208 +#define IDC_EDIT_ETCP_LINKS 209 + +/* TUN Metrics control IDs */ +#define IDC_EDIT_TUN_READ_BYTES 300 +#define IDC_EDIT_TUN_WRITE_BYTES 301 +#define IDC_EDIT_TUN_READ_PKTS 302 +#define IDC_EDIT_TUN_WRITE_PKTS 303 +#define IDC_EDIT_TUN_READ_ERRS 304 +#define IDC_EDIT_TUN_WRITE_ERRS 305 + +/* Link list control ID */ +#define IDC_LIST_LINKS 400 + +/* Main application structure */ +struct etcpmon_app { + HINSTANCE hInstance; + HWND hWndMain; + HWND hWndStatus; + + /* Connection controls */ + HWND hEditAddr; + HWND hEditPort; + HWND hBtnConnect; + HWND hBtnDisconnect; + HWND hListConnections; + + /* ETCP metrics controls */ + HWND hEditEtcpName; + HWND hEditEtcpRttLast; + HWND hEditEtcpRttAvg10; + HWND hEditEtcpRttAvg100; + HWND hEditEtcpJitter; + HWND hEditEtcpBytesSent; + HWND hEditEtcpRetrans; + HWND hEditEtcpAcks; + HWND hEditEtcpInflight; + HWND hEditEtcpLinks; + + /* TUN metrics controls */ + HWND hEditTunReadBytes; + HWND hEditTunWriteBytes; + HWND hEditTunReadPkts; + HWND hEditTunWritePkts; + HWND hEditTunReadErrs; + HWND hEditTunWriteErrs; + + /* Links list */ + HWND hListLinks; + + /* Client state - pointer to actual client structure */ + struct etcpmon_client* client; + + /* Timer ID */ + UINT_PTR updateTimer; + int isConnected; +}; + +/* Initialize application */ +int etcpmon_gui_init(struct etcpmon_app* app, HINSTANCE hInstance); + +/* Create main window */ +HWND etcpmon_gui_create_window(struct etcpmon_app* app); + +/* Run message loop */ +int etcpmon_gui_run(struct etcpmon_app* app); + +/* Cleanup */ +void etcpmon_gui_cleanup(struct etcpmon_app* app); + +/* Update connection status display */ +void etcpmon_gui_set_status(struct etcpmon_app* app, const char* text); + +/* Update connection list */ +void etcpmon_gui_update_conn_list(struct etcpmon_app* app, + struct etcpmon_conn_info* list, + uint8_t count); + +/* Update metrics display */ +void etcpmon_gui_update_metrics(struct etcpmon_app* app, + struct etcpmon_rsp_metrics* metrics, + struct etcpmon_link_metrics* links, + uint8_t links_count); + +/* Clear metrics display */ +void etcpmon_gui_clear_metrics(struct etcpmon_app* app); + +#ifdef __cplusplus +} +#endif + +#endif /* ETCPMON_GUI_H */ diff --git a/tools/etcpmon/etcpmon_main.c b/tools/etcpmon/etcpmon_main.c new file mode 100644 index 00000000..7ec10054 --- /dev/null +++ b/tools/etcpmon/etcpmon_main.c @@ -0,0 +1,36 @@ +/* + * etcpmon_main.c - ETCP Monitor Main Entry Point + */ + +#include "etcpmon_gui.h" +#include + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpCmdLine, int nCmdShow) { + (void)hPrevInstance; + (void)lpCmdLine; + (void)nCmdShow; + + struct etcpmon_app app; + + /* Initialize GUI */ + if (etcpmon_gui_init(&app, hInstance) != 0) { + MessageBoxA(NULL, "Failed to initialize application", "Error", MB_OK | MB_ICONERROR); + return 1; + } + + /* Create main window */ + if (!etcpmon_gui_create_window(&app)) { + MessageBoxA(NULL, "Failed to create main window", "Error", MB_OK | MB_ICONERROR); + etcpmon_gui_cleanup(&app); + return 1; + } + + /* Run message loop */ + int result = etcpmon_gui_run(&app); + + /* Cleanup */ + etcpmon_gui_cleanup(&app); + + return result; +} diff --git a/tools/etcpmon/etcpmon_protocol.h b/tools/etcpmon/etcpmon_protocol.h new file mode 100644 index 00000000..24ba50e4 --- /dev/null +++ b/tools/etcpmon/etcpmon_protocol.h @@ -0,0 +1,191 @@ +/* + * etcpmon_protocol.h - ETCP Monitor Protocol Definitions + * + * Binary protocol for monitoring ETCP connections + * Format: [2 bytes: msg_size] [1 byte: msg_type] [payload...] + * + * Period: 100ms (10 updates per second) + */ + +#ifndef ETCPMON_PROTOCOL_H +#define ETCPMON_PROTOCOL_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* ============================================================================ + * Protocol Constants + * ============================================================================ */ + +#define ETCPMON_PROTOCOL_VERSION 1 +#define ETCPMON_MAX_MSG_SIZE 4096 +#define ETCPMON_MAX_CONN_NAME 32 +#define ETCPMON_MAX_CONNECTIONS 256 +#define ETCPMON_MAX_LINKS 16 + +/* Update interval in milliseconds */ +#define ETCPMON_UPDATE_INTERVAL_MS 100 + +/* ============================================================================ + * Message Types + * ============================================================================ */ + +/* Client -> Server commands */ +#define ETCPMON_CMD_LIST_CONN 0x01 /* Request connection list */ +#define ETCPMON_CMD_SELECT_CONN 0x02 /* Select connection by peer_node_id */ +#define ETCPMON_CMD_GET_METRICS 0x03 /* Request metrics for selected */ +#define ETCPMON_CMD_DISCONNECT 0x04 /* Disconnect from monitoring */ + +/* Server -> Client responses */ +#define ETCPMON_RSP_CONN_LIST 0x81 /* Connection list response */ +#define ETCPMON_RSP_METRICS 0x82 /* Metrics response */ +#define ETCPMON_RSP_ERROR 0xFF /* Error response */ + +/* Error codes for RSP_ERROR */ +#define ETCPMON_ERR_INVALID_CMD 0x01 /* Unknown command */ +#define ETCPMON_ERR_INVALID_CONN 0x02 /* Invalid connection ID */ +#define ETCPMON_ERR_NO_CONN_SELECTED 0x03 /* No connection selected */ +#define ETCPMON_ERR_SERVER_BUSY 0x04 /* Server too busy */ + +/* ============================================================================ + * Message Header + * ============================================================================ */ + +#pragma pack(push, 1) + +struct etcpmon_msg_header { + uint16_t size; /* Total message size including header */ + uint8_t type; /* Message type (command or response) */ +}; + +/* ============================================================================ + * Commands (Client -> Server) + * ============================================================================ */ + +/* CMD_LIST_CONN: No payload required */ + +/* CMD_SELECT_CONN: Select connection to monitor */ +struct etcpmon_cmd_select { + uint64_t peer_node_id; /* peer_node_id of connection to monitor */ +}; + +/* CMD_GET_METRICS: No payload required (uses previously selected connection) */ + +/* CMD_DISCONNECT: No payload required */ + +/* ============================================================================ + * Responses (Server -> Client) + * ============================================================================ */ + +/* RSP_CONN_LIST: List of active connections */ +struct etcpmon_conn_info { + uint64_t peer_node_id; /* Connection identifier */ + char name[ETCPMON_MAX_CONN_NAME]; /* Null-terminated name */ +}; + +struct etcpmon_rsp_conn_list { + uint8_t count; /* Number of connections */ + /* Followed by count * struct etcpmon_conn_info */ +}; + +/* RSP_METRICS: Full metrics for ETCP connection and its links */ + +/* ETCP Connection metrics */ +struct etcpmon_etcp_metrics { + uint32_t rtt_last; /* Last RTT (0.1ms units) */ + uint32_t rtt_avg_10; /* Average RTT over 10 packets */ + uint32_t rtt_avg_100; /* Average RTT over 100 packets */ + uint32_t jitter; /* Jitter (max-min RTT) */ + uint64_t bytes_sent_total; /* Total bytes sent */ + uint32_t retrans_count; /* Retransmission count */ + uint32_t ack_count; /* ACK packets received */ + uint32_t unacked_bytes; /* Current unacknowledged bytes */ + uint32_t optimal_inflight; /* Target inflight window */ + uint8_t links_count; /* Number of active links */ +}; + +/* Link metrics */ +struct etcpmon_link_metrics { + uint8_t local_link_id; /* Local link identifier */ + uint8_t status; /* 1 = up, 0 = down */ + uint32_t encrypt_errors; /* Encryption errors */ + uint32_t decrypt_errors; /* Decryption errors */ + uint32_t send_errors; /* Send errors */ + uint32_t recv_errors; /* Receive errors */ + uint64_t total_encrypted; /* Total bytes encrypted/sent */ + uint64_t total_decrypted; /* Total bytes decrypted/received */ + uint32_t bandwidth; /* Configured bandwidth (Kbps) */ + uint32_t nat_changes_count; /* NAT address changes */ +}; + +/* TUN interface metrics */ +struct etcpmon_tun_metrics { + uint64_t bytes_read; /* Bytes read from TUN */ + uint64_t bytes_written; /* Bytes written to TUN */ + uint32_t packets_read; /* Packets read from TUN */ + uint32_t packets_written; /* Packets written to TUN */ + uint32_t read_errors; /* Read error count */ + uint32_t write_errors; /* Write error count */ +}; + +struct etcpmon_rsp_metrics { + struct etcpmon_etcp_metrics etcp; /* ETCP connection metrics */ + struct etcpmon_tun_metrics tun; /* TUN interface metrics */ + /* Followed by etcp.links_count * struct etcpmon_link_metrics */ +}; + +/* RSP_ERROR: Error response */ +struct etcpmon_rsp_error { + uint8_t error_code; /* Error code */ + /* Followed by null-terminated error message string */ +}; + +#pragma pack(pop) + +/* ============================================================================ + * Helper Macros + * ============================================================================ */ + +/* Calculate size of connection list response */ +#define ETCPMON_CONN_LIST_SIZE(count) \ + (sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_conn_list) + \ + (count) * sizeof(struct etcpmon_conn_info)) + +/* Calculate size of metrics response */ +#define ETCPMON_METRICS_SIZE(links_count) \ + (sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_metrics) + \ + (links_count) * sizeof(struct etcpmon_link_metrics)) + +/* Calculate size of error response */ +#define ETCPMON_ERROR_SIZE(msg_len) \ + (sizeof(struct etcpmon_msg_header) + sizeof(struct etcpmon_rsp_error) + (msg_len) + 1) + +/* ============================================================================ + * Utility Functions (optional, for both client and server) + * ============================================================================ */ + +/* Build message header */ +static inline void etcpmon_build_header(struct etcpmon_msg_header* hdr, + uint16_t payload_size, + uint8_t msg_type) { + hdr->size = sizeof(struct etcpmon_msg_header) + payload_size; + hdr->type = msg_type; +} + +/* Validate message header */ +static inline int etcpmon_validate_header(const struct etcpmon_msg_header* hdr) { + if (hdr->size < sizeof(struct etcpmon_msg_header) || + hdr->size > ETCPMON_MAX_MSG_SIZE) { + return -1; + } + return 0; +} + +#ifdef __cplusplus +} +#endif + +#endif /* ETCPMON_PROTOCOL_H */