Files
knock-gui/desktop/LOCAL_KNOCKING.md

412 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Локальное простукивание портов (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+