//go:build darwin package sysproxy import ( "bufio" "bytes" "fmt" "net" "os/exec" "strings" "sync" ) type darwinController struct { mu sync.Mutex enabled bool host string httpPort string socksPort string services []string } func newPlatform() Controller { return &darwinController{} } func (c *darwinController) Enable(httpHostPort string) error { c.mu.Lock() defer c.mu.Unlock() host, port, err := net.SplitHostPort(httpHostPort) if err != nil { return fmt.Errorf("sysproxy: bad address %q: %w", httpHostPort, err) } if host == "" || host == "0.0.0.0" { host = "127.0.0.1" } socks := "1080" if port == "1081" { socks = "1080" } services, err := listNetworkServices() if err != nil { return err } if len(services) == 0 { return fmt.Errorf("sysproxy: no network services") } for _, svc := range services { if err := runNetworkSetup("-setwebproxy", svc, host, port); err != nil { return err } if err := runNetworkSetup("-setsecurewebproxy", svc, host, port); err != nil { return err } if err := runNetworkSetup("-setsocksfirewallproxy", svc, host, socks); err != nil { return err } if err := runNetworkSetup("-setwebproxystate", svc, "on"); err != nil { return err } if err := runNetworkSetup("-setsecurewebproxystate", svc, "on"); err != nil { return err } if err := runNetworkSetup("-setsocksfirewallproxystate", svc, "on"); err != nil { return err } } c.enabled = true c.host = host c.httpPort = port c.socksPort = socks c.services = services return nil } func (c *darwinController) Disable() error { c.mu.Lock() defer c.mu.Unlock() if !c.enabled { return nil } services := c.services if len(services) == 0 { var err error services, err = listNetworkServices() if err != nil { return err } } var first error for _, svc := range services { for _, args := range [][]string{ {"-setwebproxystate", svc, "off"}, {"-setsecurewebproxystate", svc, "off"}, {"-setsocksfirewallproxystate", svc, "off"}, } { if err := runNetworkSetup(args...); err != nil && first == nil { first = err } } } c.enabled = false c.services = nil return first } func (c *darwinController) Enabled() bool { c.mu.Lock() defer c.mu.Unlock() return c.enabled } func listNetworkServices() ([]string, error) { out, err := exec.Command("networksetup", "-listallnetworkservices").Output() if err != nil { return nil, fmt.Errorf("networksetup: %w", err) } var services []string sc := bufio.NewScanner(bytes.NewReader(out)) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "An asterisk") { continue } if strings.HasPrefix(line, "*") { continue // disabled service } services = append(services, line) } return services, sc.Err() } func runNetworkSetup(args ...string) error { cmd := exec.Command("networksetup", args...) out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("networksetup %v: %w (%s)", args, err, strings.TrimSpace(string(out))) } return nil }