2 Commits
v1.1 ... 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
20d24a3639 feat(cli): add settings menu and VLESS log viewer with core selection
- Add settings menu to switch between Xray and V2Ray cores for VLESS connections
- Implement core type persistence in configuration with LoadSettings/SaveSettings
- Add VLESS error and access log viewer showing last 30 and 20 lines respectively
- Display current core type and system time in main menu
- Update VLESS connection to use selected core dynamically
- Refactor monitor.go to accept 'q' key input for graceful exit instead of signal handling
- Add proxy platform-specific implementations (proxy_unix.go, proxy_windows.go)
- Add downloader module for managing binary resources
- Include V2Ray and Xray configuration files and geodata (geoip.dat, geosite.dat)
- Update CLI imports to include path/filepath and time packages
- Improve user experience with core selection visibility and log diagnostics
2026-04-06 20:06:35 +06:00
33 changed files with 47953 additions and 53 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

@@ -5,9 +5,11 @@ import (
"fmt" "fmt"
"os" "os"
"os/exec" "os/exec"
"path/filepath"
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"time"
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/subscription" "vpn-client/internal/subscription"
@@ -69,6 +71,22 @@ func Run() error {
fmt.Printf("%s %v\n", red("✗"), err) fmt.Printf("%s %v\n", red("✗"), err)
} }
pause() pause()
} else {
// Настройки
settingsMenu()
}
case "7":
if isConnected {
// Настройки
settingsMenu()
} else {
// Безопасность
securityMenu()
}
case "8":
if isConnected {
// Безопасность
securityMenu()
} else { } else {
fmt.Printf("%s Неверный выбор\n", red("✗")) fmt.Printf("%s Неверный выбор\n", red("✗"))
pause() pause()
@@ -86,12 +104,25 @@ func Run() error {
func showMainMenu() { func showMainMenu() {
state, _ := vpn.GetStatus() state, _ := vpn.GetStatus()
// Получаем актуальный статус с временем
statusLine := ShowQuickStatus() statusLine := ShowQuickStatus()
// Показываем текущее ядро
settings, _ := config.LoadSettings()
coreName := "Xray"
if settings != nil && settings.CoreType == "v2ray" {
coreName = "V2Ray"
}
fmt.Println("\n" + strings.Repeat("=", 60)) fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println(bold("VPN Клиент (Go)")) fmt.Println(bold("VPN Клиент (Go)"))
fmt.Println(strings.Repeat("=", 60)) fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Статус: %s\n", statusLine) fmt.Printf("Статус: %s\n", statusLine)
fmt.Printf("Ядро VLESS: %s\n", cyan(coreName))
// Показываем текущее время
fmt.Printf("Время: %s\n", time.Now().Format("15:04:05"))
fmt.Println(strings.Repeat("=", 60)) fmt.Println(strings.Repeat("=", 60))
fmt.Println("1. WireGuard") fmt.Println("1. WireGuard")
fmt.Println("2. VLESS") fmt.Println("2. VLESS")
@@ -101,8 +132,12 @@ func showMainMenu() {
if state != nil && state.Connected { if state != nil && state.Connected {
fmt.Println("5. " + green("Мониторинг в реальном времени")) fmt.Println("5. " + green("Мониторинг в реальном времени"))
fmt.Println("6. Отключиться от VPN") fmt.Println("6. Отключиться от VPN")
fmt.Println("7. " + yellow("Настройки (выбор ядра)"))
fmt.Println("8. " + cyan("🔒 Безопасность и защита"))
} else { } else {
fmt.Println("5. Отключиться от VPN") fmt.Println("5. Отключиться от VPN")
fmt.Println("6. " + yellow("Настройки (выбор ядра)"))
fmt.Println("7. " + cyan("🔒 Безопасность и защита"))
} }
fmt.Println("0. Выход") fmt.Println("0. Выход")
@@ -120,6 +155,7 @@ func vlessMenu() {
fmt.Println("3. Удалить конфиг") fmt.Println("3. Удалить конфиг")
fmt.Println("4. Подключиться") fmt.Println("4. Подключиться")
fmt.Println("5. Тестировать конфиг (пинг)") fmt.Println("5. Тестировать конфиг (пинг)")
fmt.Println("6. " + yellow("Показать логи ошибок"))
fmt.Println("0. Назад") fmt.Println("0. Назад")
fmt.Println(strings.Repeat("=", 50)) fmt.Println(strings.Repeat("=", 50))
@@ -143,6 +179,9 @@ func vlessMenu() {
case "5": case "5":
testVLESSConfig() testVLESSConfig()
pause() pause()
case "6":
showVLESSLogs()
pause()
default: default:
fmt.Printf("%s Неверный выбор\n", red("✗")) fmt.Printf("%s Неверный выбор\n", red("✗"))
pause() pause()
@@ -270,9 +309,16 @@ func connectVLESS() {
configName := configs.VLESS[num-1].Name configName := configs.VLESS[num-1].Name
fmt.Printf("\nПодключение к '%s' через Xray...\n", configName) // Показываем текущее ядро
settings, _ := config.LoadSettings()
coreName := "Xray"
if settings != nil && settings.CoreType == "v2ray" {
coreName = "V2Ray"
}
if err := vless.Connect(configName, config.LogsDir, config.XrayDir); err != nil { fmt.Printf("\nПодключение к '%s' через %s...\n", configName, coreName)
if err := vless.Connect(configName, config.LogsDir); err != nil {
fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err) fmt.Printf("%s Ошибка подключения: %v\n", red("✗"), err)
return return
} }
@@ -904,3 +950,105 @@ func connectWireGuard() {
} }
} }
} }
func settingsMenu() {
clearScreen()
settings, err := config.LoadSettings()
if err != nil {
fmt.Printf("%s Ошибка загрузки настроек: %v\n", red("✗"), err)
pause()
return
}
currentCore := "Xray"
if settings.CoreType == "v2ray" {
currentCore = "V2Ray"
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println(bold("Настройки"))
fmt.Println(strings.Repeat("=", 60))
fmt.Printf("Текущее ядро для VLESS: %s\n", cyan(currentCore))
fmt.Println(strings.Repeat("=", 60))
fmt.Println("\nВыберите ядро для подключения к VLESS:")
fmt.Println("1. Xray (рекомендуется)")
fmt.Println("2. V2Ray")
fmt.Println("0. Назад")
fmt.Println(strings.Repeat("=", 60))
choice := readInput("\nВыберите действие: ")
switch choice {
case "1":
settings.CoreType = "xray"
if err := config.SaveSettings(settings); err != nil {
fmt.Printf("%s Ошибка сохранения настроек: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Ядро изменено на Xray\n", green("✓"))
}
pause()
case "2":
settings.CoreType = "v2ray"
if err := config.SaveSettings(settings); err != nil {
fmt.Printf("%s Ошибка сохранения настроек: %v\n", red("✗"), err)
} else {
fmt.Printf("%s Ядро изменено на V2Ray\n", green("✓"))
}
pause()
case "0":
return
default:
fmt.Printf("%s Неверный выбор\n", red("✗"))
pause()
}
}
func showVLESSLogs() {
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Println(bold("Логи VLESS/Xray/V2Ray"))
fmt.Println(strings.Repeat("=", 60))
errorLog := filepath.Join(config.LogsDir, "vless_error.log")
accessLog := filepath.Join(config.LogsDir, "vless_access.log")
// Показываем лог ошибок
fmt.Println("\n" + yellow("=== Лог ошибок (последние 30 строк) ==="))
if data, err := os.ReadFile(errorLog); err == nil && len(data) > 0 {
lines := strings.Split(string(data), "\n")
start := len(lines) - 30
if start < 0 {
start = 0
}
for i := start; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) != "" {
fmt.Println(lines[i])
}
}
} else {
fmt.Println("Лог пуст или не найден")
}
fmt.Println("\n" + cyan("=== Лог доступа (последние 20 строк) ==="))
if data, err := os.ReadFile(accessLog); err == nil && len(data) > 0 {
lines := strings.Split(string(data), "\n")
start := len(lines) - 20
if start < 0 {
start = 0
}
for i := start; i < len(lines); i++ {
if strings.TrimSpace(lines[i]) != "" {
fmt.Println(lines[i])
}
}
} else {
fmt.Println("Лог пуст или не найден")
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Printf("Полные логи:\n")
fmt.Printf(" Ошибки: %s\n", errorLog)
fmt.Printf(" Доступ: %s\n", accessLog)
}

View File

@@ -3,7 +3,8 @@ package cli
import ( import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/exec"
"runtime"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@@ -25,20 +26,22 @@ func MonitorConnection() error {
return nil return nil
} }
fmt.Println("Нажмите Ctrl+C для выхода из мониторинга\n") fmt.Println("Нажмите 'q' или Ctrl+C для выхода из мониторинга\n")
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
// Создаем канал для обработки сигналов
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
// Создаем канал для остановки мониторинга // Создаем канал для остановки мониторинга
stopChan := make(chan bool, 1) stopChan := make(chan bool, 1)
// Запускаем горутину для обработки сигналов // Запускаем горутину для чтения клавиш
go func() { go func() {
<-sigChan for {
stopChan <- true var input string
fmt.Scanln(&input)
if strings.ToLower(input) == "q" || strings.ToLower(input) == "й" {
stopChan <- true
return
}
}
}() }()
// Запускаем мониторинг // Запускаем мониторинг
@@ -50,9 +53,8 @@ func MonitorConnection() error {
case <-ticker.C: case <-ticker.C:
clearScreen() clearScreen()
displayRealtimeStatus(state) displayRealtimeStatus(state)
fmt.Printf("\n%s Нажмите 'q' и Enter для выхода\n", cyan(""))
case <-stopChan: case <-stopChan:
// Восстанавливаем обработку сигналов по умолчанию
signal.Reset(os.Interrupt, syscall.SIGTERM)
fmt.Println("\n\nВыход из мониторинга...") fmt.Println("\n\nВыход из мониторинга...")
time.Sleep(500 * time.Millisecond) time.Sleep(500 * time.Millisecond)
return nil return nil
@@ -104,7 +106,7 @@ func displayRealtimeStatus(state *config.ConnectionState) {
} }
fmt.Println(strings.Repeat("═", 70)) fmt.Println(strings.Repeat("═", 70))
fmt.Printf("\n%s Обновление каждую секунду | Нажмите Ctrl+C для выхода\n", fmt.Printf("\n%s Нажмите 'q' и Enter для выхода\n",
cyan("")) cyan(""))
} }
@@ -126,17 +128,39 @@ func displayVLESSStats(state *config.ConnectionState) {
} }
} }
// Проверяем, что процесс еще работает // Проверяем, что процесс еще работает (улучшенная проверка для Windows)
if state.ProcessPID > 0 { if state.ProcessPID > 0 {
process, err := os.FindProcess(state.ProcessPID) processRunning := checkProcessRunning(state.ProcessPID)
if err == nil { if !processRunning {
if err := process.Signal(os.Signal(nil)); err != nil { fmt.Printf("\n%s Процесс Xray не отвечает!\n", red("⚠"))
fmt.Printf("\n%s Процесс Xray не отвечает!\n", red("⚠"))
}
} }
} }
} }
// checkProcessRunning проверяет, работает ли процесс
func checkProcessRunning(pid int) bool {
process, err := os.FindProcess(pid)
if err != nil {
return false
}
// На Windows FindProcess всегда успешен, нужна дополнительная проверка
// Пытаемся получить информацию о процессе через tasklist
if runtime.GOOS == "windows" {
cmd := exec.Command("tasklist", "/FI", fmt.Sprintf("PID eq %d", pid), "/NH")
output, err := cmd.Output()
if err != nil {
return false
}
// Если процесс существует, в выводе будет его PID
return strings.Contains(string(output), fmt.Sprintf("%d", pid))
}
// На Unix используем сигнал 0
err = process.Signal(syscall.Signal(0))
return err == nil
}
func displayWireGuardStats(state *config.ConnectionState) { func displayWireGuardStats(state *config.ConnectionState) {
fmt.Println("\n" + bold("WireGuard Туннель")) fmt.Println("\n" + bold("WireGuard Туннель"))
fmt.Printf("Интерфейс: %s\n", state.Interface) fmt.Printf("Интерфейс: %s\n", state.Interface)
@@ -198,7 +222,8 @@ func ShowQuickStatus() string {
duration := time.Since(startTime) duration := time.Since(startTime)
hours := int(duration.Hours()) hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60 minutes := int(duration.Minutes()) % 60
timeStr = fmt.Sprintf(" [%02d:%02d]", hours, minutes) seconds := int(duration.Seconds()) % 60
timeStr = fmt.Sprintf(" [%02d:%02d:%02d]", hours, minutes, seconds)
} }
} }

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

