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