1 Commits
v1.2 ... main

Author SHA1 Message Date
b809e84220 feat: add security system with system-wide proxy, DNS protection and encryption
- System-wide proxy: automatic Windows proxy configuration for all apps
- DNS leak protection: force all DNS queries through VPN
- Config encryption: AES-256-GCM encryption for all config files
- File protection: strict access permissions for config directory
- Leak detection: built-in security check system
- Kill Switch: temporarily disabled (will be improved in next version)

Security features:
✓ Automatic system proxy setup
✓ DNS leak protection (optional)
✓ AES-256-GCM config encryption
✓ File and directory protection
✓ Security leak checker
⚠ Kill Switch disabled (caused internet blocking issues)

Emergency recovery scripts included:
- ОТКЛЮЧИТЬ_KILLSWITCH.bat
- EMERGENCY_FIX_INTERNET.bat
- ЕСЛИ_СЛОМАЛСЯ_ИНТЕРНЕТ.txt

Documentation:
- Markdown/SECURITY_GUIDE.md - full security guide
- БЕЗОПАСНОСТЬ_БЫСТРЫЙ_СТАРТ.md - quick start guide
- CHANGELOG_SECURITY.md - detailed changelog
2026-04-12 19:01:24 +06:00
18 changed files with 2063 additions and 31 deletions

4
.gitignore vendored
View File

@@ -7,6 +7,10 @@ vpn-client.exe
*.dylib *.dylib
*.bat *.bat
*.sh *.sh
init.bat
init.sh
build.bat
build.sh
# Test binary # Test binary
*.test *.test

136
INSTALL_GUIDE.md Normal file
View File