@@ -11,8 +11,10 @@ var (
SubscriptionsFile string SubscriptionsFile string
ConfigsFile string ConfigsFile string
StateFile string StateFile string
SettingsFile string
LogsDir string LogsDir string
XrayDir string XrayDir string
V2RayDir string
) )
type WireGuardConfig struct { type WireGuardConfig struct {
@@ -51,6 +53,10 @@ type ConnectionState struct {
LogFile string `json:"log_file"` LogFile string `json:"log_file"`
} }
type Settings struct {
CoreType string `json:"core_type"` // "xray" или "v2ray"
}
// Init инициализирует конфигурационные директории и файлы // Init инициализирует конфигурационные директории и файлы
func Init() error { func Init() error {
// Получаем рабочую директорию // Получаем рабочую директорию
@@ -63,8 +69,10 @@ func Init() error {
SubscriptionsFile = filepath.Join(ConfigDir, "subscriptions.json") SubscriptionsFile = filepath.Join(ConfigDir, "subscriptions.json")
ConfigsFile = filepath.Join(ConfigDir, "configs.json") ConfigsFile = filepath.Join(ConfigDir, "configs.json")
StateFile = filepath.Join(ConfigDir, "state.json") StateFile = filepath.Join(ConfigDir, "state.json")
SettingsFile = filepath.Join(ConfigDir, "settings.json")
LogsDir = filepath.Join(workDir, "logs") LogsDir = filepath.Join(workDir, "logs")
XrayDir = filepath.Join(workDir, "xray") XrayDir = filepath.Join(workDir, "xray")
V2RayDir = filepath.Join(workDir, "v2ray")
// Создаем директории // Создаем директории
if err := os.MkdirAll(ConfigDir, 0755); err != nil { if err := os.MkdirAll(ConfigDir, 0755); err != nil {
@@ -84,6 +92,9 @@ func Init() error {
if err := initFileIfNotExists(StateFile, ConnectionState{}); err != nil { if err := initFileIfNotExists(StateFile, ConnectionState{}); err != nil {
return err return err
} }
if err := initFileIfNotExists(SettingsFile, Settings{CoreType: "xray"}); err != nil {
return err
}
return nil return nil
} }
@@ -170,3 +181,32 @@ func SaveState(state *ConnectionState) error {
} }
return os.WriteFile(StateFile, data, 0644) return os.WriteFile(StateFile, data, 0644)
} }
// LoadSettings загружает настройки
func LoadSettings() (*Settings, error) {
data, err := os.ReadFile(SettingsFile)
if err != nil {
return nil, err
}
var settings Settings
if err := json.Unmarshal(data, &settings); err != nil {
return nil, err
}
// По умолчанию xray
if settings.CoreType == "" {
settings.CoreType = "xray"
}
return &settings, nil
}
// SaveSettings сохраняет настройки
func SaveSettings(settings *Settings) error {
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {
return err
}
return os.WriteFile(SettingsFile, data, 0644)
}

View File

@@ -0,0 +1,273 @@
package downloader
import (
"archive/zip"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/fatih/color"
)
const (
XrayVersion = "24.12.18"
V2RayVersion = "5.22.0"
XrayDownloadURL = "https://github.com/XTLS/Xray-core/releases/download/v%s/Xray-windows-64.zip"
V2RayDownloadURL = "https://github.com/v2fly/v2ray-core/releases/download/v%s/v2ray-windows-64.zip"
)
var (
green = color.New(color.FgGreen).SprintFunc()
yellow = color.New(color.FgYellow).SprintFunc()
cyan = color.New(color.FgCyan).SprintFunc()
)
// DownloadCore загружает Xray или V2Ray
func DownloadCore(coreDir, coreName string) error {
var downloadURL, version, exeName string
if coreName == "xray" {
downloadURL = fmt.Sprintf(XrayDownloadURL, XrayVersion)
version = XrayVersion
exeName = "xray.exe"
} else if coreName == "v2ray" {
downloadURL = fmt.Sprintf(V2RayDownloadURL, V2RayVersion)
version = V2RayVersion
exeName = "v2ray.exe"
} else {
return fmt.Errorf("неизвестный тип ядра: %s", coreName)
}
// Проверяем, существует ли уже
coreExe := filepath.Join(coreDir, exeName)
if _, err := os.Stat(coreExe); err == nil {
return nil
}
fmt.Println("\n" + strings.Repeat("=", 60))
fmt.Printf("%s %s не найден. Начинаю загрузку...\n", yellow("⚠"), strings.ToUpper(coreName))
fmt.Println(strings.Repeat("=", 60))
// Создаем директорию
if err := os.MkdirAll(coreDir, 0755); err != nil {
return fmt.Errorf("ошибка создания директории: %w", err)
}
fmt.Printf("\nВерсия: %s\n", cyan(version))
fmt.Printf("URL: %s\n\n", downloadURL)
// Загружаем файл
zipPath := filepath.Join(coreDir, coreName+".zip")
if err := downloadFile(zipPath, downloadURL); err != nil {
return fmt.Errorf("ошибка загрузки: %w", err)
}
fmt.Printf("\n%s Загрузка завершена\n", green("✓"))
fmt.Printf("%s Распаковка архива...\n", yellow("⚙"))
// Распаковываем
if err := unzip(zipPath, coreDir); err != nil {
os.Remove(zipPath)
return fmt.Errorf("ошибка распаковки: %w", err)
}
os.Remove(zipPath)
fmt.Printf("%s %s успешно установлен в %s\n", green("✓"), strings.ToUpper(coreName), coreDir)
fmt.Println(strings.Repeat("=", 60))
return nil
}
// DownloadXray загружает Xray
func DownloadXray(xrayDir string) error {
return DownloadCore(xrayDir, "xray")
}
// DownloadV2Ray загружает V2Ray
func DownloadV2Ray(v2rayDir string) error {
return DownloadCore(v2rayDir, "v2ray")
}
// downloadFile загружает файл с прогресс-баром
func downloadFile(filepath string, url string) error {
out, err := os.Create(filepath)
if err != nil {
return err
}
defer out.Close()
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ошибка загрузки: HTTP %d", resp.StatusCode)
}
size := resp.ContentLength
counter := &WriteCounter{Total: size}
_, err = io.Copy(out, io.TeeReader(resp.Body, counter))
if err != nil {
return err
}
fmt.Println()
return nil
}
// WriteCounter считает загруженные байты
type WriteCounter struct {
Total int64
Downloaded int64
}
func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Downloaded += int64(n)
wc.PrintProgress()
return n, nil
}
func (wc *WriteCounter) PrintProgress() {
fmt.Printf("\r%s", strings.Repeat(" ", 60))
percent := float64(wc.Downloaded) / float64(wc.Total) * 100
downloaded := formatBytes(wc.Downloaded)
total := formatBytes(wc.Total)
fmt.Printf("\rЗагрузка: %s / %s (%.1f%%)", downloaded, total, percent)
}
func formatBytes(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
// unzip распаковывает zip архив
func unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
fpath := filepath.Join(dest, f.Name)
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return fmt.Errorf("недопустимый путь файла: %s", fpath)
}
if f.FileInfo().IsDir() {
os.MkdirAll(fpath, os.ModePerm)
continue
}
if err := os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
rc, err := f.Open()
if err != nil {
outFile.Close()
return err
}
_, err = io.Copy(outFile, rc)
outFile.Close()
rc.Close()
if err != nil {
return err
}
}
return nil
}
// CheckAndDownloadXray проверяет и загружает Xray
func CheckAndDownloadXray(xrayDir string) error {
if runtime.GOOS != "windows" {
return nil
}
xrayExe := filepath.Join(xrayDir, "xray.exe")
if _, err := os.Stat(xrayExe); os.IsNotExist(err) {
return DownloadXray(xrayDir)
}
return nil
}
// CheckAndDownloadV2Ray проверяет и загружает V2Ray
func CheckAndDownloadV2Ray(v2rayDir string) error {
if runtime.GOOS != "windows" {
return nil
}
v2rayExe := filepath.Join(v2rayDir, "v2ray.exe")
if _, err := os.Stat(v2rayExe); os.IsNotExist(err) {
return DownloadV2Ray(v2rayDir)
}
return nil
}
// CheckAndDownloadBoth проверяет и загружает оба ядра
func CheckAndDownloadBoth(xrayDir, v2rayDir string) error {
if runtime.GOOS != "windows" {
return nil
}
// Проверяем Xray
xrayExe := filepath.Join(xrayDir, "xray.exe")
xrayExists := true
if _, err := os.Stat(xrayExe); os.IsNotExist(err) {
xrayExists = false
}
// Проверяем V2Ray
v2rayExe := filepath.Join(v2rayDir, "v2ray.exe")
v2rayExists := true
if _, err := os.Stat(v2rayExe); os.IsNotExist(err) {
v2rayExists = false
}
// Если оба есть, ничего не делаем
if xrayExists && v2rayExists {
return nil
}
// Загружаем недостающие
if !xrayExists {
if err := DownloadXray(xrayDir); err != nil {
return err
}
}
if !v2rayExists {
if err := DownloadV2Ray(v2rayDir); err != nil {
return err
}
}
return nil
}

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

