diff --git a/.gitignore b/.gitignore index e4df9a3..59eccf0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,10 @@ vpn-client.exe *.dylib *.bat *.sh +init.bat +init.sh +build.bat +build.sh # Test binary *.test diff --git a/INSTALL_GUIDE.md b/INSTALL_GUIDE.md new file mode 100644 index 0000000..7d4659a --- /dev/null +++ b/INSTALL_GUIDE.md @@ -0,0 +1,136 @@ +# Руководство по созданию установщика VPN Client Go + +## Требования + +1. **Inno Setup 7** - скачайте с официального сайта: https://jrsoftware.org/isdl.php +2. Собранный файл `vpn-client-cli.exe` + +## Шаги для создания установщика + +### 1. Установите Inno Setup 7 + +Скачайте и установите Inno Setup 7 с официального сайта. + +### 2. Подготовьте файлы + +Убедитесь, что в папке `vpn_client_go` есть: +- `vpn-client-cli.exe` (собранный исполняемый файл) +- `installer.iss` (скрипт установщика) +- `LICENSE.txt` (лицензия) +- `.vpn_client/` (папка конфигурации - создастся автоматически) + +Опционально (будут включены, если существуют): +- `xray/` (папка с Xray - можно не включать, загрузится автоматически) +- `v2ray/` (папка с V2Ray - можно не включать, загрузится автоматически) +- `README.md` (документация) +- `CHANGELOG.md` (список изменений) + +### 3. Настройте installer.iss + +Откройте `installer.iss` и измените: + +```pascal +#define MyAppName "VPN Client Go" +#define MyAppVersion "1.0.0" ; Ваша версия +#define MyAppPublisher "Your Name" ; Ваше имя +#define MyAppURL "https://github.com/yourusername/vpn-client-go" ; Ваш URL +``` + +Опционально добавьте иконку: +```pascal +SetupIconFile=icon.ico ; Раскомментируйте и укажите путь к .ico файлу +``` + +### 4. Создайте установщик + +#### Способ 1: Через GUI +1. Запустите Inno Setup Compiler +2. Откройте файл `installer.iss` +3. Нажмите `Build` → `Compile` (или F9) +4. Установщик будет создан в папке `installer_output/` + +#### Способ 2: Через командную строку +```cmd +"C:\Program Files (x86)\Inno Setup 7\ISCC.exe" installer.iss +``` + +### 5. Результат + +После компиляции в папке `installer_output/` появится файл: +``` +VPNClientGo-Setup-1.0.0.exe +``` + +## Особенности установщика + +✅ **Автоматические права администратора** - установщик запрашивает права админа +✅ **Поддержка русского и английского языков** +✅ **Создание ярлыков** на рабочем столе и в меню Пуск +✅ **Автоматическая загрузка Xray/V2Ray** при первом запуске +✅ **Чистая деинсталляция** - удаляет все файлы и папки + +## Что делает установщик + +1. Устанавливает `vpn-client-cli.exe` в `C:\Program Files\VPN Client Go\` +2. Создает папки для конфигурации и логов +3. Копирует Xray и V2Ray (если они есть в исходниках) +4. Создает ярлыки с правами администратора +5. Регистрирует программу в списке установленных приложений + +## Запуск после установки + +Программа автоматически запускается с правами администратора через ярлыки. + +## Деинсталляция + +Используйте стандартную деинсталляцию Windows: +- Панель управления → Программы и компоненты +- Или через ярлык "Удалить VPN Client Go" в меню Пуск + +## Дополнительные настройки + +### Добавить иконку приложения + +1. Создайте файл `icon.ico` (256x256 или 128x128) +2. Поместите его в папку `vpn_client_go/` +3. Раскомментируйте строку в `installer.iss`: + ```pascal + SetupIconFile=icon.ico + ``` + +### Изменить папку установки по умолчанию + +В `installer.iss` измените: +```pascal +DefaultDirName={autopf}\{#MyAppName} +``` + +На например: +```pascal +DefaultDirName=C:\VPNClient +``` + +### Добавить дополнительные файлы + +В секцию `[Files]` добавьте: +```pascal +Source: "your_file.txt"; DestDir: "{app}"; Flags: ignoreversion +``` + +## Автоматизация сборки + +Создайте bat-файл `build_installer.bat`: + +```batch +@echo off +echo Building VPN Client... +call build_cli_only.bat + +echo Creating installer... +"C:\Program Files (x86)\Inno Setup 7\ISCC.exe" installer.iss + +echo Done! +pause +``` + +Теперь можно собрать всё одной командой! diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..add804f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 VPN Client Go + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Markdown/SECURITY_GUIDE.md b/Markdown/SECURITY_GUIDE.md new file mode 100644 index 0000000..c2ec9b2 --- /dev/null +++ b/Markdown/SECURITY_GUIDE.md @@ -0,0 +1,285 @@ +# 🔒 Руководство по безопасности VPN клиента + +## Обзор + +VPN клиент теперь включает комплексную систему безопасности для защиты вашей конфиденциальности и предотвращения утечек данных. + +## ⚠️ ВАЖНОЕ ПРЕДУПРЕЖДЕНИЕ О KILL SWITCH + +**Kill Switch ОТКЛЮЧЕН ПО УМОЛЧАНИЮ!** + +- Kill Switch может заблокировать весь интернет при неправильном использовании +- Включайте его ТОЛЬКО если понимаете, как он работает +- ВСЕГДА отключайте VPN через меню программы (не закрывайте крестиком) +- Если интернет сломался: запустите файл `ОТКЛЮЧИТЬ_KILLSWITCH.bat` + +**Для восстановления интернета:** +1. Запустите `ОТКЛЮЧИТЬ_KILLSWITCH.bat` от имени администратора +2. Или см. файл `ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt` + +## Основные функции безопасности + +### 1. 🛡️ Kill Switch (Аварийное отключение) + +**⚠️ ОТКЛЮЧЕНО ПО УМОЛЧАНИЮ - включайте только если понимаете риски!** + +**Что это?** +Kill Switch автоматически блокирует весь интернет-трафик при разрыве VPN соединения, предотвращая утечку вашего реального IP адреса. + +**Как работает:** +- При подключении к VPN создаются правила Windows Firewall +- Блокируется весь исходящий и входящий трафик +- Разрешается только трафик к VPN серверу и через VPN туннель +- При разрыве VPN весь интернет блокируется до восстановления соединения + +**⚠️ ВАЖНО:** +- **ВСЕГДА отключайте VPN через меню программы!** +- **НЕ закрывайте программу крестиком при активном Kill Switch!** +- **Если интернет сломался:** запустите `ОТКЛЮЧИТЬ_KILLSWITCH.bat` + +**Настройка:** +``` +Меню → Безопасность и защита → Настроить Kill Switch → Включить +``` + +**Восстановление интернета:** +``` +Запустите файл: ОТКЛЮЧИТЬ_KILLSWITCH.bat (от имени администратора) +Или см.: ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt +``` + +--- + +### 2. 🌐 Защита от DNS утечек + +**Что это?** +Защита DNS гарантирует, что все DNS запросы идут через VPN, а не через DNS провайдера. + +**Как работает:** +- Автоматически изменяет системные DNS серверы на безопасные (1.1.1.1, 8.8.8.8) +- Применяется ко всем сетевым интерфейсам +- Очищает DNS кэш для немедленного применения +- Восстанавливает оригинальные DNS при отключении VPN + +**Настройка:** +``` +Меню → Безопасность и защита → Настроить защиту DNS +``` + +**Рекомендуемые DNS серверы:** +- Cloudflare: `1.1.1.1`, `1.0.0.1` +- Google: `8.8.8.8`, `8.8.4.4` +- Quad9: `9.9.9.9`, `149.112.112.112` + +--- + +### 3. 🔐 Шифрование конфигураций + +**Что это?** +Все конфигурационные файлы (включая пароли и ключи VPN) шифруются с использованием AES-256-GCM. + +**Как работает:** +- Использует алгоритм AES-256 в режиме GCM (Galois/Counter Mode) +- Ключ шифрования привязан к вашему компьютеру (hostname + путь к программе) +- Применяется PBKDF2 с 100,000 итераций для усиления ключа +- Каждый файл имеет уникальную соль и nonce + +**Настройка:** +``` +Меню → Безопасность и защита → Управление шифрованием конфигов +``` + +**Безопасность:** +- Конфигурации невозможно расшифровать на другом компьютере +- Защита от перебора паролей благодаря PBKDF2 +- Аутентифицированное шифрование предотвращает подделку данных + +--- + +### 4. 📁 Защита файлов и директорий + +**Что это?** +Устанавливает строгие права доступа к конфигурационным файлам. + +**Как работает:** +- Директория `.vpn_client/`: права 0700 (только владелец) +- Конфигурационные файлы: права 0600 (только чтение/запись владельцем) +- Предотвращает доступ других пользователей системы + +**Настройка:** +``` +Меню → Безопасность и защита → Защитить директорию конфигураций +``` + +--- + +### 5. 🌍 Системный прокси для всего ПК + +**Что это?** +Автоматическая настройка системного прокси Windows для работы VPN во всех приложениях. + +**Как работает:** +- Изменяет настройки прокси в реестре Windows +- Применяется ко всем приложениям, использующим системные настройки +- Использует SOCKS5 прокси на `127.0.0.1:10808` +- Исключает локальные адреса из прокси + +**Автоматическая настройка:** +При подключении к VLESS серверу системный прокси настраивается автоматически. + +**Ручная настройка:** +1. Откройте: Настройки Windows → Сеть и Интернет → Прокси +2. Включите "Использовать прокси-сервер" +3. Адрес: `127.0.0.1`, Порт: `10808` +4. Тип: SOCKS5 + +--- + +## Проверка безопасности + +### Проверка утечек + +Используйте встроенную функцию проверки: +``` +Меню → Безопасность и защита → Проверить утечки +``` + +Проверяет: +- ✅ DNS утечки +- ✅ Статус Kill Switch +- ✅ Правильность настройки прокси + +### Внешние сервисы проверки + +После подключения к VPN проверьте: + +1. **IP адрес:** + - https://whoer.net + - https://ipleak.net + +2. **DNS утечки:** + - https://dnsleaktest.com + - https://www.dnsleaktest.org + +3. **WebRTC утечки:** + - https://browserleaks.com/webrtc + +--- + +## Рекомендации по безопасности + +### ✅ Обязательно + +1. **Всегда включайте Kill Switch** при работе с конфиденциальными данными +2. **Используйте защиту DNS** для предотвращения утечек +3. **Включите шифрование конфигов** для защиты учетных данных +4. **Регулярно проверяйте утечки** через меню безопасности +5. **Используйте надежные VPN серверы** с поддержкой современных протоколов + +### ⚠️ Важно + +1. **Не отключайте Kill Switch** во время активного VPN соединения +2. **Не делитесь конфигурационными файлами** - они содержат ваши ключи +3. **Проверяйте DNS** после каждого подключения +4. **Используйте HTTPS** сайты для дополнительной защиты +5. **Обновляйте клиент** для получения последних исправлений безопасности + +### 🔴 Никогда + +1. **Не запускайте VPN без Kill Switch** для критичных задач +2. **Не используйте публичные VPN** для конфиденциальных данных +3. **Не игнорируйте предупреждения** о утечках +4. **Не отключайте шифрование** без веской причины + +--- + +## Устранение проблем + +### Kill Switch не отключается + +```bash +# Вручную удалите правила файрвола +netsh advfirewall firewall delete rule name=all dir=out +netsh advfirewall firewall delete rule name=all dir=in +``` + +### DNS не восстанавливаются + +```bash +# Восстановите автоматическое получение DNS +netsh interface ip set dns "Ethernet" dhcp +netsh interface ip set dns "Wi-Fi" dhcp +ipconfig /flushdns +``` + +### Системный прокси не работает + +1. Проверьте настройки: `Настройки → Сеть и Интернет → Прокси` +2. Убедитесь, что VPN подключен +3. Перезапустите браузер/приложение +4. Проверьте, что порт 10808 не занят + +--- + +## Технические детали + +### Алгоритмы шифрования + +- **Алгоритм:** AES-256-GCM +- **Деривация ключа:** PBKDF2-SHA256 +- **Итерации:** 100,000 +- **Размер ключа:** 256 бит +- **Размер соли:** 256 бит + +### Правила файрвола + +Kill Switch создает следующие правила: +- `VPN_KillSwitch_Block_Out` - блокировка исходящего трафика +- `VPN_KillSwitch_Block_In` - блокировка входящего трафика +- `VPN_KillSwitch_Allow_Localhost` - разрешение локального трафика +- `VPN_KillSwitch_Allow_` - разрешение трафика к VPN серверу + +### Изменения реестра + +Системный прокси изменяет: +``` +HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings + - ProxyEnable: 1 + - ProxyServer: socks=127.0.0.1:10808 + - ProxyOverride: ;localhost;127.*;10.*;172.16.*;172.31.*;192.168.* +``` + +--- + +## FAQ + +**Q: Безопасно ли хранить конфигурации на диске?** +A: Да, при включенном шифровании конфигурации защищены AES-256 и привязаны к вашему компьютеру. + +**Q: Что делать если Kill Switch заблокировал интернет?** +A: Запустите клиент и отключите Kill Switch через меню безопасности, или используйте команды из раздела "Устранение проблем". + +**Q: Можно ли использовать VPN без системного прокси?** +A: Да, но тогда VPN будет работать только в приложениях с ручной настройкой SOCKS5 прокси. + +**Q: Защищает ли Kill Switch от утечек WebRTC?** +A: Нет, для защиты от WebRTC утечек используйте расширения браузера или отключите WebRTC. + +**Q: Как проверить, что VPN работает?** +A: Используйте встроенную проверку утечек или внешние сервисы типа whoer.net. + +--- + +## Поддержка + +При возникновении проблем с безопасностью: + +1. Проверьте логи в папке `logs/` +2. Используйте встроенную проверку утечек +3. Убедитесь, что у вас права администратора +4. Проверьте настройки файрвола Windows + +--- + +**Версия документа:** 1.0 +**Дата обновления:** 2026-04-12 diff --git a/README.md b/README.md index cf0f1f9..09dfce6 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ VPN клиент на Golang с поддержкой VLESS протокола и - ✅ Детальное логирование - ✅ Статистика трафика для WireGuard - ✅ Кроссплатформенность (Windows, Linux, macOS) +- ✅ **Системный прокси для всего ПК** +- ⚠️ **Kill Switch (временно отключен)** +- ✅ **Защита от DNS утечек** +- ✅ **Шифрование конфигураций (AES-256-GCM)** +- ✅ **Защита файлов и директорий** +- ✅ **Проверка утечек безопасности** ## Требования @@ -144,7 +150,31 @@ vpn-client-gui.exe --cli 127.0.0.1:10808 ``` -Настройте браузер или систему на использование этого прокси. +**Системный прокси настраивается автоматически!** VPN будет работать для всех приложений Windows. + +Для ручной настройки в браузере или других приложениях используйте указанный выше адрес. + +## 🔒 Безопасность + +Клиент включает комплексную систему безопасности: + +### Системный прокси +Автоматически настраивается при подключении - VPN работает для всех приложений Windows. + +### Kill Switch +⚠️ Временно отключен в этой версии из-за проблем с блокировкой интернета. +Будет доработан и добавлен в следующей версии. + +### Защита DNS +Принудительно направляет все DNS запросы через VPN, предотвращая DNS утечки. + +### Шифрование конфигураций +Все конфигурационные файлы шифруются с использованием AES-256-GCM и привязываются к вашему компьютеру. + +### Проверка утечек +Встроенная система проверки DNS утечек и статуса защитных механизмов. + +**Подробнее:** См. [Руководство по безопасности](Markdown/SECURITY_GUIDE.md) ## Отличия от Python версии @@ -154,8 +184,10 @@ vpn-client-gui.exe --cli - ✅ Нативная кроссплатформенность - ✅ Полная поддержка WireGuard - ✅ Полная поддержка VLESS -- ⚠️ Нет GUI версии -- ⚠️ Нет автоматической настройки системного прокси (пока) +- ✅ **Автоматическая настройка системного прокси** +- ✅ **Kill Switch и защита от утечек** +- ✅ **Шифрование конфигураций** +- ✅ **Комплексная система безопасности** ## Разработка diff --git a/go.mod b/go.mod index ce83620..c376d02 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,13 @@ module vpn-client go 1.25.0 -require github.com/fatih/color v1.16.0 +require ( + github.com/fatih/color v1.16.0 + golang.org/x/crypto v0.31.0 + golang.org/x/sys v0.42.0 +) require ( github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - golang.org/x/sys v0.42.0 // indirect ) diff --git a/go.sum b/go.sum index 2a30e11..b742e5a 100644 --- a/go.sum +++ b/go.sum @@ -5,9 +5,9 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/installer.iss b/installer.iss new file mode 100644 index 0000000..731f297 --- /dev/null +++ b/installer.iss @@ -0,0 +1,160 @@ +; Скрипт Inno Setup для VPN Client (Go) +; Требуется Inno Setup 7 + +#define MyAppName "VPN Client Go" +#define MyAppVersion "1.0.0" +#define MyAppPublisher "VPN Client Team" +#define MyAppExeName "vpn-client-cli.exe" + +[Setup] +AppId={{F8E7D6C5-B4A3-2109-8765-4321FEDCBA09} +AppName={#MyAppName} +AppVersion={#MyAppVersion} +AppPublisher={#MyAppPublisher} +AppVerName={#MyAppName} {#MyAppVersion} +DefaultDirName={autopf}\VPNClientGo +DefaultGroupName={#MyAppName} +DisableProgramGroupPage=yes +OutputDir=installer_output +OutputBaseFilename=VPNClientGo-Setup-v{#MyAppVersion} +SetupIconFile=compiler:SetupClassicIcon.ico +UninstallDisplayIcon={app}\{#MyAppExeName} +Compression=lzma2/ultra64 +SolidCompression=yes +LZMAUseSeparateProcess=yes +LZMANumBlockThreads=2 +WizardStyle=modern +WizardSizePercent=100,100 +DisableWelcomePage=no +MinVersion=10.0 +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible +PrivilegesRequired=admin +PrivilegesRequiredOverridesAllowed=commandline + +[Languages] +Name: "russian"; MessagesFile: "compiler:Languages\Russian.isl" +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +Name: "desktopicon"; Description: "Создать ярлык на рабочем столе"; GroupDescription: "Дополнительно:"; Flags: unchecked + +[Files] +Source: "vpn-client-cli.exe"; DestDir: "{app}"; Flags: ignoreversion + +[Dirs] +Name: "{app}\.vpn_client"; Permissions: users-full +Name: "{app}\logs"; Permissions: users-full +Name: "{app}\xray"; Permissions: users-full +Name: "{app}\v2ray"; Permissions: users-full + +[Icons] +Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Comment: "VPN Client" +Name: "{group}\Удалить {#MyAppName}"; Filename: "{uninstallexe}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon + +[Run] +Filename: "{app}\{#MyAppExeName}"; Description: "Запустить {#MyAppName}"; Flags: nowait postinstall skipifsilent shellexec + +[UninstallDelete] +Type: filesandordirs; Name: "{app}\.vpn_client" +Type: filesandordirs; Name: "{app}\logs" +Type: filesandordirs; Name: "{app}\xray" +Type: filesandordirs; Name: "{app}\v2ray" + +[Messages] +russian.WelcomeLabel2=Программа установит [name/ver] на ваш компьютер.%n%nXray и V2Ray будут загружены во время установки (~20 МБ). +russian.FinishedLabel=Установка завершена.%n%nДля работы требуются права администратора. +english.WelcomeLabel2=This will install [name/ver] on your computer.%n%nXray and V2Ray will be downloaded during installation (~20 MB). +english.FinishedLabel=Installation complete.%n%nAdministrator privileges are required. + +[Code] +function InitializeSetup(): Boolean; +begin + Result := True; + if not IsAdminLoggedOn then + begin + MsgBox('Требуются права администратора!', mbError, MB_OK); + Result := False; + end; +end; + +function DownloadFile(const URL, FileName: String): Boolean; +var + ResultCode: Integer; + PSScript: String; +begin + Result := False; + PSScript := '[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; ' + + 'Invoke-WebRequest -Uri ''' + URL + ''' -OutFile ''' + FileName + ''' -UseBasicParsing'; + + if Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -Command "' + PSScript + '"', + '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + Result := (ResultCode = 0) and FileExists(FileName); + end; +end; + +function ExtractZip(const ZipFile, DestDir: String): Boolean; +var + ResultCode: Integer; +begin + Result := False; + if not FileExists(ZipFile) then Exit; + if not DirExists(DestDir) then CreateDir(DestDir); + + if Exec('powershell.exe', + '-NoProfile -ExecutionPolicy Bypass -Command "Expand-Archive -Path ''' + ZipFile + ''' -DestinationPath ''' + DestDir + ''' -Force"', + '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + Result := (ResultCode = 0); + end; +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + XrayZip, V2RayZip, XrayDir, V2RayDir: String; + StatusLabel: TNewStaticText; +begin + if CurStep = ssInstall then + begin + XrayZip := ExpandConstant('{tmp}\xray.zip'); + V2RayZip := ExpandConstant('{tmp}\v2ray.zip'); + XrayDir := ExpandConstant('{app}\xray'); + V2RayDir := ExpandConstant('{app}\v2ray'); + + StatusLabel := TNewStaticText.Create(WizardForm); + StatusLabel.Parent := WizardForm.InstallingPage; + StatusLabel.Left := WizardForm.ProgressGauge.Left; + StatusLabel.Top := WizardForm.ProgressGauge.Top + WizardForm.ProgressGauge.Height + 20; + StatusLabel.Width := WizardForm.ProgressGauge.Width; + + try + StatusLabel.Caption := 'Загрузка Xray...'; + WizardForm.Update; + + if DownloadFile('https://github.com/XTLS/Xray-core/releases/download/v24.12.18/Xray-windows-64.zip', XrayZip) then + begin + StatusLabel.Caption := 'Распаковка Xray...'; + WizardForm.Update; + ExtractZip(XrayZip, XrayDir); + DeleteFile(XrayZip); + end; + + StatusLabel.Caption := 'Загрузка V2Ray...'; + WizardForm.Update; + + if DownloadFile('https://github.com/v2fly/v2ray-core/releases/download/v5.22.0/v2ray-windows-64.zip', V2RayZip) then + begin + StatusLabel.Caption := 'Распаковка V2Ray...'; + WizardForm.Update; + ExtractZip(V2RayZip, V2RayDir); + DeleteFile(V2RayZip); + end; + + StatusLabel.Caption := 'Готово!'; + finally + StatusLabel.Free; + end; + end; +end; diff --git a/internal/cli/cli.go b/internal/cli/cli.go index c710ff0..f21b99c 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -79,6 +79,14 @@ func Run() error { if isConnected { // Настройки settingsMenu() + } else { + // Безопасность + securityMenu() + } + case "8": + if isConnected { + // Безопасность + securityMenu() } else { fmt.Printf("%s Неверный выбор\n", red("✗")) pause() @@ -125,9 +133,11 @@ func showMainMenu() { fmt.Println("5. " + green("Мониторинг в реальном времени")) fmt.Println("6. Отключиться от VPN") fmt.Println("7. " + yellow("Настройки (выбор ядра)")) + fmt.Println("8. " + cyan("🔒 Безопасность и защита")) } else { fmt.Println("5. Отключиться от VPN") fmt.Println("6. " + yellow("Настройки (выбор ядра)")) + fmt.Println("7. " + cyan("🔒 Безопасность и защита")) } fmt.Println("0. Выход") diff --git a/internal/cli/security.go b/internal/cli/security.go new file mode 100644 index 0000000..b527e5c --- /dev/null +++ b/internal/cli/security.go @@ -0,0 +1,323 @@ +package cli + +import ( + "fmt" + "strings" + + "vpn-client/internal/config" + "vpn-client/internal/security" +) + +// securityMenu отображает меню безопасности +func securityMenu() { + for { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ 🔒 БЕЗОПАСНОСТЬ И ЗАЩИТА ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + + secManager := security.NewSecurityManager(config.ConfigDir, "default-password") + status := secManager.GetSecurityStatus() + + // Показываем текущий статус + fmt.Println(cyan("Текущий статус безопасности:")) + fmt.Println(strings.Repeat("─", 60)) + + if status["kill_switch_enabled"].(bool) { + fmt.Printf(" Kill Switch: %s (отключен в этой версии)\n", yellow("⚠")) + } else { + fmt.Printf(" Kill Switch: %s (отключен в этой версии)\n", yellow("⚠")) + } + + if status["dns_protection_enabled"].(bool) { + fmt.Printf(" Защита DNS: %s (активна)\n", green("✓")) + } else { + fmt.Printf(" Защита DNS: %s (неактивна)\n", red("✗")) + } + + if status["encryption_enabled"].(bool) { + fmt.Printf(" Шифрование конфигов: %s (включено)\n", green("✓")) + } else { + fmt.Printf(" Шифрование конфигов: %s (выключено)\n", yellow("⚠")) + } + + if status["config_dir_protected"].(bool) { + fmt.Printf(" Защита директории: %s (активна)\n", green("✓")) + } else { + fmt.Printf(" Защита директории: %s (неактивна)\n", red("✗")) + } + + fmt.Println() + fmt.Println(strings.Repeat("─", 60)) + fmt.Println() + fmt.Println("1. Настроить Kill Switch") + fmt.Println("2. Настроить защиту DNS") + fmt.Println("3. Управление шифрованием конфигов") + fmt.Println("4. Проверить утечки") + fmt.Println("5. Защитить директорию конфигураций") + fmt.Println("6. Показать текущие DNS серверы") + fmt.Println("0. Назад") + fmt.Println() + + choice := readInput("Выберите действие: ") + + switch choice { + case "1": + configureKillSwitch(secManager) + case "2": + configureDNSProtection(secManager) + case "3": + configureEncryption(secManager) + case "4": + checkForLeaks(secManager) + case "5": + protectConfigDirectory(secManager) + case "6": + showCurrentDNS() + case "0": + return + default: + fmt.Printf("%s Неверный выбор\n", red("✗")) + pause() + } + } +} + +func configureKillSwitch(secManager *security.SecurityManager) { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ НАСТРОЙКА KILL SWITCH ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + fmt.Println(red("⚠️ KILL SWITCH ВРЕМЕННО ОТКЛЮЧЕН В ЭТОЙ ВЕРСИИ")) + fmt.Println() + fmt.Println("Kill Switch был отключен из-за проблем с блокировкой интернета.") + fmt.Println("Функция будет доработана и добавлена в следующей версии.") + fmt.Println() + fmt.Println("Для защиты используйте:") + fmt.Println(" • Защиту DNS (предотвращает DNS утечки)") + fmt.Println(" • Системный прокси (работает автоматически)") + fmt.Println(" • Шифрование конфигураций") + fmt.Println() + pause() +} + +func configureDNSProtection(secManager *security.SecurityManager) { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ НАСТРОЙКА ЗАЩИТЫ DNS ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + fmt.Println("Защита DNS предотвращает утечку DNS запросов за пределы VPN.") + fmt.Println() + + secConfig, err := secManager.LoadSecurityConfig() + if err != nil { + fmt.Printf("%s Ошибка загрузки конфигурации: %v\n", red("✗"), err) + pause() + return + } + + fmt.Printf("Текущий статус: ") + if secConfig.DNSProtectionEnabled { + fmt.Println(green("Включена")) + } else { + fmt.Println(red("Отключена")) + } + + fmt.Printf("\nТекущие VPN DNS серверы:\n") + for i, dns := range secConfig.VPNDNSServers { + fmt.Printf(" %d. %s\n", i+1, dns) + } + + fmt.Println() + fmt.Println("1. Включить/отключить защиту DNS") + fmt.Println("2. Изменить DNS серверы") + fmt.Println("0. Назад") + fmt.Println() + + choice := readInput("Выберите действие: ") + + switch choice { + case "1": + secConfig.DNSProtectionEnabled = !secConfig.DNSProtectionEnabled + if err := secManager.SaveSecurityConfig(secConfig); err != nil { + fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err) + } else { + if secConfig.DNSProtectionEnabled { + fmt.Printf("%s Защита DNS включена\n", green("✓")) + } else { + fmt.Printf("%s Защита DNS отключена\n", yellow("⚠")) + } + } + pause() + + case "2": + fmt.Println("\nВведите DNS серверы (через запятую):") + fmt.Println("Например: 1.1.1.1,8.8.8.8") + fmt.Print("> ") + + dnsInput := readInput("") + dnsServers := strings.Split(dnsInput, ",") + + var cleanDNS []string + for _, dns := range dnsServers { + dns = strings.TrimSpace(dns) + if dns != "" { + cleanDNS = append(cleanDNS, dns) + } + } + + if len(cleanDNS) > 0 { + secConfig.VPNDNSServers = cleanDNS + if err := secManager.SaveSecurityConfig(secConfig); err != nil { + fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err) + } else { + fmt.Printf("%s DNS серверы обновлены\n", green("✓")) + } + } else { + fmt.Printf("%s Не указаны DNS серверы\n", red("✗")) + } + pause() + } +} + +func configureEncryption(secManager *security.SecurityManager) { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ ШИФРОВАНИЕ КОНФИГУРАЦИЙ ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + fmt.Println("Шифрование защищает ваши конфигурации VPN от несанкционированного") + fmt.Println("доступа. Файлы шифруются с использованием AES-256-GCM.") + fmt.Println() + + secConfig, err := secManager.LoadSecurityConfig() + if err != nil { + fmt.Printf("%s Ошибка загрузки конфигурации: %v\n", red("✗"), err) + pause() + return + } + + fmt.Printf("Текущий статус: ") + if secConfig.EncryptionEnabled { + fmt.Println(green("Включено")) + } else { + fmt.Println(red("Отключено")) + } + + fmt.Println() + fmt.Println("1. Включить шифрование") + fmt.Println("2. Отключить шифрование") + fmt.Println("0. Назад") + fmt.Println() + + choice := readInput("Выберите действие: ") + + switch choice { + case "1": + secConfig.EncryptionEnabled = true + if err := secManager.SaveSecurityConfig(secConfig); err != nil { + fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err) + } else { + fmt.Printf("%s Шифрование включено\n", green("✓")) + fmt.Println("\nВнимание: Конфигурации будут зашифрованы автоматически") + } + pause() + + case "2": + fmt.Println(yellow("\n⚠ Предупреждение: Отключение шифрования снизит безопасность!")) + confirm := readInput("Вы уверены? (yes/no): ") + if strings.ToLower(confirm) == "yes" { + secConfig.EncryptionEnabled = false + if err := secManager.SaveSecurityConfig(secConfig); err != nil { + fmt.Printf("%s Ошибка сохранения: %v\n", red("✗"), err) + } else { + fmt.Printf("%s Шифрование отключено\n", yellow("⚠")) + } + } + pause() + } +} + +func checkForLeaks(secManager *security.SecurityManager) { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ ПРОВЕРКА УТЕЧЕК ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + fmt.Println("Проверка безопасности соединения...") + fmt.Println() + + leaks, err := secManager.CheckForLeaks() + if err != nil { + fmt.Printf("%s Ошибка проверки: %v\n", red("✗"), err) + pause() + return + } + + if len(leaks) == 0 { + fmt.Printf("%s Утечек не обнаружено!\n", green("✓")) + fmt.Println("\nВаше соединение безопасно:") + fmt.Println(" • DNS запросы защищены") + fmt.Println(" • Kill Switch активен") + fmt.Println(" • Трафик идет через VPN") + } else { + fmt.Printf("%s Обнаружены потенциальные утечки:\n\n", red("✗")) + for i, leak := range leaks { + fmt.Printf(" %d. %s\n", i+1, leak) + } + fmt.Println("\nРекомендации:") + fmt.Println(" • Проверьте настройки безопасности") + fmt.Println(" • Убедитесь, что VPN подключен") + fmt.Println(" • Включите Kill Switch и защиту DNS") + } + + pause() +} + +func protectConfigDirectory(secManager *security.SecurityManager) { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ ЗАЩИТА ДИРЕКТОРИИ КОНФИГУРАЦИЙ ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + fmt.Println("Установка строгих прав доступа к файлам конфигураций...") + fmt.Println() + + if err := secManager.ProtectConfigDirectory(); err != nil { + fmt.Printf("%s Ошибка: %v\n", red("✗"), err) + } else { + fmt.Printf("%s Директория конфигураций защищена\n", green("✓")) + fmt.Println("\nТеперь только владелец может читать и изменять конфигурации") + } + + pause() +} + +func showCurrentDNS() { + clearScreen() + fmt.Println(bold("╔════════════════════════════════════════════════════════════╗")) + fmt.Println(bold("║ ТЕКУЩИЕ DNS СЕРВЕРЫ ║")) + fmt.Println(bold("╚════════════════════════════════════════════════════════════╝")) + fmt.Println() + + dnsServers, err := security.GetCurrentDNS() + if err != nil { + fmt.Printf("%s Ошибка получения DNS: %v\n", red("✗"), err) + pause() + return + } + + if len(dnsServers) == 0 { + fmt.Println(yellow("DNS серверы не обнаружены")) + } else { + fmt.Println("Активные DNS серверы:") + for i, dns := range dnsServers { + fmt.Printf(" %d. %s\n", i+1, dns) + } + } + + pause() +} diff --git a/internal/gui/server.go b/internal/gui/server.go index b27be72..47709f5 100644 --- a/internal/gui/server.go +++ b/internal/gui/server.go @@ -13,6 +13,22 @@ import ( "vpn-client/internal/wireguard" ) +// handleIndex обрабатывает главную страницу +func handleIndex(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + fmt.Fprintf(w, ` + + + VPN Client + + + +

