Files
Go-VPN-Client/internal/gui/server.go
arkonsadter 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

454 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gui
import (
"encoding/json"
"fmt"
"net/http"
"time"
"vpn-client/internal/config"
"vpn-client/internal/subscription"
"vpn-client/internal/vless"
"vpn-client/internal/vpn"
"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 сервера
func startServer() string {
addr := "127.0.0.1:8765"
http.HandleFunc("/", handleIndex)
http.HandleFunc("/api/status", handleStatus)
http.HandleFunc("/api/wireguard/list", handleWireGuardList)
http.HandleFunc("/api/wireguard/add", handleWireGuardAdd)
http.HandleFunc("/api/wireguard/delete", handleWireGuardDelete)
http.HandleFunc("/api/wireguard/connect", handleWireGuardConnect)
http.HandleFunc("/api/vless/list", handleVLESSList)
http.HandleFunc("/api/vless/add", handleVLESSAdd)
http.HandleFunc("/api/vless/delete", handleVLESSDelete)
http.HandleFunc("/api/vless/connect", handleVLESSConnect)
http.HandleFunc("/api/vless/test", handleVLESSTest)
http.HandleFunc("/api/subscriptions/list", handleSubscriptionsList)
http.HandleFunc("/api/subscriptions/add", handleSubscriptionsAdd)
http.HandleFunc("/api/subscriptions/delete", handleSubscriptionsDelete)
http.HandleFunc("/api/subscriptions/update", handleSubscriptionsUpdate)
http.HandleFunc("/api/subscriptions/show", handleSubscriptionsShow)
http.HandleFunc("/api/subscriptions/test", handleSubscriptionsTest)
http.HandleFunc("/api/disconnect", handleDisconnect)
go http.ListenAndServe(addr, nil)
return "http://" + addr
}
// API handlers
func handleStatus(w http.ResponseWriter, r *http.Request) {
state, err := vpn.GetStatus()
response := map[string]interface{}{
"connected": false,
"info": "",
}
if err == nil && state.Connected {
response["connected"] = true
info := fmt.Sprintf("Конфиг: %s | Тип: %s", state.ConfigName, state.ConfigType)
if state.StartTime != "" {
startTime, err := time.Parse(time.RFC3339, state.StartTime)
if err == nil {
duration := time.Since(startTime)
hours := int(duration.Hours())
minutes := int(duration.Minutes()) % 60
seconds := int(duration.Seconds()) % 60
info += fmt.Sprintf(" | Время: %02d:%02d:%02d", hours, minutes, seconds)
}
}
if state.ConfigType == "vless" {
info += " | Прокси: 127.0.0.1:10808"
} else if state.ConfigType == "wireguard" {
stats, err := wireguard.GetStats(state.Interface)
if err == nil {
info += fmt.Sprintf(" | ↓%s ↑%s", stats["rx"], stats["tx"])
}
}
response["info"] = info
}
json.NewEncoder(w).Encode(response)
}
func handleWireGuardList(w http.ResponseWriter, r *http.Request) {
configs, _ := config.LoadConfigs()
json.NewEncoder(w).Encode(configs.WireGuard)
}
func handleWireGuardAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Config string `json:"config"`
}
json.NewDecoder(r.Body).Decode(&req)
err := wireguard.AddConfig(req.Name, req.Config)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Конфиг добавлен"
}(),
})
}
func handleWireGuardDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.WireGuard) {
name := configs.WireGuard[req.Index].Name
wireguard.DeleteConfig(name)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleWireGuardConnect(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.WireGuard) {
name := configs.WireGuard[req.Index].Name
err := wireguard.Connect(name, config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Подключено к " + name
}(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Конфиг не найден",
})
}
func handleVLESSList(w http.ResponseWriter, r *http.Request) {
configs, _ := config.LoadConfigs()
json.NewEncoder(w).Encode(configs.VLESS)
}
func handleVLESSAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
URL string `json:"url"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, err := config.LoadConfigs()
if err == nil {
configs.VLESS = append(configs.VLESS, config.VLESSConfig{
Name: req.Name,
URL: req.URL,
Protocol: "VLESS",
})
config.SaveConfigs(configs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleVLESSDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) {
configs.VLESS = append(configs.VLESS[:req.Index], configs.VLESS[req.Index+1:]...)
config.SaveConfigs(configs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleVLESSConnect(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) {
name := configs.VLESS[req.Index].Name
err := vless.Connect(name, config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Подключено к " + name + "\n\nSOCKS5 прокси: 127.0.0.1:10808"
}(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Конфиг не найден",
})
}
func handleVLESSTest(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
configs, _ := config.LoadConfigs()
if req.Index >= 0 && req.Index < len(configs.VLESS) {
cfg := configs.VLESS[req.Index]
success, ping, err := vless.PingServer(cfg.URL, 5*time.Second)
if err != nil || !success {
msg := "Сервер недоступен"
if err != nil {
msg += "\n\nОшибка: " + err.Error()
}
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
quality := "Плохо"
if ping < 50 {
quality = "Отлично"
} else if ping < 100 {
quality = "Хорошо"
} else if ping < 200 {
quality = "Средне"
}
msg := fmt.Sprintf("✓ Сервер доступен\n\nПинг: %.2f мс\nКачество: %s", ping, quality)
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
json.NewEncoder(w).Encode(map[string]string{"message": "Конфиг не найден"})
}
func handleSubscriptionsList(w http.ResponseWriter, r *http.Request) {
subs, _ := config.LoadSubscriptions()
json.NewEncoder(w).Encode(subs.Subscriptions)
}
func handleSubscriptionsAdd(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
URL string `json:"url"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, err := config.LoadSubscriptions()
if err == nil {
subs.Subscriptions = append(subs.Subscriptions, config.Subscription{
Name: req.Name,
URL: req.URL,
})
config.SaveSubscriptions(subs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleSubscriptionsDelete(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
subs.Subscriptions = append(subs.Subscriptions[:req.Index], subs.Subscriptions[req.Index+1:]...)
config.SaveSubscriptions(subs)
}
json.NewEncoder(w).Encode(map[string]bool{"success": true})
}
func handleSubscriptionsUpdate(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
name := subs.Subscriptions[req.Index].Name
err := subscription.UpdateSubscription(name, config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Конфиги обновлены из подписки"
}(),
})
return
}
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "Подписка не найдена",
})
}
func handleSubscriptionsShow(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
subName := subs.Subscriptions[req.Index].Name
configs, _ := config.LoadConfigs()
var subConfigs []config.VLESSConfig
for _, cfg := range configs.VLESS {
if cfg.Subscription == subName {
subConfigs = append(subConfigs, cfg)
}
}
if len(subConfigs) == 0 {
json.NewEncoder(w).Encode(map[string]string{
"message": "Нет конфигов из этой подписки.\n\nСначала обновите конфиги из подписки.",
})
return
}
msg := fmt.Sprintf("Конфиги из '%s':\n\n", subName)
for i, cfg := range subConfigs {
protocol := cfg.Protocol
if protocol == "" {
protocol = "Unknown"
}
msg += fmt.Sprintf("%d. [%s] %s\n", i+1, protocol, cfg.Name)
}
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
json.NewEncoder(w).Encode(map[string]string{"message": "Подписка не найдена"})
}
func handleSubscriptionsTest(w http.ResponseWriter, r *http.Request) {
var req struct {
Index int `json:"index"`
}
json.NewDecoder(r.Body).Decode(&req)
subs, _ := config.LoadSubscriptions()
if req.Index >= 0 && req.Index < len(subs.Subscriptions) {
subName := subs.Subscriptions[req.Index].Name
configs, _ := config.LoadConfigs()
var subConfigs []config.VLESSConfig
for _, cfg := range configs.VLESS {
if cfg.Subscription == subName {
subConfigs = append(subConfigs, cfg)
}
}
if len(subConfigs) == 0 {
json.NewEncoder(w).Encode(map[string]string{"message": "Нет конфигов из этой подписки"})
return
}
// Тестирование (упрощенная версия - первые 10)
msg := fmt.Sprintf("Тестирование серверов из '%s'...\n\n", subName)
tested := 0
for i, cfg := range subConfigs {
if i >= 10 {
break
}
tested++
success, ping, _ := vless.PingServer(cfg.URL, 3*time.Second)
if success {
msg += fmt.Sprintf("✓ %s - %.2f мс\n", cfg.Name, ping)
} else {
msg += fmt.Sprintf("✗ %s - недоступен\n", cfg.Name)
}
}
if len(subConfigs) > 10 {
msg += fmt.Sprintf("\n(Показано %d из %d серверов)", tested, len(subConfigs))
}
json.NewEncoder(w).Encode(map[string]string{"message": msg})
return
}
json.NewEncoder(w).Encode(map[string]string{"message": "Подписка не найдена"})
}
func handleDisconnect(w http.ResponseWriter, r *http.Request) {
state, err := vpn.GetStatus()
if err != nil || !state.Connected {
json.NewEncoder(w).Encode(map[string]interface{}{
"success": false,
"message": "VPN не подключен",
})
return
}
err = vpn.Disconnect(config.LogsDir)
json.NewEncoder(w).Encode(map[string]interface{}{
"success": err == nil,
"message": func() string {
if err != nil {
return err.Error()
}
return "Отключено от VPN"
}(),
})
}