@@ -0,0 +1,22 @@
// +build !windows
package proxy
import (
"fmt"
)
// EnableSystemProxy включает системный прокси (заглушка для Unix)
func EnableSystemProxy(proxyAddr string) error {
return fmt.Errorf("автоматическая настройка системного прокси не поддерживается на этой платформе")
}
// DisableSystemProxy отключает системный прокси (заглушка для Unix)
func DisableSystemProxy() error {
return nil
}
// GetSystemProxyStatus проверяет статус системного прокси (заглушка для Unix)
func GetSystemProxyStatus() (bool, string, error) {
return false, "", nil
}

View File

@@ -0,0 +1,209 @@
package proxy
import (
"fmt"
"os/exec"
"strings"
"syscall"
)
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",
"HKCU\\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)
}
// Устанавливаем адрес прокси (используем socks= для SOCKS5)
cmd = exec.Command("reg", "add",
"HKCU\\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)
}
// Отключаем прокси для локальных адресов
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()
// Уведомляем систему о изменении настроек сети
notifyNetworkChange()
return nil
}
// DisableSystemProxy отключает системный прокси в Windows
func DisableSystemProxy() error {
// Отключаем прокси
cmd := exec.Command("reg", "add",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyEnable",
"/t", "REG_DWORD",
"/d", "0",
"/f")
if err := cmd.Run(); err != nil {
return fmt.Errorf("ошибка отключения прокси: %w", err)
}
// Очищаем адрес прокси
cmd = exec.Command("reg", "delete",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer",
"/f")
cmd.Run() // Игнорируем ошибку, если ключ не существует
// Обновляем настройки
refreshIESettings()
return nil
}
// GetSystemProxyStatus проверяет статус системного прокси
func GetSystemProxyStatus() (bool, string, error) {
// Проверяем, включен ли прокси
cmd := exec.Command("reg", "query",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyEnable")
output, err := cmd.Output()
if err != nil {
return false, "", nil
}
enabled := strings.Contains(string(output), "0x1")
// Получаем адрес прокси
cmd = exec.Command("reg", "query",
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings",
"/v", "ProxyServer")
output, err = cmd.Output()
proxyAddr := ""
if err == nil {
lines := strings.Split(string(output), "\n")
for _, line := range lines {
if strings.Contains(line, "ProxyServer") {
parts := strings.Fields(line)
if len(parts) >= 3 {
proxyAddr = parts[len(parts)-1]
}
}
}
}
return enabled, proxyAddr, nil
}
func refreshIESettings() {
// Используем rundll32 для обновления настроек Internet Explorer
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
}

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

@@ -11,10 +11,13 @@ import (
"runtime" "runtime"
"strconv" "strconv"
"strings" "strings"
"syscall"
"time" "time"
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/logger" "vpn-client/internal/logger"
"vpn-client/internal/proxy"
"vpn-client/internal/security"
) )
// XrayConfig представляет конфигурацию Xray // XrayConfig представляет конфигурацию Xray
@@ -331,7 +334,35 @@ func getParam(params map[string]string, key, defaultValue string) string {
} }
// Connect подключается к VLESS серверу // Connect подключается к VLESS серверу
func Connect(configName string, logsDir, xrayDir string) error { func Connect(configName string, logsDir string) error {
// Загружаем настройки
settings, err := config.LoadSettings()
if err != nil {
return fmt.Errorf("ошибка загрузки настроек: %w", err)
}
// Определяем директорию и имя исполняемого файла
var coreDir, coreExe, coreName string
if settings.CoreType == "v2ray" {
coreDir = config.V2RayDir
coreName = "V2Ray"
if runtime.GOOS == "windows" {
coreExe = "v2ray.exe"
} else {
coreExe = "v2ray"
}
} else {
coreDir = config.XrayDir
coreName = "Xray"
if runtime.GOOS == "windows" {
coreExe = "xray.exe"
} else {
coreExe = "xray"
}
}
corePath := filepath.Join(coreDir, coreExe)
// Загружаем конфигурации // Загружаем конфигурации
configs, err := config.LoadConfigs() configs, err := config.LoadConfigs()
if err != nil { if err != nil {
@@ -351,15 +382,15 @@ func Connect(configName string, logsDir, xrayDir string) error {
return fmt.Errorf("конфиг '%s' не найден", configName) return fmt.Errorf("конфиг '%s' не найден", configName)
} }
// Создаем конфигурацию Xray // Создаем конфигурацию
xrayConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir) coreConfig, err := ParseVLESSURL(vlessConfig.URL, logsDir)
if err != nil { if err != nil {
return fmt.Errorf("ошибка парсинга VLESS URL: %w", err) return fmt.Errorf("ошибка парсинга VLESS URL: %w", err)
} }
// Сохраняем конфигурацию // Сохраняем конфигурацию
configPath := filepath.Join(config.ConfigDir, "xray_config.json") configPath := filepath.Join(config.ConfigDir, "xray_config.json")
data, err := json.MarshalIndent(xrayConfig, "", " ") data, err := json.MarshalIndent(coreConfig, "", " ")
if err != nil { if err != nil {
return fmt.Errorf("ошибка сериализации конфига: %w", err) return fmt.Errorf("ошибка сериализации конфига: %w", err)
} }
@@ -368,15 +399,9 @@ func Connect(configName string, logsDir, xrayDir string) error {
return fmt.Errorf("ошибка сохранения конфига: %w", err) return fmt.Errorf("ошибка сохранения конфига: %w", err)
} }
// Путь к xray // Проверяем наличие исполняемого файла
xrayExe := "xray" if _, err := os.Stat(corePath); os.IsNotExist(err) {
if runtime.GOOS == "windows" { return fmt.Errorf("%s не найден в %s\n\nПожалуйста, перезапустите приложение для автоматической загрузки %s", coreName, coreDir, coreName)
xrayExe = "xray.exe"
}
xrayPath := filepath.Join(xrayDir, xrayExe)
if _, err := os.Stat(xrayPath); os.IsNotExist(err) {
return fmt.Errorf("xray не найден в %s", xrayDir)
} }
// Создаем лог-файл трафика // Создаем лог-файл трафика
@@ -389,27 +414,70 @@ func Connect(configName string, logsDir, xrayDir string) error {
// Записываем заголовок // Записываем заголовок
fmt.Fprintf(logFile, "=== VPN Подключение начато: %s ===\n", time.Now().Format("2006-01-02 15:04:05")) fmt.Fprintf(logFile, "=== VPN Подключение начато: %s ===\n", time.Now().Format("2006-01-02 15:04:05"))
fmt.Fprintf(logFile, "Конфиг: %s\n", configName) fmt.Fprintf(logFile, "Конфиг: %s\n", configName)
fmt.Fprintf(logFile, "Клиент: Xray\n") fmt.Fprintf(logFile, "Клиент: %s\n", coreName)
fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n") fmt.Fprintf(logFile, "Прокси: 127.0.0.1:10808\n")
fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60)) fmt.Fprintf(logFile, "%s\n\n", strings.Repeat("=", 60))
// Запускаем xray // Запускаем ядро
cmd := exec.Command(xrayPath, "run", "-c", configPath) cmd := exec.Command(corePath, "run", "-c", configPath)
cmd.Stdout = logFile cmd.Stdout = logFile
cmd.Stderr = logFile cmd.Stderr = logFile
// На Windows создаем процесс в новой группе, чтобы Ctrl+C не убивал его
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP,
}
}
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
logFile.Close() logFile.Close()
return fmt.Errorf("ошибка запуска xray: %w", err) return fmt.Errorf("ошибка запуска %s: %w", coreName, err)
} }
// Ждем немного для проверки запуска // Ждем немного для проверки запуска
time.Sleep(3 * time.Second) time.Sleep(2 * time.Second)
// Проверяем, что процесс еще работает // Проверяем, что процесс еще работает (для Windows используем другой метод)
if err := cmd.Process.Signal(os.Signal(nil)); err != nil { processRunning := true
if runtime.GOOS == "windows" {
// На Windows просто проверяем, что процесс существует
process, err := os.FindProcess(cmd.Process.Pid)
if err != nil {
processRunning = false
} else {
// Пытаемся отправить сигнал 0 (проверка существования)
err = process.Signal(syscall.Signal(0))
// На Windows это всегда возвращает ошибку, поэтому просто считаем что процесс работает
processRunning = true
}
} else {
// На Unix используем стандартную проверку
err := cmd.Process.Signal(syscall.Signal(0))
processRunning = (err == nil)
}
if !processRunning {
logFile.Close() logFile.Close()
return fmt.Errorf("процесс xray завершился с ошибкой")
// Читаем логи для диагностики
errorLog := filepath.Join(logsDir, "vless_error.log")
if errorData, readErr := os.ReadFile(errorLog); readErr == nil && len(errorData) > 0 {
lines := strings.Split(string(errorData), "\n")
errorLines := []string{}
for i := len(lines) - 1; i >= 0 && len(errorLines) < 10; i-- {
if strings.TrimSpace(lines[i]) != "" {
errorLines = append([]string{lines[i]}, errorLines...)
}
}
if len(errorLines) > 0 {
return fmt.Errorf("процесс %s завершился с ошибкой.\n\nПоследние строки из лога ошибок:\n%s\n\nПолный лог: %s",
coreName, strings.Join(errorLines, "\n"), errorLog)
}
}
return fmt.Errorf("процесс %s завершился с ошибкой.\nПроверьте логи в: %s", coreName, errorLog)
} }
// Сохраняем состояние // Сохраняем состояние
@@ -418,7 +486,7 @@ func Connect(configName string, logsDir, xrayDir string) error {
ConfigName: configName, ConfigName: configName,
ConfigType: "vless", ConfigType: "vless",
StartTime: time.Now().Format(time.RFC3339), StartTime: time.Now().Format(time.RFC3339),
Interface: "xray", Interface: strings.ToLower(coreName),
ProcessPID: cmd.Process.Pid, ProcessPID: cmd.Process.Pid,
LogFile: trafficLog, LogFile: trafficLog,
} }
@@ -431,10 +499,58 @@ func Connect(configName string, logsDir, xrayDir string) error {
// Логируем успешное подключение // Логируем успешное подключение
logPath := logger.GetLogPath(logsDir, "vless") logPath := logger.GetLogPath(logsDir, "vless")
logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' (PID: %d, Лог: %s)", configName, cmd.Process.Pid, trafficLog)) logger.LogMessage(logPath, fmt.Sprintf("Успешно подключено к '%s' через %s (PID: %d, Лог: %s)", configName, coreName, cmd.Process.Pid, trafficLog))
fmt.Printf("✓ Подключено к '%s'\n", configName) 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("Настройка системного прокси для работы VPN по всему ПК...")
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")
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("✓ Защита DNS включена")
}
} else {
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") fmt.Printf("\nЛоги трафика:\n")
fmt.Printf(" - Основной: %s\n", trafficLog) fmt.Printf(" - Основной: %s\n", trafficLog)
fmt.Printf(" - Доступ (IP): %s\n", filepath.Join(logsDir, "vless_access.log")) fmt.Printf(" - Доступ (IP): %s\n", filepath.Join(logsDir, "vless_access.log"))
@@ -443,6 +559,37 @@ func Connect(configName string, logsDir, xrayDir 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

@@ -9,6 +9,8 @@ import (
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/logger" "vpn-client/internal/logger"
"vpn-client/internal/proxy"
"vpn-client/internal/security"
"vpn-client/internal/wireguard" "vpn-client/internal/wireguard"
) )
@@ -26,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" {
@@ -84,6 +97,16 @@ func Disconnect(logsDir string) error {
return fmt.Errorf("ошибка сохранения состояния: %w", err) return fmt.Errorf("ошибка сохранения состояния: %w", err)
} }
// Отключаем системный прокси если был VLESS
if state.ConfigType == "vless" {
if err := proxy.DisableSystemProxy(); err != nil {
fmt.Printf("%s Не удалось отключить системный прокси: %v\n", "⚠", err)
fmt.Println("Отключите его вручную в настройках Windows")
} else {
fmt.Println("✓ Системный прокси отключен")
}
}
fmt.Println("✓ Отключено от VPN") fmt.Println("✓ Отключено от VPN")
return nil return nil
} }

