added desktop and rust version

This commit is contained in:
2025-09-25 15:32:49 +06:00
parent 10af1a9a63
commit 2c2725cd19
29 changed files with 12835 additions and 1589 deletions

411
desktop/LOCAL_KNOCKING.md Normal file
View File

@@ -0,0 +1,411 @@
# Локальное простукивание портов (Local Port Knocking)
## Обзор
Функционал локального простукивания позволяет выполнять knock операции напрямую через Node.js API без использования внешнего HTTP API сервера. Это обеспечивает независимость от внешних сервисов и возможность работы в автономном режиме.
## Условия активации
Локальное простукивание активируется автоматически когда:
1. **API URL пуст** - поле `apiBase` не заполнено или содержит пустую строку
2. **API URL = "internal"** - значение `apiBase` установлено в `"internal"`
3. **API URL не задан** - значение `apiBase` равно `null` или `undefined`
## Архитектура
### Файлы реализации
#### 1. `src/main/main.js` - Основная логика
**Строки 210-367**: Реализация локального простукивания
**Ключевые функции:**
- `parseTarget(targetStr)` - парсинг строки цели в объект
- `parseDelay(delayStr)` - конвертация задержки в миллисекунды
- `knockTcp(host, port, timeout)` - TCP простукивание
- `knockUdp(host, port, timeout)` - UDP простукивание
- `performLocalKnock(targets, delay, verbose)` - основная функция простукивания
- `ipcMain.handle('knock:local', ...)` - IPC обработчик
**Поддерживаемые протоколы:**
- **TCP** - создает соединение и немедленно закрывает
- **UDP** - отправляет пакет данных (fire-and-forget)
**Формат целей:**
``` text
protocol:host:port[:gateway]
```
Примеры:
- `tcp:127.0.0.1:22`
- `udp:192.168.1.1:53`
- `tcp:example.com:80:gateway.com`
**Поддержка Gateway:**
Gateway можно указать двумя способами:
1. **В строке цели**: `tcp:host:port:gateway_ip`
2. **Глобально через поле Gateway**: используется для всех целей, если не указан в самой цели
**Приоритет gateway:**
- Gateway из строки цели имеет приоритет над глобальным
- Если gateway не указан, используется системный маршрут по умолчанию
**Обход VPN/туннелей:**
Gateway использует `localAddress` для принудительного направления трафика через указанный локальный IP-адрес. Это позволяет:
- Обходить VPN соединения (WireGuard, OpenVPN и др.)
- Использовать конкретный сетевой интерфейс
- Направлять трафик через локальный шлюз
**Пример обхода WireGuard:**
```json
{
"gateway": "192.168.89.1"
}
```
Трафик будет направлен через интерфейс с IP `192.168.89.1`, минуя WireGuard туннель.
## Хелпер для gateway (Rust приоритетно, Go как fallback)
Когда задан `gateway` (IP или имя интерфейса), десктоп-приложение запускает встроенный бинарь из `desktop/bin/`:
- `knock-local-rust` — приоритетный Rust-хелпер (если присутствует)
- `knock-local` — Go-хелпер как запасной вариант
Оба на Linux используют `SO_BINDTODEVICE` для привязки к интерфейсу и надежного обхода VPN/туннелей (WireGuard и пр.).
Сборка при разработке:
- `npm run rust:build` — соберёт Rust-хелпер
- `npm run go:build` — соберёт Go-хелпер
В прод-сборках оба бинаря автоматически включаются в образ приложения.
Важно для TCP: привязка интерфейса устанавливается до `connect()`. Это гарантирует, что исходящее соединение пойдёт через нужный интерфейс, а не в туннель.
**Формат задержки:**
- `1s` - 1 секунда
- `500ms` - 500 миллисекунд (не поддерживается, используйте `0.5s`)
- `2m` - 2 минуты
- `1h` - 1 час
#### 2. `src/preload/preload.js` - IPC мост
**Строка 13**: Добавлен метод `localKnock`
```javascript
localKnock: async (payload) => ipcRenderer.invoke('knock:local', payload)
```
#### 3. `src/renderer/renderer.js` - UI логика
**Строки 317-376**: Логика выбора между локальным и API простукиванием
**Ключевые изменения:**
- Проверка условия `useLocalKnock = !apiBase || apiBase.trim() === '' || apiBase === 'internal'`
- Извлечение targets из всех режимов (inline, form, yaml)
- Вызов `window.api.localKnock()` вместо HTTP запросов
## Режимы работы
### 1. Inline режим
```javascript
// Извлекает targets из поля #targets
targets = qsi("#targets").value.split(';').filter(t => t.trim());
```
### 2. Form режим
```javascript
// Сериализует формы в строку targets
targets = [serializeFormTargetsToInline()];
```
### 3. YAML режим
```javascript
// Парсит YAML и извлекает targets
const config = yaml.load(yamlContent);
targets = config.targets.map(t => {
const protocol = t.protocol || 'tcp';
const host = t.host || '127.0.0.1';
const ports = t.ports || [t.port] || [22];
return ports.map(port => `${protocol}:${host}:${port}`);
}).flat();
```
## API локального простукивания
### Входные параметры
```javascript
{
targets: string[], // Массив целей в формате "protocol:host:port[:gateway]"
delay: string, // Задержка между целями (например "1s")
verbose: boolean, // Подробный вывод в консоль
gateway: string // Глобальный gateway для всех целей (опционально)
}
```
### Выходные данные
```javascript
{
success: boolean, // Успешность операции
results: [ // Детальные результаты по каждой цели
{
target: string, // Исходная строка цели
success: boolean, // Успешность простукивания
message: string // Сообщение о результате
}
],
summary: { // Общая статистика
total: number, // Общее количество целей
successful: number, // Количество успешных
failed: number // Количество неудачных
}
}
```
## Примеры использования
### Настройка для локального режима
#### Вариант 1: Пустой API URL
```json
{
"apiBase": ""
}
```
#### Вариант 2: Специальное значение
```json
{
"apiBase": "internal"
}
```
### Пример конфигурации
```json
{
"apiBase": "internal",
"gateway": "192.168.1.1",
"inlineTargets": "tcp:127.0.0.1:22;tcp:192.168.1.100:80",
"delay": "2s"
}
```
### Пример YAML конфигурации
```yaml
targets:
- protocol: tcp
host: 127.0.0.1
ports: [22, 80]
- protocol: udp
host: 192.168.1.1
ports: [53]
delay: 1s
```
## Логирование и отладка
### Консольный вывод
При `verbose: true` в консоли main процесса появляются сообщения:
``` text
Knocking TCP 127.0.0.1:22
Knocking UDP 192.168.1.1:53 via 192.168.1.1
Knocking TCP example.com:80 via 10.0.0.1
```
### Результаты в DevTools
Детальные результаты логируются в консоль renderer процесса:
```javascript
console.log('Local knock results:', result.results);
```
### Статус в UI
В интерфейсе отображается краткий статус:
``` text
"Локальное простукивание завершено: 2/3 успешно"
```
## Ограничения
### Поддерживаемые протоколы
- ✅ **TCP** - полная поддержка
- ✅ **UDP** - отправка пакетов
- ❌ **ICMP** - не поддерживается
- ❌ **Другие протоколы** - не поддерживаются
### Таймауты
- **TCP**: 5 секунд по умолчанию
- **UDP**: 5 секунд по умолчанию
- Настраивается в коде функций `knockTcp` и `knockUdp`
### Сетевая безопасность
- Локальное простукивание использует системные сокеты
- Подчиняется правилам файрвола операционной системы
- Не требует дополнительных разрешений в Electron
## Совместимость
### Операционные системы
- ✅ **Windows** - полная поддержка
- ✅ **macOS** - полная поддержка
- ✅ **Linux** - полная поддержка
### Electron версии
- ✅ **v28+** - протестировано
- ⚠️ **v27 и ниже** - может потребовать адаптации
## Переключение между режимами
### API → Локальный
1. Открыть настройки (Ctrl/Cmd+,)
2. Установить `apiBase` в `"internal"`
3. Сохранить настройки
4. Перезапустить приложение
### Локальный → API
1. Открыть настройки
2. Установить корректный `apiBase` URL
3. Сохранить настройки
4. Перезапустить приложение
## Устранение неполадок
### Проблема: "No targets provided"
**Причина**: Не удалось извлечь цели из конфигурации
**Решение**: Проверить корректность заполнения полей targets
### Проблема: "Unsupported protocol"
**Причина**: Использован неподдерживаемый протокол
**Решение**: Использовать только `tcp` или `udp`
### Проблема: "Connection timeout"
**Причина**: Цель недоступна или заблокирована файрволом
**Решение**: Проверить доступность цели и настройки файрвола
### Проблема: "Invalid target format"
**Причина**: Неверный формат строки цели
**Решение**: Использовать формат `protocol:host:port`
### Проблема: "Uncaught Exception"
**Причина**: Необработанные ошибки в асинхронных операциях
**Решение**: ✅ **ИСПРАВЛЕНО** - Добавлены глобальные обработчики ошибок и защита от двойного resolve
**Исправления в версии 1.1:**
- Добавлен флаг `resolved` в TCP/UDP функциях для предотвращения двойного вызова resolve
- Глобальные обработчики `uncaughtException` и `unhandledRejection` в main процессе
- Глобальные обработчики ошибок в renderer процессе
- Улучшенная валидация входных данных в IPC обработчике
- Try-catch блоки вокруг всех критических операций
## Безопасность
### Ограничения доступа
- Локальное простукивание выполняется с правами пользователя приложения
- Не требует root/administrator прав
- Подчиняется системным ограничениям сетевого доступа
### Логирование
- Результаты простукивания логируются в консоль
- Не сохраняются в файлы по умолчанию
- Можно отключить через параметр `verbose: false`
## Разработка и расширение
### Добавление новых протоколов
1. Создать функцию `knockProtocol()` в `src/main/main.js`
2. Добавить обработку в `performLocalKnock()`
3. Обновить документацию
### Настройка таймаутов
```javascript
// В src/main/main.js
function knockTcp(host, port, timeout = 10000) { // 10 секунд
// ...
}
```
### Добавление дополнительных опций
```javascript
// Расширить payload в IPC
{
targets: string[],
delay: string,
verbose: boolean,
timeout: number, // новый параметр
retries: number // новый параметр
}
```
---
## Пример обхода WireGuard
### Проблема
WireGuard активен, весь трафик идет через туннель, но нужно простучать порт через локальный шлюз.
### Решение
```json
{
"apiBase": "internal",
"gateway": "192.168.89.1",
"inlineTargets": "tcp:external-server.com:22",
"delay": "1s"
}
```
### Логи
```
Using localAddress 192.168.89.1 to bypass VPN/tunnel
Knocking TCP external-server.com:22 via 192.168.89.1
TCP connection to external-server.com:22 via 192.168.89.1 successful
```
---
**Версия документации**: 1.2
**Дата создания**: 2024
**Дата обновления**: 2024 (поддержка обхода VPN)
**Совместимость**: Electron Desktop App v1.0+