Browse Source

+etcpmon

nodeinfo-routing-update
jeka 7 months ago
parent
commit
fee6542abb
  1. 171
      tools/etcpmon/IMPLEMENTATION.md
  2. 31
      tools/etcpmon/Makefile.mingw
  3. 137
      tools/etcpmon/README.md
  4. 58
      tools/etcpmon/build.bat
  5. 104
      tools/etcpmon/build.sh
  6. 9
      tools/etcpmon/clean.bat
  7. 581
      tools/etcpmon/etcpmon_client.c
  8. 115
      tools/etcpmon/etcpmon_client.h
  9. 663
      tools/etcpmon/etcpmon_gui.c
  10. 139
      tools/etcpmon/etcpmon_gui.h
  11. 36
      tools/etcpmon/etcpmon_main.c
  12. 191
      tools/etcpmon/etcpmon_protocol.h

171
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.

31
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)

137
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.

58
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

104
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

9
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

581
tools/etcpmon/etcpmon_client.c

@ -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;
}

115
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 <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 */

663
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 <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);
}

139
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 <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 */

36
tools/etcpmon/etcpmon_main.c

@ -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;
}

191
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 <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…
Cancel
Save