View File

@@ -7,6 +7,7 @@ import (
"vpn-client/internal/admin" "vpn-client/internal/admin"
"vpn-client/internal/cli" "vpn-client/internal/cli"
"vpn-client/internal/config" "vpn-client/internal/config"
"vpn-client/internal/downloader"
) )
func main() { func main() {
@@ -19,9 +20,23 @@ func main() {
os.Exit(1) os.Exit(1)
} }
// Проверка и загрузка Xray и V2Ray
fmt.Println("Проверка наличия Xray и V2Ray...")
if err := downloader.CheckAndDownloadBoth(config.XrayDir, config.V2RayDir); err != nil {
fmt.Fprintf(os.Stderr, "Ошибка загрузки ядер: %v\n", err)
fmt.Println("\nНажмите Enter для выхода...")
fmt.Scanln()
os.Exit(1)
}
// Запуск CLI // Запуск CLI
if err := cli.Run(); err != nil { if err := cli.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err) fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
fmt.Println("\nНажмите Enter для выхода...")
fmt.Scanln()
os.Exit(1) os.Exit(1)
} }
// Нормальный выход
os.Exit(0)
} }

144
v2ray/config.json Normal file
View File

@@ -0,0 +1,144 @@
// Config file of V2Ray. This file follows standard JSON format, with comments support.
// Uncomment entries below to satisfy your needs. Also read our manual for more detail at
// https://www.v2fly.org/
{
"log": {
// By default, V2Ray writes access log to stdout.
// "access": "/path/to/access/log/file",
// By default, V2Ray write error log to stdout.
// "error": "/path/to/error/log/file",
// Log level, one of "debug", "info", "warning", "error", "none"
"loglevel": "warning"
},
// List of inbound proxy configurations.
"inbounds": [{
// Port to listen on. You may need root access if the value is less than 1024.
"port": 1080,
// IP address to listen on. Change to "0.0.0.0" to listen on all network interfaces.
"listen": "127.0.0.1",
// Tag of the inbound proxy. May be used for routing.
"tag": "socks-inbound",
// Protocol name of inbound proxy.
"protocol": "socks",
// Settings of the protocol. Varies based on protocol.
"settings": {
"auth": "noauth",
"udp": false,
"ip": "127.0.0.1"
},
// Enable sniffing on TCP connection.
"sniffing": {
"enabled": true,
// Target domain will be overriden to the one carried by the connection, if the connection is HTTP or HTTPS.
"destOverride": ["http", "tls"]
}
}],
// List of outbound proxy configurations.
"outbounds": [{
// Protocol name of the outbound proxy.
"protocol": "freedom",
// Settings of the protocol. Varies based on protocol.
"settings": {},
// Tag of the outbound. May be used for routing.
"tag": "direct"
},{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}],
// Transport is for global transport settings. If you have multiple transports with same settings
// (say mKCP), you may put it here, instead of in each individual inbound/outbounds.
//"transport": {},
// Routing controls how traffic from inbounds are sent to outbounds.
"routing": {
"domainStrategy": "IPOnDemand",
"rules":[
{
// Blocks access to private IPs. Remove this if you want to access your router.
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
},
{
// Blocks major ads.
"type": "field",
"domain": ["geosite:category-ads"],
"outboundTag": "blocked"
}
]
},
// Dns settings for domain resolution.
"dns": {
// Static hosts, similar to hosts file.
"hosts": {
// Match v2fly.org to another domain on CloudFlare. This domain will be used when querying IPs for v2fly.org.
"domain:v2fly.org": "www.vicemc.net",
// The following settings help to eliminate DNS poisoning in mainland China.
// It is safe to comment these out if this is not the case for you.
"domain:github.io": "pages.github.com",
"domain:wikipedia.org": "www.wikimedia.org",
"domain:shadowsocks.org": "electronicsrealm.com"
},
"servers": [
"1.1.1.1",
{
"address": "114.114.114.114",
"port": 53,
// List of domains that use this DNS first.
"domains": [
"geosite:cn"
]
},
"8.8.8.8",
"localhost"
]
},
// Policy controls some internal behavior of how V2Ray handles connections.
// It may be on connection level by user levels in 'levels', or global settings in 'system.'
"policy": {
// Connection policys by user levels
"levels": {
"0": {
"uplinkOnly": 0,
"downlinkOnly": 0
}
},
"system": {
"statsInboundUplink": false,
"statsInboundDownlink": false,
"statsOutboundUplink": false,
"statsOutboundDownlink": false
}
},
// Stats enables internal stats counter.
// This setting can be used together with Policy and Api.
//"stats":{},
// Api enables gRPC APIs for external programs to communicate with V2Ray instance.
//"api": {
//"tag": "api",
//"services": [
// "HandlerService",
// "LoggerService",
// "StatsService"
//]
//},
// You may add other entries to the configuration, but they will not be recognized by V2Ray.
"other": {}
}

