12 changed files with 2235 additions and 0 deletions
@ -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. |
||||||
@ -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)
|
||||||
@ -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. |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -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 |
||||||
@ -0,0 +1,581 @@ |
|||||||
|
/*
|
||||||
|
* etcpmon_client.c - ETCP Monitor Client Network Implementation |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "etcpmon_client.h" |
||||||
|
#include <stdio.h> |
||||||
|
#include <string.h> |
||||||
|
#include <ws2tcpip.h> |
||||||
|
#include <windows.h> |
||||||
|
|
||||||
|
//#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; |
||||||
|
} |
||||||
@ -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 <winsock2.h> |
||||||
|
#include <stdint.h> |
||||||
|
#include <stdio.h> |
||||||
|
|
||||||
|
#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 */ |
||||||
@ -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 <winsock2.h> |
||||||
|
#include <windows.h> |
||||||
|
#include <commctrl.h> |
||||||
|
|
||||||
|
#include "etcpmon_gui.h" |
||||||
|
#include "etcpmon_client.h" |
||||||
|
#include "etcpmon_protocol.h" |
||||||
|
#include <stdio.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
//#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); |
||||||
|
} |
||||||
@ -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 <windows.h> |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
/* 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 */ |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
/*
|
||||||
|
* etcpmon_main.c - ETCP Monitor Main Entry Point |
||||||
|
*/ |
||||||
|
|
||||||
|
#include "etcpmon_gui.h" |
||||||
|
#include <stdio.h> |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
@ -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 <stdint.h> |
||||||
|
|
||||||
|
#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 */ |
||||||
Loading…
Reference in new issue