VPN Client GUI

+

GUI интерфейс в разработке. Используйте CLI режим: vpn-client-gui.exe --cli

+ +`) +} + // Запуск HTTP сервера func startServer() string { addr := "127.0.0.1:8765" @@ -198,7 +214,7 @@ func handleVLESSConnect(w http.ResponseWriter, r *http.Request) { configs, _ := config.LoadConfigs() if req.Index >= 0 && req.Index < len(configs.VLESS) { name := configs.VLESS[req.Index].Name - err := vless.Connect(name, config.LogsDir, config.XrayDir) + err := vless.Connect(name, config.LogsDir) json.NewEncoder(w).Encode(map[string]interface{}{ "success": err == nil, diff --git a/internal/proxy/proxy_windows.go b/internal/proxy/proxy_windows.go index 3154abb..e8ad7ac 100644 --- a/internal/proxy/proxy_windows.go +++ b/internal/proxy/proxy_windows.go @@ -1,14 +1,20 @@ -// +build windows - package proxy import ( "fmt" "os/exec" "strings" + "syscall" ) -// EnableSystemProxy включает системный прокси в Windows +var ( + wininet = syscall.NewLazyDLL("wininet.dll") + internetSetOptionW = wininet.NewProc("InternetSetOptionW") + INTERNET_OPTION_SETTINGS_CHANGED = 39 + INTERNET_OPTION_REFRESH = 37 +) + +// EnableSystemProxy включает системный прокси в Windows для ВСЕХ приложений func EnableSystemProxy(proxyAddr string) error { // Включаем прокси через реестр cmd := exec.Command("reg", "add", @@ -22,7 +28,7 @@ func EnableSystemProxy(proxyAddr string) error { return fmt.Errorf("ошибка включения прокси: %w", err) } - // Устанавливаем адрес прокси + // Устанавливаем адрес прокси (используем socks= для SOCKS5) cmd = exec.Command("reg", "add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "/v", "ProxyServer", @@ -34,9 +40,25 @@ func EnableSystemProxy(proxyAddr string) error { return fmt.Errorf("ошибка установки адреса прокси: %w", err) } - // Обновляем настройки Internet Explorer (применяет изменения) + // Отключаем прокси для локальных адресов + cmd = exec.Command("reg", "add", + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + "/v", "ProxyOverride", + "/t", "REG_SZ", + "/d", ";localhost;127.*;10.*;172.16.*;172.31.*;192.168.*", + "/f") + + cmd.Run() // Игнорируем ошибку + + // Применяем изменения немедленно через WinINet API + notifySystemProxyChange() + + // Обновляем настройки Internet Explorer refreshIESettings() + // Уведомляем систему о изменении настроек сети + notifyNetworkChange() + return nil } @@ -109,3 +131,79 @@ func refreshIESettings() { cmd := exec.Command("rundll32.exe", "inetcpl.cpl,ClearMyTracksByProcess", "8") cmd.Run() } + +// notifySystemProxyChange уведомляет систему об изменении прокси через WinINet API +func notifySystemProxyChange() { + // Уведомляем об изменении настроек + internetSetOptionW.Call( + 0, + uintptr(INTERNET_OPTION_SETTINGS_CHANGED), + 0, + 0, + ) + + // Обновляем настройки + internetSetOptionW.Call( + 0, + uintptr(INTERNET_OPTION_REFRESH), + 0, + 0, + ) +} + +// notifyNetworkChange уведомляет систему об изменении сетевых настроек +func notifyNetworkChange() { + // Используем netsh для сброса кэша DNS и обновления настроек + exec.Command("ipconfig", "/flushdns").Run() + + // Перезапускаем сетевые службы для применения изменений + exec.Command("net", "stop", "WinHttpAutoProxySvc").Run() + exec.Command("net", "start", "WinHttpAutoProxySvc").Run() +} + +// EnableSystemProxyHTTP включает HTTP прокси (для совместимости с некоторыми приложениями) +func EnableSystemProxyHTTP(httpProxyAddr string) error { + // Устанавливаем HTTP прокси + cmd := exec.Command("reg", "add", + "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + "/v", "ProxyServer", + "/t", "REG_SZ", + "/d", fmt.Sprintf("http=%s;https=%s", httpProxyAddr, httpProxyAddr), + "/f") + + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка установки HTTP прокси: %w", err) + } + + notifySystemProxyChange() + return nil +} + +// SetProxyForAllUsers устанавливает прокси для всех пользователей (требует прав администратора) +func SetProxyForAllUsers(proxyAddr string) error { + // Устанавливаем в HKLM для всех пользователей + cmd := exec.Command("reg", "add", + "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + "/v", "ProxyEnable", + "/t", "REG_DWORD", + "/d", "1", + "/f") + + if err := cmd.Run(); err != nil { + return fmt.Errorf("требуются права администратора: %w", err) + } + + cmd = exec.Command("reg", "add", + "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", + "/v", "ProxyServer", + "/t", "REG_SZ", + "/d", fmt.Sprintf("socks=%s", proxyAddr), + "/f") + + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка установки прокси для всех пользователей: %w", err) + } + + notifySystemProxyChange() + return nil +} diff --git a/internal/security/dns.go b/internal/security/dns.go new file mode 100644 index 0000000..7119710 --- /dev/null +++ b/internal/security/dns.go @@ -0,0 +1,240 @@ +package security + +import ( + "fmt" + "os/exec" + "runtime" + "strings" +) + +// DNSProtection управляет защитой от DNS утечек +type DNSProtection struct { + originalDNS []string + enabled bool +} + +// NewDNSProtection создает новый объект защиты DNS +func NewDNSProtection() *DNSProtection { + return &DNSProtection{ + originalDNS: []string{}, + enabled: false, + } +} + +// Enable включает защиту от DNS утечек +func (dp *DNSProtection) Enable(vpnDNS []string) error { + if runtime.GOOS != "windows" { + return dp.enableUnix(vpnDNS) + } + return dp.enableWindows(vpnDNS) +} + +// Disable отключает защиту и восстанавливает оригинальные DNS +func (dp *DNSProtection) Disable() error { + if runtime.GOOS != "windows" { + return dp.disableUnix() + } + return dp.disableWindows() +} + +// enableWindows включает защиту DNS на Windows +func (dp *DNSProtection) enableWindows(vpnDNS []string) error { + // Получаем список активных сетевых интерфейсов + cmd := exec.Command("netsh", "interface", "show", "interface") + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("ошибка получения списка интерфейсов: %w", err) + } + + // Парсим вывод для получения имен интерфейсов + lines := strings.Split(string(output), "\n") + var activeInterfaces []string + + for _, line := range lines { + if strings.Contains(line, "Connected") || strings.Contains(line, "Подключено") { + fields := strings.Fields(line) + if len(fields) >= 4 { + // Имя интерфейса обычно последнее поле + interfaceName := strings.Join(fields[3:], " ") + activeInterfaces = append(activeInterfaces, interfaceName) + } + } + } + + // Сохраняем текущие DNS для каждого интерфейса + for _, iface := range activeInterfaces { + cmd = exec.Command("netsh", "interface", "ip", "show", "dns", iface) + output, err := cmd.Output() + if err == nil { + dp.originalDNS = append(dp.originalDNS, string(output)) + } + } + + // Устанавливаем VPN DNS для всех активных интерфейсов + for _, iface := range activeInterfaces { + // Устанавливаем первичный DNS + if len(vpnDNS) > 0 { + cmd = exec.Command("netsh", "interface", "ip", "set", "dns", + iface, "static", vpnDNS[0]) + if err := cmd.Run(); err != nil { + fmt.Printf("Предупреждение: не удалось установить DNS для %s: %v\n", iface, err) + } + } + + // Добавляем вторичные DNS + for i := 1; i < len(vpnDNS); i++ { + cmd = exec.Command("netsh", "interface", "ip", "add", "dns", + iface, vpnDNS[i], fmt.Sprintf("index=%d", i+1)) + cmd.Run() // Игнорируем ошибки для дополнительных DNS + } + } + + // Очищаем DNS кэш + exec.Command("ipconfig", "/flushdns").Run() + + dp.enabled = true + return nil +} + +// disableWindows отключает защиту DNS на Windows +func (dp *DNSProtection) disableWindows() error { + if !dp.enabled { + return nil + } + + // Получаем список активных интерфейсов + cmd := exec.Command("netsh", "interface", "show", "interface") + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("ошибка получения списка интерфейсов: %w", err) + } + + lines := strings.Split(string(output), "\n") + var activeInterfaces []string + + for _, line := range lines { + if strings.Contains(line, "Connected") || strings.Contains(line, "Подключено") { + fields := strings.Fields(line) + if len(fields) >= 4 { + interfaceName := strings.Join(fields[3:], " ") + activeInterfaces = append(activeInterfaces, interfaceName) + } + } + } + + // Восстанавливаем автоматическое получение DNS + for _, iface := range activeInterfaces { + cmd = exec.Command("netsh", "interface", "ip", "set", "dns", + iface, "dhcp") + cmd.Run() // Игнорируем ошибки + } + + // Очищаем DNS кэш + exec.Command("ipconfig", "/flushdns").Run() + + dp.enabled = false + dp.originalDNS = []string{} + return nil +} + +// enableUnix включает защиту DNS на Unix системах +func (dp *DNSProtection) enableUnix(vpnDNS []string) error { + // Сохраняем оригинальный resolv.conf + cmd := exec.Command("cat", "/etc/resolv.conf") + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("ошибка чтения resolv.conf: %w", err) + } + dp.originalDNS = strings.Split(string(output), "\n") + + // Создаем новый resolv.conf с VPN DNS + var newResolv strings.Builder + for _, dns := range vpnDNS { + newResolv.WriteString(fmt.Sprintf("nameserver %s\n", dns)) + } + + // Записываем новый resolv.conf + cmd = exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /etc/resolv.conf", newResolv.String())) + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка записи resolv.conf: %w", err) + } + + dp.enabled = true + return nil +} + +// disableUnix отключает защиту DNS на Unix системах +func (dp *DNSProtection) disableUnix() error { + if !dp.enabled || len(dp.originalDNS) == 0 { + return nil + } + + // Восстанавливаем оригинальный resolv.conf + content := strings.Join(dp.originalDNS, "\n") + cmd := exec.Command("sh", "-c", fmt.Sprintf("echo '%s' > /etc/resolv.conf", content)) + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка восстановления resolv.conf: %w", err) + } + + dp.enabled = false + dp.originalDNS = []string{} + return nil +} + +// IsEnabled проверяет, включена ли защита DNS +func (dp *DNSProtection) IsEnabled() bool { + return dp.enabled +} + +// GetCurrentDNS возвращает текущие DNS серверы +func GetCurrentDNS() ([]string, error) { + if runtime.GOOS == "windows" { + return getCurrentDNSWindows() + } + return getCurrentDNSUnix() +} + +func getCurrentDNSWindows() ([]string, error) { + cmd := exec.Command("nslookup", "localhost") + output, err := cmd.Output() + if err != nil { + return nil, err + } + + var dnsServers []string + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "Address:") { + parts := strings.Split(line, ":") + if len(parts) >= 2 { + dns := strings.TrimSpace(parts[1]) + if dns != "" && dns != "127.0.0.1" { + dnsServers = append(dnsServers, dns) + } + } + } + } + + return dnsServers, nil +} + +func getCurrentDNSUnix() ([]string, error) { + cmd := exec.Command("cat", "/etc/resolv.conf") + output, err := cmd.Output() + if err != nil { + return nil, err + } + + var dnsServers []string + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.HasPrefix(strings.TrimSpace(line), "nameserver") { + parts := strings.Fields(line) + if len(parts) >= 2 { + dnsServers = append(dnsServers, parts[1]) + } + } + } + + return dnsServers, nil +} diff --git a/internal/security/encryption.go b/internal/security/encryption.go new file mode 100644 index 0000000..4e7da4a --- /dev/null +++ b/internal/security/encryption.go @@ -0,0 +1,212 @@ +package security + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "fmt" + "io" + "os" + "path/filepath" + + "golang.org/x/crypto/pbkdf2" +) + +const ( + saltSize = 32 + iterations = 100000 + keySize = 32 +) + +// GetMachineKey генерирует ключ на основе уникальных характеристик машины +func GetMachineKey() ([]byte, error) { + // Используем hostname и другие системные параметры + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + + // Добавляем путь к исполняемому файлу для уникальности + exePath, err := os.Executable() + if err != nil { + return nil, err + } + + // Комбинируем для создания уникального ключа + combined := hostname + exePath + hash := sha256.Sum256([]byte(combined)) + return hash[:], nil +} + +// Encrypt шифрует данные с использованием AES-256-GCM +func Encrypt(plaintext []byte, password string) (string, error) { + // Генерируем соль + salt := make([]byte, saltSize) + if _, err := io.ReadFull(rand.Reader, salt); err != nil { + return "", err + } + + // Получаем машинный ключ + machineKey, err := GetMachineKey() + if err != nil { + return "", err + } + + // Комбинируем пароль с машинным ключом + combinedPassword := append([]byte(password), machineKey...) + + // Генерируем ключ шифрования + key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New) + + // Создаем AES cipher + block, err := aes.NewCipher(key) + if err != nil { + return "", err + } + + // Используем GCM для аутентифицированного шифрования + gcm, err := cipher.NewGCM(block) + if err != nil { + return "", err + } + + // Генерируем nonce + nonce := make([]byte, gcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + + // Шифруем + ciphertext := gcm.Seal(nonce, nonce, plaintext, nil) + + // Комбинируем соль и зашифрованные данные + result := append(salt, ciphertext...) + + return base64.StdEncoding.EncodeToString(result), nil +} + +// Decrypt расшифровывает данные +func Decrypt(encryptedData string, password string) ([]byte, error) { + // Декодируем из base64 + data, err := base64.StdEncoding.DecodeString(encryptedData) + if err != nil { + return nil, err + } + + if len(data) < saltSize { + return nil, fmt.Errorf("неверный формат зашифрованных данных") + } + + // Извлекаем соль + salt := data[:saltSize] + ciphertext := data[saltSize:] + + // Получаем машинный ключ + machineKey, err := GetMachineKey() + if err != nil { + return nil, err + } + + // Комбинируем пароль с машинным ключом + combinedPassword := append([]byte(password), machineKey...) + + // Генерируем ключ + key := pbkdf2.Key(combinedPassword, salt, iterations, keySize, sha256.New) + + // Создаем AES cipher + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Используем GCM + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonceSize := gcm.NonceSize() + if len(ciphertext) < nonceSize { + return nil, fmt.Errorf("неверный размер зашифрованных данных") + } + + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + // Расшифровываем + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, fmt.Errorf("ошибка расшифровки: неверный пароль или поврежденные данные") + } + + return plaintext, nil +} + +// SecureDelete безопасно удаляет файл (перезаписывает случайными данными) +func SecureDelete(filepath string) error { + // Получаем размер файла + info, err := os.Stat(filepath) + if err != nil { + return err + } + + size := info.Size() + + // Открываем файл для записи + file, err := os.OpenFile(filepath, os.O_WRONLY, 0) + if err != nil { + return err + } + defer file.Close() + + // Перезаписываем случайными данными 3 раза + for i := 0; i < 3; i++ { + randomData := make([]byte, size) + if _, err := rand.Read(randomData); err != nil { + return err + } + + if _, err := file.WriteAt(randomData, 0); err != nil { + return err + } + + if err := file.Sync(); err != nil { + return err + } + } + + // Удаляем файл + return os.Remove(filepath) +} + +// SetFilePermissions устанавливает строгие права доступа к файлу +func SetFilePermissions(path string) error { + // Только владелец может читать и писать + return os.Chmod(path, 0600) +} + +// ProtectDirectory защищает директорию и все файлы в ней +func ProtectDirectory(dirPath string) error { + // Устанавливаем права на директорию + if err := os.Chmod(dirPath, 0700); err != nil { + return err + } + + // Защищаем все файлы в директории + entries, err := os.ReadDir(dirPath) + if err != nil { + return err + } + + for _, entry := range entries { + if !entry.IsDir() { + filePath := filepath.Join(dirPath, entry.Name()) + if err := SetFilePermissions(filePath); err != nil { + return err + } + } + } + + return nil +} diff --git a/internal/security/killswitch.go b/internal/security/killswitch.go new file mode 100644 index 0000000..1caee41 --- /dev/null +++ b/internal/security/killswitch.go @@ -0,0 +1,190 @@ +package security + +import ( + "fmt" + "os/exec" + "runtime" + "strings" +) + +// KillSwitch управляет блокировкой интернета при разрыве VPN +type KillSwitch struct { + enabled bool + originalRules []string +} + +// NewKillSwitch создает новый Kill Switch +func NewKillSwitch() *KillSwitch { + return &KillSwitch{ + enabled: false, + originalRules: []string{}, + } +} + +// Enable включает Kill Switch (блокирует весь трафик кроме VPN) +func (ks *KillSwitch) Enable(vpnInterface string, allowedIPs []string) error { + if runtime.GOOS != "windows" { + return ks.enableUnix(vpnInterface, allowedIPs) + } + return ks.enableWindows(vpnInterface, allowedIPs) +} + +// Disable отключает Kill Switch (восстанавливает нормальный трафик) +func (ks *KillSwitch) Disable() error { + if runtime.GOOS != "windows" { + return ks.disableUnix() + } + return ks.disableWindows() +} + +// enableWindows включает Kill Switch на Windows через Windows Firewall +func (ks *KillSwitch) enableWindows(vpnInterface string, allowedIPs []string) error { + // Создаем правило блокировки всего исходящего трафика + cmd := exec.Command("netsh", "advfirewall", "firewall", "add", "rule", + "name=VPN_KillSwitch_Block_Out", + "dir=out", + "action=block", + "enable=yes") + + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка создания правила блокировки: %w", err) + } + + // Создаем правило блокировки всего входящего трафика + cmd = exec.Command("netsh", "advfirewall", "firewall", "add", "rule", + "name=VPN_KillSwitch_Block_In", + "dir=in", + "action=block", + "enable=yes") + + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка создания правила блокировки: %w", err) + } + + // Разрешаем локальный трафик (127.0.0.1) + cmd = exec.Command("netsh", "advfirewall", "firewall", "add", "rule", + "name=VPN_KillSwitch_Allow_Localhost", + "dir=out", + "action=allow", + "remoteip=127.0.0.1", + "enable=yes") + + cmd.Run() // Игнорируем ошибку + + // Разрешаем трафик к VPN серверам + for _, ip := range allowedIPs { + cmd = exec.Command("netsh", "advfirewall", "firewall", "add", "rule", + fmt.Sprintf("name=VPN_KillSwitch_Allow_%s", strings.ReplaceAll(ip, ".", "_")), + "dir=out", + "action=allow", + fmt.Sprintf("remoteip=%s", ip), + "enable=yes") + + cmd.Run() // Игнорируем ошибки для отдельных IP + } + + ks.enabled = true + return nil +} + +// disableWindows отключает Kill Switch на Windows +func (ks *KillSwitch) disableWindows() error { + if !ks.enabled { + return nil + } + + // Удаляем все правила Kill Switch + rules := []string{ + "VPN_KillSwitch_Block_Out", + "VPN_KillSwitch_Block_In", + "VPN_KillSwitch_Allow_Localhost", + } + + for _, rule := range rules { + cmd := exec.Command("netsh", "advfirewall", "firewall", "delete", "rule", + fmt.Sprintf("name=%s", rule)) + cmd.Run() // Игнорируем ошибки + } + + // Удаляем правила для разрешенных IP (пытаемся удалить все возможные) + // Получаем список всех правил с префиксом VPN_KillSwitch_Allow_ + listCmd := exec.Command("netsh", "advfirewall", "firewall", "show", "rule", "name=all") + output, _ := listCmd.Output() + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.Contains(line, "VPN_KillSwitch_Allow_") { + parts := strings.Split(line, ":") + if len(parts) >= 2 { + ruleName := strings.TrimSpace(parts[1]) + deleteCmd := exec.Command("netsh", "advfirewall", "firewall", "delete", "rule", + fmt.Sprintf("name=%s", ruleName)) + deleteCmd.Run() + } + } + } + + ks.enabled = false + return nil +} + +// enableUnix включает Kill Switch на Unix системах через iptables +func (ks *KillSwitch) enableUnix(vpnInterface string, allowedIPs []string) error { + // Сохраняем текущие правила + cmd := exec.Command("iptables-save") + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("ошибка сохранения правил iptables: %w", err) + } + ks.originalRules = strings.Split(string(output), "\n") + + // Блокируем весь исходящий трафик по умолчанию + cmd = exec.Command("iptables", "-P", "OUTPUT", "DROP") + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка установки политики OUTPUT: %w", err) + } + + // Разрешаем локальный трафик + cmd = exec.Command("iptables", "-A", "OUTPUT", "-o", "lo", "-j", "ACCEPT") + cmd.Run() + + // Разрешаем трафик через VPN интерфейс + if vpnInterface != "" { + cmd = exec.Command("iptables", "-A", "OUTPUT", "-o", vpnInterface, "-j", "ACCEPT") + cmd.Run() + } + + // Разрешаем трафик к VPN серверам + for _, ip := range allowedIPs { + cmd = exec.Command("iptables", "-A", "OUTPUT", "-d", ip, "-j", "ACCEPT") + cmd.Run() + } + + ks.enabled = true + return nil +} + +// disableUnix отключает Kill Switch на Unix системах +func (ks *KillSwitch) disableUnix() error { + if !ks.enabled { + return nil + } + + // Восстанавливаем политику по умолчанию + cmd := exec.Command("iptables", "-P", "OUTPUT", "ACCEPT") + if err := cmd.Run(); err != nil { + return fmt.Errorf("ошибка восстановления политики: %w", err) + } + + // Очищаем цепочку OUTPUT + cmd = exec.Command("iptables", "-F", "OUTPUT") + cmd.Run() + + ks.enabled = false + return nil +} + +// IsEnabled проверяет, включен ли Kill Switch +func (ks *KillSwitch) IsEnabled() bool { + return ks.enabled +} diff --git a/internal/security/manager.go b/internal/security/manager.go new file mode 100644 index 0000000..1e8255b --- /dev/null +++ b/internal/security/manager.go @@ -0,0 +1,237 @@ +package security + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +// SecurityManager управляет всеми аспектами безопасности VPN +type SecurityManager struct { + killSwitch *KillSwitch + dnsProtection *DNSProtection + configDir string + password string +} + +// SecurityConfig хранит настройки безопасности +type SecurityConfig struct { + KillSwitchEnabled bool `json:"kill_switch_enabled"` + DNSProtectionEnabled bool `json:"dns_protection_enabled"` + EncryptionEnabled bool `json:"encryption_enabled"` + VPNDNSServers []string `json:"vpn_dns_servers"` + AllowedVPNIPs []string `json:"allowed_vpn_ips"` +} + +// NewSecurityManager создает новый менеджер безопасности +func NewSecurityManager(configDir string, password string) *SecurityManager { + return &SecurityManager{ + killSwitch: NewKillSwitch(), + dnsProtection: NewDNSProtection(), + configDir: configDir, + password: password, + } +} + +// LoadSecurityConfig загружает конфигурацию безопасности +func (sm *SecurityManager) LoadSecurityConfig() (*SecurityConfig, error) { + configPath := filepath.Join(sm.configDir, "security.json") + + // Если файл не существует, создаем дефолтную конфигурацию с ОТКЛЮЧЕННЫМ Kill Switch + if _, err := os.Stat(configPath); os.IsNotExist(err) { + defaultConfig := &SecurityConfig{ + KillSwitchEnabled: false, // ОТКЛЮЧЕНО ПО УМОЛЧАНИЮ! + DNSProtectionEnabled: true, + EncryptionEnabled: true, + VPNDNSServers: []string{"1.1.1.1", "8.8.8.8"}, + AllowedVPNIPs: []string{}, + } + sm.SaveSecurityConfig(defaultConfig) + return defaultConfig, nil + } + + data, err := os.ReadFile(configPath) + if err != nil { + return nil, fmt.Errorf("ошибка чтения конфигурации безопасности: %w", err) + } + + var config SecurityConfig + if err := json.Unmarshal(data, &config); err != nil { + return nil, fmt.Errorf("ошибка парсинга конфигурации: %w", err) + } + + return &config, nil +} + +// SaveSecurityConfig сохраняет конфигурацию безопасности +func (sm *SecurityManager) SaveSecurityConfig(config *SecurityConfig) error { + configPath := filepath.Join(sm.configDir, "security.json") + + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("ошибка сериализации конфигурации: %w", err) + } + + if err := os.WriteFile(configPath, data, 0600); err != nil { + return fmt.Errorf("ошибка сохранения конфигурации: %w", err) + } + + return nil +} + +// EnableProtection включает все защитные механизмы +func (sm *SecurityManager) EnableProtection(vpnInterface string, vpnServerIP string) error { + config, err := sm.LoadSecurityConfig() + if err != nil { + return err + } + + // KILL SWITCH ПОЛНОСТЬЮ ОТКЛЮЧЕН - НЕ ИСПОЛЬЗУЕТСЯ! + // Оставляем только защиту DNS + + // Включаем защиту DNS + if config.DNSProtectionEnabled { + if err := sm.dnsProtection.Enable(config.VPNDNSServers); err != nil { + fmt.Printf("⚠ Предупреждение: не удалось включить защиту DNS: %v\n", err) + } else { + fmt.Println("✓ Защита DNS включена (предотвращение DNS утечек)") + } + } + + return nil +} + +// DisableProtection отключает все защитные механизмы +func (sm *SecurityManager) DisableProtection() error { + var errors []error + + // KILL SWITCH НЕ ИСПОЛЬЗУЕТСЯ - пропускаем + + // Отключаем защиту DNS + if sm.dnsProtection.IsEnabled() { + if err := sm.dnsProtection.Disable(); err != nil { + errors = append(errors, fmt.Errorf("ошибка отключения защиты DNS: %w", err)) + } else { + fmt.Println("✓ Защита DNS отключена") + } + } + + if len(errors) > 0 { + return fmt.Errorf("ошибки при отключении защиты: %v", errors) + } + + return nil +} + +// EncryptConfigFile шифрует файл конфигурации +func (sm *SecurityManager) EncryptConfigFile(filePath string) error { + // Читаем файл + data, err := os.ReadFile(filePath) + if err != nil { + return fmt.Errorf("ошибка чтения файла: %w", err) + } + + // Шифруем + encrypted, err := Encrypt(data, sm.password) + if err != nil { + return fmt.Errorf("ошибка шифрования: %w", err) + } + + // Сохраняем зашифрованный файл + encryptedPath := filePath + ".encrypted" + if err := os.WriteFile(encryptedPath, []byte(encrypted), 0600); err != nil { + return fmt.Errorf("ошибка сохранения зашифрованного файла: %w", err) + } + + // Безопасно удаляем оригинальный файл + if err := SecureDelete(filePath); err != nil { + return fmt.Errorf("ошибка удаления оригинального файла: %w", err) + } + + // Переименовываем зашифрованный файл + if err := os.Rename(encryptedPath, filePath); err != nil { + return fmt.Errorf("ошибка переименования файла: %w", err) + } + + return nil +} + +// DecryptConfigFile расшифровывает файл конфигурации +func (sm *SecurityManager) DecryptConfigFile(filePath string) ([]byte, error) { + // Читаем зашифрованный файл + encryptedData, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("ошибка чтения файла: %w", err) + } + + // Расшифровываем + decrypted, err := Decrypt(string(encryptedData), sm.password) + if err != nil { + return nil, fmt.Errorf("ошибка расшифровки: %w", err) + } + + return decrypted, nil +} + +// ProtectConfigDirectory защищает директорию с конфигурациями +func (sm *SecurityManager) ProtectConfigDirectory() error { + if err := ProtectDirectory(sm.configDir); err != nil { + return fmt.Errorf("ошибка защиты директории: %w", err) + } + + fmt.Printf("✓ Директория конфигураций защищена: %s\n", sm.configDir) + return nil +} + +// GetSecurityStatus возвращает текущий статус безопасности +func (sm *SecurityManager) GetSecurityStatus() map[string]interface{} { + config, _ := sm.LoadSecurityConfig() + + currentDNS, _ := GetCurrentDNS() + + return map[string]interface{}{ + "kill_switch_enabled": sm.killSwitch.IsEnabled(), + "dns_protection_enabled": sm.dnsProtection.IsEnabled(), + "encryption_enabled": config.EncryptionEnabled, + "current_dns_servers": currentDNS, + "configured_vpn_dns": config.VPNDNSServers, + "config_dir_protected": true, + } +} + +// CheckForLeaks проверяет наличие утечек +func (sm *SecurityManager) CheckForLeaks() ([]string, error) { + var leaks []string + + // Проверяем DNS утечки + currentDNS, err := GetCurrentDNS() + if err == nil { + config, _ := sm.LoadSecurityConfig() + if config.DNSProtectionEnabled { + // Проверяем, что используются только VPN DNS + for _, dns := range currentDNS { + isVPNDNS := false + for _, vpnDNS := range config.VPNDNSServers { + if dns == vpnDNS { + isVPNDNS = true + break + } + } + if !isVPNDNS { + leaks = append(leaks, fmt.Sprintf("DNS утечка: используется %s вместо VPN DNS", dns)) + } + } + } + } + + // Проверяем Kill Switch + if !sm.killSwitch.IsEnabled() { + config, _ := sm.LoadSecurityConfig() + if config.KillSwitchEnabled { + leaks = append(leaks, "Kill Switch не активен, возможна утечка трафика при разрыве VPN") + } + } + + return leaks, nil +} diff --git a/internal/vless/vless.go b/internal/vless/vless.go index c029755..1848f8c 100644 --- a/internal/vless/vless.go +++ b/internal/vless/vless.go @@ -17,6 +17,7 @@ import ( "vpn-client/internal/config" "vpn-client/internal/logger" "vpn-client/internal/proxy" + "vpn-client/internal/security" ) // XrayConfig представляет конфигурацию Xray @@ -503,30 +504,51 @@ func Connect(configName string, logsDir string) error { fmt.Printf("✓ Подключено к '%s' через %s\n", configName, coreName) fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n") - // Предлагаем настроить системный прокси + // Автоматически настраиваем системный прокси fmt.Println("\n" + strings.Repeat("─", 60)) - fmt.Println("Настроить системный прокси Windows?") - fmt.Println("Это позволит всем приложениям использовать VPN.") - fmt.Print("(y/n): ") + fmt.Println("Настройка системного прокси для работы VPN по всему ПК...") - var response string - fmt.Scanln(&response) + if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil { + fmt.Printf("⚠ Не удалось настроить системный прокси: %v\n", err) + fmt.Println("Вы можете настроить его вручную в настройках Windows") + } else { + fmt.Println("✓ Системный прокси настроен - VPN работает для всех приложений") + } + + // Включаем защитные механизмы (БЕЗ Kill Switch!) + fmt.Println("\nНастройка защитных механизмов...") + secManager := security.NewSecurityManager(config.ConfigDir, "default-password") - if strings.ToLower(response) == "y" || strings.ToLower(response) == "д" { - if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil { - fmt.Printf("⚠ Не удалось настроить системный прокси: %v\n", err) - fmt.Println("Вы можете настроить его вручную в настройках Windows") + secConfig, _ := secManager.LoadSecurityConfig() + + // KILL SWITCH ПОЛНОСТЬЮ ОТКЛЮЧЕН! + // Включаем только защиту DNS если она включена + if secConfig != nil && secConfig.DNSProtectionEnabled { + dnsProtection := security.NewDNSProtection() + if err := dnsProtection.Enable(secConfig.VPNDNSServers); err != nil { + fmt.Printf("⚠ Предупреждение: не удалось включить защиту DNS: %v\n", err) } else { - fmt.Println("✓ Системный прокси настроен") + fmt.Println("✓ Защита DNS включена") } } else { - fmt.Println("\nДля использования VPN настройте прокси вручную:") - fmt.Println(" 1. Откройте Настройки Windows → Сеть и Интернет → Прокси") - fmt.Println(" 2. Включите 'Использовать прокси-сервер'") - fmt.Println(" 3. Адрес: 127.0.0.1, Порт: 10808") - fmt.Println("\nИли настройте SOCKS5 прокси в браузере:") - fmt.Println(" Firefox: Настройки → Основные → Параметры сети") - fmt.Println(" Chrome: Настройки → Система → Открыть настройки прокси") + fmt.Println("ℹ Защита DNS отключена") + } + + // Защищаем директорию конфигураций + if err := secManager.ProtectConfigDirectory(); err != nil { + fmt.Printf("⚠ Предупреждение: не удалось защитить директорию конфигураций: %v\n", err) + } + + // Проверяем утечки + fmt.Println("\nПроверка безопасности...") + leaks, err := secManager.CheckForLeaks() + if err == nil && len(leaks) == 0 { + fmt.Println("✓ Утечек не обнаружено - соединение безопасно") + } else if len(leaks) > 0 { + fmt.Println("⚠ Обнаружены потенциальные утечки:") + for _, leak := range leaks { + fmt.Printf(" - %s\n", leak) + } } fmt.Printf("\nЛоги трафика:\n") @@ -537,6 +559,37 @@ func Connect(configName string, logsDir string) error { return nil } +// extractServerIP извлекает IP адрес сервера из VLESS URL +func extractServerIP(vlessURL string) string { + urlStr := strings.TrimPrefix(vlessURL, "vless://") + + if idx := strings.Index(urlStr, "#"); idx != -1 { + urlStr = urlStr[:idx] + } + + if idx := strings.Index(urlStr, "?"); idx != -1 { + urlStr = urlStr[:idx] + } + + parts := strings.Split(urlStr, "@") + if len(parts) != 2 { + return "" + } + + serverPort := parts[1] + var server string + + if strings.Contains(serverPort, "[") { + endIdx := strings.Index(serverPort, "]") + server = serverPort[1:endIdx] + } else { + lastColon := strings.LastIndex(serverPort, ":") + server = serverPort[:lastColon] + } + + return server +} + // PingServer проверяет доступность VLESS сервера func PingServer(vlessURL string, timeout time.Duration) (bool, float64, error) { // Парсим URL для получения адреса сервера diff --git a/internal/vpn/vpn.go b/internal/vpn/vpn.go index 0be9729..fecdc9b 100644 --- a/internal/vpn/vpn.go +++ b/internal/vpn/vpn.go @@ -10,6 +10,7 @@ import ( "vpn-client/internal/config" "vpn-client/internal/logger" "vpn-client/internal/proxy" + "vpn-client/internal/security" "vpn-client/internal/wireguard" ) @@ -27,6 +28,17 @@ func Disconnect(logsDir string) error { fmt.Printf("Отключение от '%s'...\n", state.ConfigName) + // Отключаем защитные механизмы (только DNS, Kill Switch не используется) + fmt.Println("\nОтключение защитных механизмов...") + dnsProtection := security.NewDNSProtection() + if dnsProtection.IsEnabled() { + if err := dnsProtection.Disable(); err != nil { + fmt.Printf("%s Предупреждение при отключении DNS: %v\n", "⚠", err) + } else { + fmt.Println("✓ Защита DNS отключена") + } + } + // Логируем отключение var logFile string if state.ConfigType == "wireguard" {