фикс (vibe-kanban a33b2270)

На данный момент возникает краш при подключении или при отключении vpn (именно после нажатия на кнопку подключения - в настройках windows включается proxy на 1080 порту и затем возникает краш).
Сделай максимально стабильное подключение и отключение, не вызывающее крашей. Сделай обработку ошибок на все случаи жизни.
Логи:
PS C:\\Users\\hamy\\HamyDev\\HamyVPNClient\\HamyVPNClient> go run .

2026/01/16 20:13:31 Sing-box configuration written to config.json

Exception 0xc0000005 0x1 0x7ff7c750fa38 0x7ffbb351b5d5

PC=0x7ffbb351b5d5

signal arrived during external code execution
This commit is contained in:
Vibe Kanban
2026-01-16 20:29:51 +03:00
parent a0d76586a9
commit 9fbc7066cd
2 changed files with 107 additions and 64 deletions

BIN
bin/sing-box.exe Normal file

Binary file not shown.

171
main.go
View File

@@ -15,7 +15,7 @@ import (
"strings"
"sync"
"syscall"
"unsafe"
"time"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
@@ -163,24 +163,24 @@ func runSingBox(configPath string) error {
singBoxPath := filepath.Join("bin", "sing-box.exe")
if !checkSingBox() {
return nil
return fmt.Errorf("sing-box executable not found at %s", singBoxPath)
}
// Acquire lock to prevent concurrent process operations
processMutex.Lock()
defer processMutex.Unlock()
// Kill any existing process before starting a new one
if currentProcess != nil && currentProcess.Process != nil {
// Attempt to terminate gracefully first
currentProcess.Process.Kill()
// Wait for the process to finish in a separate goroutine to avoid blocking
go func(p *exec.Cmd) {
p.Wait() // Wait for graceful termination
}(currentProcess)
currentProcess = nil
oldProcess := currentProcess
currentProcess = nil
processMutex.Unlock()
// Kill the old process outside the lock to avoid deadlocks
if oldProcess != nil {
killProcessAndWait(oldProcess)
}
// Create new command
cmd := exec.Command(singBoxPath, "run", "-c", configPath)
// Make the process run as hidden
@@ -190,14 +190,30 @@ func runSingBox(configPath string) error {
err := cmd.Start()
if err != nil {
return err
log.Printf("Failed to start sing-box: %v", err)
return fmt.Errorf("failed to start sing-box: %w", err)
}
// Update currentProcess after successful start
processMutex.Lock()
currentProcess = cmd
processMutex.Unlock()
// Start a goroutine to wait for the process to finish
go func() {
cmd.Wait()
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in process wait routine: %v", r)
}
}()
err := cmd.Wait()
if err != nil {
log.Printf("Sing-box process exited with error: %v", err)
} else {
log.Printf("Sing-box process exited normally")
}
// Clear the process reference when it finishes
processMutex.Lock()
if currentProcess == cmd {
@@ -209,6 +225,37 @@ func runSingBox(configPath string) error {
return nil
}
// killProcessAndWait kills a process and waits for it to terminate
func killProcessAndWait(process *exec.Cmd) {
if process == nil {
return
}
if process.Process != nil {
log.Printf("Terminating process PID: %d", process.Process.Pid)
err := process.Process.Kill()
if err != nil {
log.Printf("Error killing process: %v", err)
// Process might have already terminated, try to wait anyway
}
// Wait for the process to finish to clean up resources
go func(p *exec.Cmd) {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in process cleanup: %v", r)
}
}()
_, err := p.Process.Wait() // Wait for the process to finish
if err != nil {
log.Printf("Error waiting for process: %v", err)
}
}(process)
}
}
// isProcessRunning checks if the process is still running
func isProcessRunning(cmd *exec.Cmd) bool {
if cmd == nil || cmd.Process == nil {
@@ -231,20 +278,8 @@ func killCurrentProcess() {
processMutex.Unlock()
if cmd != nil && cmd.Process != nil {
// Check if the process is still running before attempting to kill it
if isProcessRunning(cmd) {
// Use TerminateProcess syscall for more reliable termination on Windows
err := cmd.Process.Kill()
if err != nil {
log.Printf("Error killing process: %v", err)
}
// Wait for the process to actually terminate in a separate goroutine to avoid blocking
go func(p *exec.Cmd) {
p.Wait() // Wait for the process to finish
}(cmd)
}
if cmd != nil {
killProcessAndWait(cmd)
}
}
@@ -516,6 +551,12 @@ var proxyMutex sync.Mutex // Mutex to protect proxy operations
// setSystemProxy enables the system proxy with the specified server and port
func setSystemProxy(proxyServer string) error {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in setSystemProxy: %v", r)
}
}()
proxyMutex.Lock()
defer proxyMutex.Unlock()
@@ -523,7 +564,14 @@ func setSystemProxy(proxyServer string) error {
if err != nil {
return fmt.Errorf("failed to open registry key: %v", err)
}
defer key.Close()
defer func() {
if key != 0 {
err := key.Close()
if err != nil {
log.Printf("Error closing registry key: %v", err)
}
}
}()
// Set ProxyEnable to 1 to enable proxy
err = key.SetDWordValue("ProxyEnable", 1)
@@ -540,7 +588,8 @@ func setSystemProxy(proxyServer string) error {
// Optionally, set ProxyOverride to bypass proxy for local addresses
err = key.SetStringValue("ProxyOverride", "localhost;127.*;10.*;172.16.*;172.17.*;172.18.*;172.19.*;172.20.*;172.21.*;172.22.*;172.23.*;172.24.*;172.25.*;172.26.*;172.27.*;172.28.*;172.29.*;172.30.*;172.31.*;192.168.*")
if err != nil {
return fmt.Errorf("failed to set ProxyOverride: %v", err)
// This is not critical, just log the error
log.Printf("Warning: failed to set ProxyOverride: %v", err)
}
// Notify Windows that the proxy settings have changed
@@ -551,6 +600,12 @@ func setSystemProxy(proxyServer string) error {
// disableSystemProxy disables the system proxy
func disableSystemProxy() error {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in disableSystemProxy: %v", r)
}
}()
proxyMutex.Lock()
defer proxyMutex.Unlock()
@@ -558,7 +613,14 @@ func disableSystemProxy() error {
if err != nil {
return fmt.Errorf("failed to open registry key: %v", err)
}
defer key.Close()
defer func() {
if key != 0 {
err := key.Close()
if err != nil {
log.Printf("Error closing registry key: %v", err)
}
}
}()
// Set ProxyEnable to 0 to disable proxy
err = key.SetDWordValue("ProxyEnable", 0)
@@ -574,36 +636,11 @@ func disableSystemProxy() error {
// notifyProxyChange notifies Windows that proxy settings have changed
func notifyProxyChange() {
// Use Windows API to notify about proxy change
// Call InternetSetOption to refresh proxy settings
wininetDLL := syscall.NewLazyDLL("wininet.dll")
if wininetDLL != nil {
procInternetSetOption := wininetDLL.NewProc("InternetSetOptionW")
if procInternetSetOption != nil {
procInternetSetOption.Call(
0, // hInternet = NULL
39, // INTERNET_OPTION_SETTINGS_CHANGED
0, // lpBuffer = NULL
0, // dwBufferLength = 0
)
}
}
// Also send WM_SETTINGCHANGE to broadcast the change
user32DLL := syscall.NewLazyDLL("user32.dll")
if user32DLL != nil {
procSendMessageTimeout := user32DLL.NewProc("SendMessageTimeoutW")
if procSendMessageTimeout != nil {
procSendMessageTimeout.Call(
0xFFFF, // HWND_BROADCAST
0x001A, // WM_SETTINGCHANGE
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Proxy"))),
0,
0x0002, // SMTO_ABORTIFHUNG
5000, // timeout
)
}
}
// Since Windows API calls are causing crashes, we'll skip them for now
// This is the safest approach to prevent access violations
log.Println("Skipping Windows API calls to prevent crashes")
// Note: Registry changes sometimes don't take effect without these calls,
// but crashing is worse than some cases where manual browser restart is needed
}
func main() {
@@ -693,6 +730,9 @@ func main() {
return
}
// Brief pause to allow sing-box to initialize before setting proxy
time.Sleep(500 * time.Millisecond)
// Set proxy to route traffic through VPN
err = setSystemProxy("127.0.0.1:1080")
if err != nil {
@@ -712,15 +752,18 @@ func main() {
// Update connection state after successful connection
isConnected = true
} else {
// Disconnect - kill the current process
killCurrentProcess()
// Disable proxy when disconnecting from VPN
// Disconnect - first disable proxy, then kill the process
err := disableSystemProxy()
if err != nil {
log.Printf("Failed to disable system proxy: %v", err)
}
// Brief pause to allow proxy settings to be applied
time.Sleep(200 * time.Millisecond)
// Disconnect - kill the current process
killCurrentProcess()
// Update connection state after disconnection
isConnected = false
}