Files

168 lines
3.7 KiB
Go

//go:build windows
package sysproxy
import (
"fmt"
"strings"
"sync"
"golang.org/x/sys/windows"
"golang.org/x/sys/windows/registry"
)
const (
internetOptionSettingsChanged = 39
internetOptionRefresh = 37
)
type windowsController struct {
mu sync.Mutex
enabled bool
hadProxy bool
oldEnable uint64
oldServer string
oldOverride string
}
func newPlatform() Controller {
return &windowsController{}
}
func (c *windowsController) Enabled() bool {
c.mu.Lock()
defer c.mu.Unlock()
return c.enabled
}
func (c *windowsController) Enable(httpHostPort string) error {
c.mu.Lock()
defer c.mu.Unlock()
if httpHostPort == "" {
return fmt.Errorf("sysproxy: empty http proxy address")
}
key, err := registry.OpenKey(registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
return fmt.Errorf("sysproxy: open registry: %w", err)
}
defer key.Close()
if !c.enabled {
c.oldEnable, _, _ = key.GetIntegerValue("ProxyEnable")
c.oldServer, _, _ = key.GetStringValue("ProxyServer")
c.oldOverride, _, _ = key.GetStringValue("ProxyOverride")
c.hadProxy = true
}
override := strings.Join([]string{
"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.*",
"<local>",
}, ";")
if err := key.SetDWordValue("ProxyEnable", 1); err != nil {
return err
}
if err := key.SetStringValue("ProxyServer", httpHostPort); err != nil {
return err
}
if err := key.SetStringValue("ProxyOverride", override); err != nil {
return err
}
if err := notifyInternetSettingsChanged(); err != nil {
return err
}
// Skip netsh winhttp — spawning netsh is a common AV heuristic; WinINET covers browsers.
c.enabled = true
return nil
}
func (c *windowsController) Disable() error {
c.mu.Lock()
defer c.mu.Unlock()
if !c.enabled {
return nil
}
return c.disableLocked(true)
}
func (c *windowsController) ForceDisable() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.disableLocked(false)
}
func (c *windowsController) disableLocked(restoreSaved bool) error {
key, err := registry.OpenKey(registry.CURRENT_USER,
`Software\Microsoft\Windows\CurrentVersion\Internet Settings`,
registry.QUERY_VALUE|registry.SET_VALUE)
if err != nil {
return err
}
defer key.Close()
if restoreSaved && c.hadProxy {
_ = key.SetDWordValue("ProxyEnable", uint32(c.oldEnable))
if c.oldServer != "" {
_ = key.SetStringValue("ProxyServer", c.oldServer)
} else {
_ = key.DeleteValue("ProxyServer")
}
if c.oldOverride != "" {
_ = key.SetStringValue("ProxyOverride", c.oldOverride)
}
} else {
// Crash recovery: if proxy still points at local Navis ports, clear it.
server, _, _ := key.GetStringValue("ProxyServer")
lower := strings.ToLower(server)
if strings.Contains(lower, "127.0.0.1") || strings.Contains(lower, "localhost") {
_ = key.SetDWordValue("ProxyEnable", 0)
_ = key.DeleteValue("ProxyServer")
} else if !restoreSaved {
_ = key.SetDWordValue("ProxyEnable", 0)
}
}
_ = notifyInternetSettingsChanged()
c.enabled = false
c.hadProxy = false
return nil
}
func notifyInternetSettingsChanged() error {
wininet := windows.NewLazySystemDLL("wininet.dll")
proc := wininet.NewProc("InternetSetOptionW")
r1, _, err := proc.Call(0, uintptr(internetOptionSettingsChanged), 0, 0)
if r1 == 0 {
return fmt.Errorf("InternetSetOption(SETTINGS_CHANGED): %w", err)
}
r1, _, err = proc.Call(0, uintptr(internetOptionRefresh), 0, 0)
if r1 == 0 {
return fmt.Errorf("InternetSetOption(REFRESH): %w", err)
}
return nil
}