Binary file not shown.

BIN
v2ray/geoip.dat Normal file

Binary file not shown.

22209
v2ray/geosite.dat Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,25 @@
{
"log": {
"loglevel": "warning"
},
"inbounds": [{
"port": 1080,
"listen": "127.0.0.1",
"protocol": "socks",
"settings": {
"auth": "noauth",
"udp": false,
"ip": "127.0.0.1"
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {},
"tag": "direct"
}],
"policy": {
"levels": {
"0": {"uplinkOnly": 0}
}
}
}

View File

@@ -0,0 +1,32 @@
{
"inbounds": [{
"port": 10086,
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "23ad6b10-8d1a-40f7-8ad0-e3e35cd38297",
"level": 1,
"alterId": 64
}
]
}
}],
"outbounds": [{
"protocol": "freedom",
"settings": {}
},{
"protocol": "blackhole",
"settings": {},
"tag": "blocked"
}],
"routing": {
"rules": [
{
"type": "field",
"ip": ["geoip:private"],
"outboundTag": "blocked"
}
]
}
}

373
xray/LICENSE Normal file
View File

@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.

147
xray/README.md Normal file
View File

@@ -0,0 +1,147 @@
# Project X
[Project X](https://github.com/XTLS) originates from XTLS protocol, providing a set of network tools such as [Xray-core](https://github.com/XTLS/Xray-core) and [REALITY](https://github.com/XTLS/REALITY).
[README](https://github.com/XTLS/Xray-core#readme) is open, so feel free to submit your project [here](https://github.com/XTLS/Xray-core/pulls).
## Donation & NFTs
- **ETH/USDT/USDC: `0xDc3Fe44F0f25D13CACb1C4896CD0D321df3146Ee`**
- **Project X NFT: [Announcement of NFTs by Project X](https://github.com/XTLS/Xray-core/discussions/3633)**
- **REALITY NFT: [XHTTP: Beyond REALITY](https://github.com/XTLS/Xray-core/discussions/4113)**
## License
[Mozilla Public License Version 2.0](https://github.com/XTLS/Xray-core/blob/main/LICENSE)
## Documentation
[Project X Official Website](https://xtls.github.io)
## Telegram
[Project X](https://t.me/projectXray)
[Project X Channel](https://t.me/projectXtls)
[Project VLESS](https://t.me/projectVless) (non-Chinese)
## Installation
- Linux Script
- [XTLS/Xray-install](https://github.com/XTLS/Xray-install) (**Official**)
- [tempest](https://github.com/team-cloudchaser/tempest) (supports [`systemd`](https://systemd.io) and [OpenRC](https://github.com/OpenRC/openrc); Linux-only)
- Docker
- [ghcr.io/xtls/xray-core](https://ghcr.io/xtls/xray-core) (**Official**)
- [teddysun/xray](https://hub.docker.com/r/teddysun/xray)
- [wulabing/xray_docker](https://github.com/wulabing/xray_docker)
- Web Panel - **WARNING: Please DO NOT USE plain HTTP panels like 3X-UI**, as they are believed to be bribed by Iran GFW for supporting plain HTTP by default and refused to change (https://github.com/XTLS/Xray-core/pull/3884#issuecomment-2439595331), which has already put many users' data security in danger in the past few years. **If you are already using 3X-UI, please switch to the following panels, which are verified to support HTTPS and SSH port forwarding only:**
- [Marzban](https://github.com/Gozargah/Marzban)
- [Xray-UI](https://github.com/qist/xray-ui)
- [Hiddify](https://github.com/hiddify/Hiddify-Manager)
- One Click
- [Xray-REALITY](https://github.com/zxcvos/Xray-script), [xray-reality](https://github.com/sajjaddg/xray-reality), [reality-ezpz](https://github.com/aleskxyz/reality-ezpz)
- [Xray_bash_onekey](https://github.com/hello-yunshu/Xray_bash_onekey), [XTool](https://github.com/LordPenguin666/XTool)
- [v2ray-agent](https://github.com/mack-a/v2ray-agent), [Xray_onekey](https://github.com/wulabing/Xray_onekey), [ProxySU](https://github.com/proxysu/ProxySU)
- Magisk
- [Xray4Magisk](https://github.com/Asterisk4Magisk/Xray4Magisk)
- [Xray_For_Magisk](https://github.com/E7KMbb/Xray_For_Magisk)
- Homebrew
- `brew install xray`
## Usage
- Example
- [VLESS-XTLS-uTLS-REALITY](https://github.com/XTLS/REALITY#readme)
- [VLESS-TCP-XTLS-Vision](https://github.com/XTLS/Xray-examples/tree/main/VLESS-TCP-XTLS-Vision)
- [All-in-One-fallbacks-Nginx](https://github.com/XTLS/Xray-examples/tree/main/All-in-One-fallbacks-Nginx)
- Xray-examples
- [XTLS/Xray-examples](https://github.com/XTLS/Xray-examples)
- [chika0801/Xray-examples](https://github.com/chika0801/Xray-examples)
- [lxhao61/integrated-examples](https://github.com/lxhao61/integrated-examples)
- Tutorial
- [XTLS Vision](https://github.com/chika0801/Xray-install)
- [REALITY (English)](https://cscot.pages.dev/2023/03/02/Xray-REALITY-tutorial/)
- [XTLS-Iran-Reality (English)](https://github.com/SasukeFreestyle/XTLS-Iran-Reality)
- [Xray REALITY with 'steal oneself' (English)](https://computerscot.github.io/vless-xtls-utls-reality-steal-oneself.html)
- [Xray with WireGuard inbound (English)](https://g800.pages.dev/wireguard)
## GUI Clients
- OpenWrt
- [PassWall](https://github.com/xiaorouji/openwrt-passwall), [PassWall 2](https://github.com/xiaorouji/openwrt-passwall2)
- [ShadowSocksR Plus+](https://github.com/fw876/helloworld)
- [luci-app-xray](https://github.com/yichya/luci-app-xray) ([openwrt-xray](https://github.com/yichya/openwrt-xray))
- Windows
- [v2rayN](https://github.com/2dust/v2rayN)
- [Furious](https://github.com/LorenEteval/Furious)
- [Invisible Man - Xray](https://github.com/InvisibleManVPN/InvisibleMan-XRayClient)
- Android
- [v2rayNG](https://github.com/2dust/v2rayNG)
- [X-flutter](https://github.com/XTLS/X-flutter)
- [SaeedDev94/Xray](https://github.com/SaeedDev94/Xray)
- iOS & macOS arm64
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
- macOS arm64 & x64
- [V2rayU](https://github.com/yanue/V2rayU)
- [V2RayXS](https://github.com/tzmax/V2RayXS)
- [Furious](https://github.com/LorenEteval/Furious)
- [FoXray](https://apps.apple.com/app/foxray/id6448898396)
- Linux
- [v2rayA](https://github.com/v2rayA/v2rayA)
- [Furious](https://github.com/LorenEteval/Furious)
## Others that support VLESS, XTLS, REALITY, XUDP, PLUX...
- iOS & macOS arm64
- [Shadowrocket](https://apps.apple.com/app/shadowrocket/id932747118)
- Xray Tools
- [xray-knife](https://github.com/lilendian0x00/xray-knife)
- Xray Wrapper
- [XTLS/libXray](https://github.com/XTLS/libXray)
- [xtlsapi](https://github.com/hiddify/xtlsapi)
- [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite)
- [Xray-core-python](https://github.com/LorenEteval/Xray-core-python)
- [xray-api](https://github.com/XVGuardian/xray-api)
- [XrayR](https://github.com/XrayR-project/XrayR)
- [XrayR-release](https://github.com/XrayR-project/XrayR-release)
- [XrayR-V2Board](https://github.com/missuo/XrayR-V2Board)
- [Clash.Meta](https://github.com/MetaCubeX/Clash.Meta)
- [clashN](https://github.com/2dust/clashN)
- [Clash Meta for Android](https://github.com/MetaCubeX/ClashMetaForAndroid)
- [sing-box](https://github.com/SagerNet/sing-box)
## Contributing
[Code of Conduct](https://github.com/XTLS/Xray-core/blob/main/CODE_OF_CONDUCT.md)
## Credits
- [Xray-core v1.0.0](https://github.com/XTLS/Xray-core/releases/tag/v1.0.0) was forked from [v2fly-core 9a03cc5](https://github.com/v2fly/v2ray-core/commit/9a03cc5c98d04cc28320fcee26dbc236b3291256), and we have made & accumulated a huge number of enhancements over time, check [the release notes for each version](https://github.com/XTLS/Xray-core/releases).
- For third-party projects used in [Xray-core](https://github.com/XTLS/Xray-core), check your local or [the latest go.mod](https://github.com/XTLS/Xray-core/blob/main/go.mod).
## Compilation
### Windows (PowerShell)
```powershell
$env:CGO_ENABLED=0
go build -o xray.exe -trimpath -ldflags "-s -w -buildid=" ./main
```
### Linux / macOS
```bash
CGO_ENABLED=0 go build -o xray -trimpath -ldflags "-s -w -buildid=" ./main
```
### Reproducible Releases
```bash
make
```
## Stargazers over time
[![Stargazers over time](https://starchart.cc/XTLS/Xray-core.svg)](https://starchart.cc/XTLS/Xray-core)

BIN
xray/geoip.dat Normal file

Binary file not shown.

22209
xray/geosite.dat Normal file

File diff suppressed because one or more lines are too long