@@ -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
```
Теперь можно собрать всё одной командой!

21
LICENSE.txt Normal file
View File

@@ -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.

285
Markdown/SECURITY_GUIDE.md Normal file
View File

@@ -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_<IP>` - разрешение трафика к VPN серверу
### Изменения реестра
Системный прокси изменяет:
```
HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings
- ProxyEnable: 1
- ProxyServer: socks=127.0.0.1:10808
- ProxyOverride: <local>;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

View File

@@ -12,6 +12,12 @@ VPN клиент на Golang с поддержкой VLESS протокола и
- ✅ Детальное логирование - ✅ Детальное логирование
- ✅ Статистика трафика для WireGuard - ✅ Статистика трафика для WireGuard
- ✅ Кроссплатформенность (Windows, Linux, macOS) - ✅ Кроссплатформенность (Windows, Linux, macOS)
-**Системный прокси для всего ПК**
- ⚠️ **Kill Switch (временно отключен)**
-**Защита от DNS утечек**
-**Шифрование конфигураций (AES-256-GCM)**
-**Защита файлов и директорий**
-**Проверка утечек безопасности**
## Требования ## Требования
@@ -144,7 +150,31 @@ vpn-client-gui.exe --cli
127.0.0.1:10808 127.0.0.1:10808
``` ```
Настройте браузер или систему на использование этого прокси. **Системный прокси настраивается автоматически!** VPN будет работать для всех приложений Windows.
Для ручной настройки в браузере или других приложениях используйте указанный выше адрес.
## 🔒 Безопасность
Клиент включает комплексную систему безопасности:
### Системный прокси
Автоматически настраивается при подключении - VPN работает для всех приложений Windows.
### Kill Switch
⚠️ Временно отключен в этой версии из-за проблем с блокировкой интернета.
Будет доработан и добавлен в следующей версии.
### Защита DNS
Принудительно направляет все DNS запросы через VPN, предотвращая DNS утечки.
### Шифрование конфигураций
Все конфигурационные файлы шифруются с использованием AES-256-GCM и привязываются к вашему компьютеру.
### Проверка утечек
Встроенная система проверки DNS утечек и статуса защитных механизмов.
**Подробнее:** См. [Руководство по безопасности](Markdown/SECURITY_GUIDE.md)
## Отличия от Python версии ## Отличия от Python версии
@@ -154,8 +184,10 @@ vpn-client-gui.exe --cli
- ✅ Нативная кроссплатформенность - ✅ Нативная кроссплатформенность
- ✅ Полная поддержка WireGuard - ✅ Полная поддержка WireGuard
- ✅ Полная поддержка VLESS - ✅ Полная поддержка VLESS
- ⚠️ Нет GUI версии - **Автоматическая настройка системного прокси**
- ⚠️ Нет автоматической настройки системного прокси (пока) - **Kill Switch и защита от утечек**
-**Шифрование конфигураций**
-**Комплексная система безопасности**
## Разработка ## Разработка

7
go.mod
View File

@@ -2,10 +2,13 @@ module vpn-client
go 1.25.0 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 ( require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.42.0 // indirect
) )

4
go.sum
View File

@@ -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.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 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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.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.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 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=

160
installer.iss Normal file
View File

@@ -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;

View File

@@ -79,6 +79,14 @@ func Run() error {
if isConnected { if isConnected {
// Настройки // Настройки
settingsMenu() settingsMenu()
} else {
// Безопасность
securityMenu()
}
case "8":
if isConnected {
// Безопасность
securityMenu()
} else { } else {
fmt.Printf("%s Неверный выбор\n", red("✗")) fmt.Printf("%s Неверный выбор\n", red("✗"))
pause() pause()
@@ -125,9 +133,11 @@ func showMainMenu() {
fmt.Println("5. " + green("Мониторинг в реальном времени")) fmt.Println("5. " + green("Мониторинг в реальном времени"))
fmt.Println("6. Отключиться от VPN") fmt.Println("6. Отключиться от VPN")
fmt.Println("7. " + yellow("Настройки (выбор ядра)")) fmt.Println("7. " + yellow("Настройки (выбор ядра)"))
fmt.Println("8. " + cyan("🔒 Безопасность и защита"))
} else { } else {
fmt.Println("5. Отключиться от VPN") fmt.Println("5. Отключиться от VPN")
fmt.Println("6. " + yellow("Настройки (выбор ядра)")) fmt.Println("6. " + yellow("Настройки (выбор ядра)"))
fmt.Println("7. " + cyan("🔒 Безопасность и защита"))
} }
fmt.Println("0. Выход") fmt.Println("0. Выход")

323
internal/cli/security.go Normal file
View File

@@ -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()
}

View File

@@ -13,6 +13,22 @@ import (
"vpn-client/internal/wireguard" "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, `<!DOCTYPE html>
<html>
<head>
<title>VPN Client</title>
<meta charset="utf-8">
</head>
<body>
<h1>VPN Client GUI</h1>
<p>GUI интерфейс в разработке. Используйте CLI режим: <code>vpn-client-gui.exe --cli</code></p>
</body>
</html>`)
}
// Запуск HTTP сервера // Запуск HTTP сервера
func startServer() string { func startServer() string {
addr := "127.0.0.1:8765" addr := "127.0.0.1:8765"
@@ -198,7 +214,7 @@ func handleVLESSConnect(w http.ResponseWriter, r *http.Request) {
configs, _ := config.LoadConfigs() configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) { if req.Index >= 0 && req.Index < len(configs.VLESS) {
name := configs.VLESS[req.Index].Name 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{}{ json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil, "success": err == nil,

View File

@@ -1,14 +1,20 @@
// +build windows
package proxy package proxy
import ( import (
"fmt" "fmt"
"os/exec" "os/exec"
"strings" "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 { func EnableSystemProxy(proxyAddr string) error {
// Включаем прокси через реестр // Включаем прокси через реестр
cmd := exec.Command("reg", "add", cmd := exec.Command("reg", "add",
@@ -22,7 +28,7 @@ func EnableSystemProxy(proxyAddr string) error {
return fmt.Errorf("ошибка включения прокси: %w", err) return fmt.Errorf("ошибка включения прокси: %w", err)
} }
// Устанавливаем адрес прокси // Устанавливаем адрес прокси (используем socks= для SOCKS5)
cmd = exec.Command("reg", "add", cmd = exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer", "/v", "ProxyServer",
@@ -34,9 +40,25 @@ func EnableSystemProxy(proxyAddr string) error {
return fmt.Errorf("ошибка установки адреса прокси: %w", err) 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", "<local>;localhost;127.*;10.*;172.16.*;172.31.*;192.168.*",
"/f")
cmd.Run() // Игнорируем ошибку
// Применяем изменения немедленно через WinINet API
notifySystemProxyChange()
// Обновляем настройки Internet Explorer
refreshIESettings() refreshIESettings()
// Уведомляем систему о изменении настроек сети
notifyNetworkChange()
return nil return nil
} }
@@ -109,3 +131,79 @@ func refreshIESettings() {
cmd := exec.Command("rundll32.exe", "inetcpl.cpl,ClearMyTracksByProcess", "8") cmd := exec.Command("rundll32.exe", "inetcpl.cpl,ClearMyTracksByProcess", "8")
cmd.Run() 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
}

240
internal/security/dns.go Normal file
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -17,6 +17,7 @@ import (
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/logger" "vpn-client/internal/logger"
"vpn-client/internal/proxy" "vpn-client/internal/proxy"
"vpn-client/internal/security"
) )
// XrayConfig представляет конфигурацию Xray // XrayConfig представляет конфигурацию Xray
@@ -503,30 +504,51 @@ func Connect(configName string, logsDir string) error {
fmt.Printf("✓ Подключено к '%s' через %s\n", configName, coreName) fmt.Printf("✓ Подключено к '%s' через %s\n", configName, coreName)
fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n") fmt.Printf("SOCKS5 прокси: 127.0.0.1:10808\n")
// Предлагаем настроить системный прокси // Автоматически настраиваем системный прокси
fmt.Println("\n" + strings.Repeat("─", 60)) fmt.Println("\n" + strings.Repeat("─", 60))
fmt.Println("Настроить системный прокси Windows?") fmt.Println("Настройка системного прокси для работы VPN по всему ПК...")
fmt.Println("Это позволит всем приложениям использовать VPN.")
fmt.Print("(y/n): ")
var response string if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil {
fmt.Scanln(&response) 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) == "д" { secConfig, _ := secManager.LoadSecurityConfig()
if err := proxy.EnableSystemProxy("127.0.0.1:10808"); err != nil {
fmt.Printf("⚠ Не удалось настроить системный прокси: %v\n", err) // KILL SWITCH ПОЛНОСТЬЮ ОТКЛЮЧЕН!
fmt.Println("Вы можете настроить его вручную в настройках Windows") // Включаем только защиту DNS если она включена
if secConfig != nil && secConfig.DNSProtectionEnabled {
dnsProtection := security.NewDNSProtection()
if err := dnsProtection.Enable(secConfig.VPNDNSServers); err != nil {
fmt.Printf("⚠ Предупреждение: не удалось включить защиту DNS: %v\n", err)
} else { } else {
fmt.Println("✓ Системный прокси настроен") fmt.Println("✓ Защита DNS включена")
} }
} else { } else {
fmt.Println("\nДля использования VPN настройте прокси вручную:") fmt.Println(" Защита DNS отключена")
fmt.Println(" 1. Откройте Настройки Windows → Сеть и Интернет → Прокси") }
fmt.Println(" 2. Включите 'Использовать прокси-сервер'")
fmt.Println(" 3. Адрес: 127.0.0.1, Порт: 10808") // Защищаем директорию конфигураций
fmt.Println("\nИли настройте SOCKS5 прокси в браузере:") if err := secManager.ProtectConfigDirectory(); err != nil {
fmt.Println(" Firefox: Настройки → Основные → Параметры сети") fmt.Printf("⚠ Предупреждение: не удалось защитить директорию конфигураций: %v\n", err)
fmt.Println(" Chrome: Настройки → Система → Открыть настройки прокси") }
// Проверяем утечки
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") fmt.Printf("\nЛоги трафика:\n")
@@ -537,6 +559,37 @@ func Connect(configName string, logsDir string) error {
return nil 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 сервера // PingServer проверяет доступность VLESS сервера
func PingServer(vlessURL string, timeout time.Duration) (bool, float64, error) { func PingServer(vlessURL string, timeout time.Duration) (bool, float64, error) {
// Парсим URL для получения адреса сервера // Парсим URL для получения адреса сервера

View File

@@ -10,6 +10,7 @@ import (
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/logger" "vpn-client/internal/logger"
"vpn-client/internal/proxy" "vpn-client/internal/proxy"
"vpn-client/internal/security"
"vpn-client/internal/wireguard" "vpn-client/internal/wireguard"
) )
@@ -27,6 +28,17 @@ func Disconnect(logsDir string) error {
fmt.Printf("Отключение от '%s'...\n", state.ConfigName) 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 var logFile string
if state.ConfigType == "wireguard" { if state.ConfigType == "wireguard" {