14 KiB
Локальное простукивание портов (Local Port Knocking)
Обзор
Функционал локального простукивания позволяет выполнять knock операции напрямую через Node.js API без использования внешнего HTTP API сервера. Это обеспечивает независимость от внешних сервисов и возможность работы в автономном режиме.
Условия активации
Локальное простукивание активируется автоматически когда:
- API URL пуст - поле
apiBase
не заполнено или содержит пустую строку - API URL = "internal" - значение
apiBase
установлено в"internal"
- 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)
Формат целей:
protocol:host:port[:gateway]
Примеры:
tcp:127.0.0.1:22
udp:192.168.1.1:53
tcp:example.com:80:gateway.com
Поддержка Gateway:
Gateway можно указать двумя способами:
- В строке цели:
tcp:host:port:gateway_ip
- Глобально через поле Gateway: используется для всех целей, если не указан в самой цели
Приоритет gateway:
- Gateway из строки цели имеет приоритет над глобальным
- Если gateway не указан, используется системный маршрут по умолчанию
Обход VPN/туннелей:
Gateway использует localAddress
для принудительного направления трафика через указанный локальный IP-адрес. Это позволяет:
- Обходить VPN соединения (WireGuard, OpenVPN и др.)
- Использовать конкретный сетевой интерфейс
- Направлять трафик через локальный шлюз
Пример обхода WireGuard:
{
"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
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 режим
// Извлекает targets из поля #targets
targets = qsi("#targets").value.split(';').filter(t => t.trim());
2. Form режим
// Сериализует формы в строку targets
targets = [serializeFormTargetsToInline()];
3. YAML режим
// Парсит 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 локального простукивания
Входные параметры
{
targets: string[], // Массив целей в формате "protocol:host:port[:gateway]"
delay: string, // Задержка между целями (например "1s")
verbose: boolean, // Подробный вывод в консоль
gateway: string // Глобальный gateway для всех целей (опционально)
}
Выходные данные
{
success: boolean, // Успешность операции
results: [ // Детальные результаты по каждой цели
{
target: string, // Исходная строка цели
success: boolean, // Успешность простукивания
message: string // Сообщение о результате
}
],
summary: { // Общая статистика
total: number, // Общее количество целей
successful: number, // Количество успешных
failed: number // Количество неудачных
}
}
Примеры использования
Настройка для локального режима
Вариант 1: Пустой API URL
{
"apiBase": ""
}
Вариант 2: Специальное значение
{
"apiBase": "internal"
}
Пример конфигурации
{
"apiBase": "internal",
"gateway": "192.168.1.1",
"inlineTargets": "tcp:127.0.0.1:22;tcp:192.168.1.100:80",
"delay": "2s"
}
Пример 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 процесса появляются сообщения:
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 процесса:
console.log('Local knock results:', result.results);
Статус в UI
В интерфейсе отображается краткий статус:
"Локальное простукивание завершено: 2/3 успешно"
Ограничения
Поддерживаемые протоколы
- ✅ TCP - полная поддержка
- ✅ UDP - отправка пакетов
- ❌ ICMP - не поддерживается
- ❌ Другие протоколы - не поддерживаются
Таймауты
- TCP: 5 секунд по умолчанию
- UDP: 5 секунд по умолчанию
- Настраивается в коде функций
knockTcp
иknockUdp
Сетевая безопасность
- Локальное простукивание использует системные сокеты
- Подчиняется правилам файрвола операционной системы
- Не требует дополнительных разрешений в Electron
Совместимость
Операционные системы
- ✅ Windows - полная поддержка
- ✅ macOS - полная поддержка
- ✅ Linux - полная поддержка
Electron версии
- ✅ v28+ - протестировано
- ⚠️ v27 и ниже - может потребовать адаптации
Переключение между режимами
API → Локальный
- Открыть настройки (Ctrl/Cmd+,)
- Установить
apiBase
в"internal"
- Сохранить настройки
- Перезапустить приложение
Локальный → API
- Открыть настройки
- Установить корректный
apiBase
URL - Сохранить настройки
- Перезапустить приложение
Устранение неполадок
Проблема: "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
Разработка и расширение
Добавление новых протоколов
- Создать функцию
knockProtocol()
вsrc/main/main.js
- Добавить обработку в
performLocalKnock()
- Обновить документацию
Настройка таймаутов
// В src/main/main.js
function knockTcp(host, port, timeout = 10000) { // 10 секунд
// ...
}
Добавление дополнительных опций
// Расширить payload в IPC
{
targets: string[],
delay: string,
verbose: boolean,
timeout: number, // новый параметр
retries: number // новый параметр
}
Пример обхода WireGuard
Проблема
WireGuard активен, весь трафик идет через туннель, но нужно простучать порт через локальный шлюз.
Решение
{
"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+