Improve system proxy API

This commit is contained in:
世界 2023-09-03 14:29:37 +08:00
parent 69499a51a5
commit 5d8af150a7
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
7 changed files with 295 additions and 162 deletions

View file

@ -1,43 +1,73 @@
package settings package settings
import ( import (
"context"
"os" "os"
"strings" "strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/shell"
) )
var ( type AndroidSystemProxy struct {
useRish bool useRish bool
rishPath string rishPath string
) serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func init() { func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*AndroidSystemProxy, error) {
userId := os.Getuid() userId := os.Getuid()
var (
useRish bool
rishPath string
)
if userId == 0 || userId == 1000 || userId == 2000 { if userId == 0 || userId == 1000 || userId == 2000 {
useRish = false useRish = false
} else { } else {
rishPath, useRish = C.FindPath("rish") rishPath, useRish = C.FindPath("rish")
}
}
func runAndroidShell(name string, args ...string) error {
if !useRish { if !useRish {
return shell.Exec(name, args...).Attach().Run() return nil, E.Cause(os.ErrPermission, "root or system (adb) permission is required for set system proxy")
} else {
return shell.Exec("sh", rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} }
}
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
err := runAndroidShell("settings", "put", "global", "http_proxy", F.ToString("127.0.0.1:", port))
if err != nil {
return nil, err
} }
return func() error { return &AndroidSystemProxy{
return runAndroidShell("settings", "put", "global", "http_proxy", ":0") useRish: useRish,
rishPath: rishPath,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil }, nil
} }
func (p *AndroidSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *AndroidSystemProxy) Enable() error {
err := p.runAndroidShell("settings", "put", "global", "http_proxy", p.serverAddr.String())
if err != nil {
return err
}
p.isEnabled = true
return nil
}
func (p *AndroidSystemProxy) Disable() error {
err := p.runAndroidShell("settings", "put", "global", "http_proxy", ":0")
if err != nil {
return err
}
p.isEnabled = false
return nil
}
func (p *AndroidSystemProxy) runAndroidShell(name string, args ...string) error {
if !p.useRish {
return shell.Exec(name, args...).Attach().Run()
} else {
return shell.Exec("sh", p.rishPath, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
}
}

View file

@ -1,56 +1,56 @@
package settings package settings
import ( import (
"context"
"net/netip" "net/netip"
"strconv"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/shell"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
) )
type systemProxy struct { type DarwinSystemProxy struct {
monitor tun.DefaultInterfaceMonitor monitor tun.DefaultInterfaceMonitor
interfaceName string interfaceName string
element *list.Element[tun.DefaultInterfaceUpdateCallback] element *list.Element[tun.DefaultInterfaceUpdateCallback]
port uint16 serverAddr M.Socksaddr
isMixed bool supportSOCKS bool
isEnabled bool
} }
func (p *systemProxy) update(event int) { func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*DarwinSystemProxy, error) {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified()) interfaceMonitor := adapter.RouterFromContext(ctx).InterfaceMonitor()
if p.interfaceName == newInterfaceName { if interfaceMonitor == nil {
return return nil, E.New("missing interface monitor")
} }
if p.interfaceName != "" { proxy := &DarwinSystemProxy{
_ = p.unset() monitor: interfaceMonitor,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
} }
p.interfaceName = newInterfaceName proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) return proxy, nil
if err != nil {
return
}
if p.isMixed {
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
if err == nil {
_ = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, "127.0.0.1", F.ToString(p.port)).Attach().Run()
}
return
} }
func (p *systemProxy) unset() error { func (p *DarwinSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *DarwinSystemProxy) Enable() error {
return p.update0()
}
func (p *DarwinSystemProxy) Disable() error {
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName) interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil { if err != nil {
return err return err
} }
if p.isMixed { if p.supportSOCKS {
err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run() err = shell.Exec("networksetup", "-setsocksfirewallproxystate", interfaceDisplayName, "off").Attach().Run()
} }
if err == nil { if err == nil {
@ -59,9 +59,53 @@ func (p *systemProxy) unset() error {
if err == nil { if err == nil {
err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run() err = shell.Exec("networksetup", "-setsecurewebproxystate", interfaceDisplayName, "off").Attach().Run()
} }
if err == nil {
p.isEnabled = false
}
return err return err
} }
func (p *DarwinSystemProxy) update(event int) {
if event&tun.EventInterfaceUpdate == 0 {
return
}
if !p.isEnabled {
return
}
_ = p.update0()
}
func (p *DarwinSystemProxy) update0() error {
newInterfaceName := p.monitor.DefaultInterfaceName(netip.IPv4Unspecified())
if p.interfaceName == newInterfaceName {
return nil
}
if p.interfaceName != "" {
_ = p.Disable()
}
p.interfaceName = newInterfaceName
interfaceDisplayName, err := getInterfaceDisplayName(p.interfaceName)
if err != nil {
return err
}
if p.supportSOCKS {
err = shell.Exec("networksetup", "-setsocksfirewallproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
}
if err != nil {
return err
}
err = shell.Exec("networksetup", "-setwebproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
if err != nil {
return err
}
err = shell.Exec("networksetup", "-setsecurewebproxy", interfaceDisplayName, p.serverAddr.AddrString(), strconv.Itoa(int(p.serverAddr.Port))).Attach().Run()
if err != nil {
return err
}
p.isEnabled = true
return nil
}
func getInterfaceDisplayName(name string) (string, error) { func getInterfaceDisplayName(name string) (string, error) {
content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput() content, err := shell.Exec("networksetup", "-listallhardwareports").ReadOutput()
if err != nil { if err != nil {
@ -77,21 +121,3 @@ func getInterfaceDisplayName(name string) (string, error) {
} }
return "", E.New(name, " not found in networksetup -listallhardwareports") return "", E.New(name, " not found in networksetup -listallhardwareports")
} }
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) {
interfaceMonitor := router.InterfaceMonitor()
if interfaceMonitor == nil {
return nil, E.New("missing interface monitor")
}
proxy := &systemProxy{
monitor: interfaceMonitor,
port: port,
isMixed: isMixed,
}
proxy.update(tun.EventInterfaceUpdate)
proxy.element = interfaceMonitor.RegisterCallback(proxy.update)
return func() error {
interfaceMonitor.UnregisterCallback(proxy.element)
return proxy.unset()
}, nil
}

View file

@ -3,106 +3,137 @@
package settings package settings
import ( import (
"context"
"os" "os"
"os/exec" "os/exec"
"strings" "strings"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/shell" "github.com/sagernet/sing/common/shell"
) )
var ( type LinuxSystemProxy struct {
hasGSettings bool hasGSettings bool
isKDE5 bool hasKWriteConfig5 bool
sudoUser string sudoUser string
) serverAddr M.Socksaddr
supportSOCKS bool
isEnabled bool
}
func init() { func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
isKDE5 = common.Error(exec.LookPath("kwriteconfig5")) == nil hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
hasGSettings = common.Error(exec.LookPath("gsettings")) == nil hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
var sudoUser string
if os.Getuid() == 0 { if os.Getuid() == 0 {
sudoUser = os.Getenv("SUDO_USER") sudoUser = os.Getenv("SUDO_USER")
} }
if !hasGSettings && !hasKWriteConfig5 {
return nil, E.New("unsupported desktop environment")
}
return &LinuxSystemProxy{
hasGSettings: hasGSettings,
hasKWriteConfig5: hasKWriteConfig5,
sudoUser: sudoUser,
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil
} }
func runAsUser(name string, args ...string) error { func (p *LinuxSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *LinuxSystemProxy) Enable() error {
if p.hasGSettings {
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
if err != nil {
return err
}
if p.supportSOCKS {
err = p.setGnomeProxy("ftp", "http", "https", "socks")
} else {
err = p.setGnomeProxy("http", "https")
}
if err != nil {
return err
}
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(p.supportSOCKS))
if err != nil {
return err
}
err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
if err != nil {
return err
}
}
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "1")
if err != nil {
return err
}
if p.supportSOCKS {
err = p.setKDEProxy("ftp", "http", "https", "socks")
} else {
err = p.setKDEProxy("http", "https")
}
if err != nil {
return err
}
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "Authmode", "0")
if err != nil {
return err
}
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return err
}
}
p.isEnabled = true
return nil
}
func (p *LinuxSystemProxy) Disable() error {
if p.hasGSettings {
err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
if err != nil {
return err
}
}
if p.hasKWriteConfig5 {
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "0")
if err != nil {
return err
}
err = p.runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return err
}
}
p.isEnabled = false
return nil
}
func (p *LinuxSystemProxy) runAsUser(name string, args ...string) error {
if os.Getuid() != 0 { if os.Getuid() != 0 {
return shell.Exec(name, args...).Attach().Run() return shell.Exec(name, args...).Attach().Run()
} else if sudoUser != "" { } else if p.sudoUser != "" {
return shell.Exec("su", "-", sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run() return shell.Exec("su", "-", p.sudoUser, "-c", F.ToString(name, " ", strings.Join(args, " "))).Attach().Run()
} else { } else {
return E.New("set system proxy: unable to set as root") return E.New("set system proxy: unable to set as root")
} }
} }
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) { func (p *LinuxSystemProxy) setGnomeProxy(proxyTypes ...string) error {
if hasGSettings {
err := runAsUser("gsettings", "set", "org.gnome.system.proxy.http", "enabled", "true")
if err != nil {
return nil, err
}
if isMixed {
err = setGnomeProxy(port, "ftp", "http", "https", "socks")
} else {
err = setGnomeProxy(port, "http", "https")
}
if err != nil {
return nil, err
}
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "use-same-proxy", F.ToString(isMixed))
if err != nil {
return nil, err
}
err = runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "manual")
if err != nil {
return nil, err
}
return func() error {
return runAsUser("gsettings", "set", "org.gnome.system.proxy", "mode", "none")
}, nil
}
if isKDE5 {
err := runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "1")
if err != nil {
return nil, err
}
if isMixed {
err = setKDEProxy(port, "ftp", "http", "https", "socks")
} else {
err = setKDEProxy(port, "http", "https")
}
if err != nil {
return nil, err
}
err = runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "Authmode", "0")
if err != nil {
return nil, err
}
err = runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
if err != nil {
return nil, err
}
return func() error {
err = runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "'Proxy Settings'", "--key", "ProxyType", "0")
if err != nil {
return err
}
return runAsUser("dbus-send", "--type=signal", "/KIO/Scheduler", "org.kde.KIO.Scheduler.reparseSlaveConfiguration", "string:''")
}, nil
}
return nil, E.New("unsupported desktop environment")
}
func setGnomeProxy(port uint16, proxyTypes ...string) error {
for _, proxyType := range proxyTypes { for _, proxyType := range proxyTypes {
err := runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", "127.0.0.1") err := p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "host", p.serverAddr.AddrString())
if err != nil { if err != nil {
return err return err
} }
err = runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(port)) err = p.runAsUser("gsettings", "set", "org.gnome.system.proxy."+proxyType, "port", F.ToString(p.serverAddr.Port))
if err != nil { if err != nil {
return err return err
} }
@ -110,20 +141,20 @@ func setGnomeProxy(port uint16, proxyTypes ...string) error {
return nil return nil
} }
func setKDEProxy(port uint16, proxyTypes ...string) error { func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
for _, proxyType := range proxyTypes { for _, proxyType := range proxyTypes {
var proxyUrl string var proxyUrl string
if proxyType == "socks" { if proxyType == "socks" {
proxyUrl = "socks://127.0.0.1:" + F.ToString(port) proxyUrl = "socks://" + p.serverAddr.String()
} else { } else {
proxyUrl = "http://127.0.0.1:" + F.ToString(port) proxyUrl = "http://" + p.serverAddr.String()
} }
err := runAsUser( err := p.runAsUser(
"kwriteconfig5", "kwriteconfig5",
"--file", "--file",
"kioslaverc", "kioslaverc",
"--group", "--group",
"'Proxy Settings'", "Proxy Settings",
"--key", proxyType+"Proxy", "--key", proxyType+"Proxy",
proxyUrl, proxyUrl,
) )

View file

@ -3,11 +3,12 @@
package settings package settings
import ( import (
"context"
"os" "os"
"github.com/sagernet/sing-box/adapter" M "github.com/sagernet/sing/common/metadata"
) )
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) { func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (SystemProxy, error) {
return nil, os.ErrInvalid return nil, os.ErrInvalid
} }

View file

@ -1,17 +1,43 @@
package settings package settings
import ( import (
"github.com/sagernet/sing-box/adapter" "context"
F "github.com/sagernet/sing/common/format"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/common/wininet" "github.com/sagernet/sing/common/wininet"
) )
func SetSystemProxy(router adapter.Router, port uint16, isMixed bool) (func() error, error) { type WindowsSystemProxy struct {
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", port), "") serverAddr M.Socksaddr
if err != nil { supportSOCKS bool
return nil, err isEnabled bool
} }
return func() error {
return wininet.ClearSystemProxy() func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*WindowsSystemProxy, error) {
return &WindowsSystemProxy{
serverAddr: serverAddr,
supportSOCKS: supportSOCKS,
}, nil }, nil
} }
func (p *WindowsSystemProxy) IsEnabled() bool {
return p.isEnabled
}
func (p *WindowsSystemProxy) Enable() error {
err := wininet.SetSystemProxy("http://"+p.serverAddr.String(), "")
if err != nil {
return err
}
p.isEnabled = true
return nil
}
func (p *WindowsSystemProxy) Disable() error {
err := wininet.ClearSystemProxy()
if err != nil {
return err
}
p.isEnabled = false
return nil
}

View file

@ -0,0 +1,7 @@
package settings
type SystemProxy interface {
IsEnabled() bool
Enable() error
Disable() error
}

View file

@ -34,7 +34,7 @@ type myInboundAdapter struct {
// http mixed // http mixed
setSystemProxy bool setSystemProxy bool
clearSystemProxy func() error systemProxy settings.SystemProxy
// internal // internal
@ -91,7 +91,19 @@ func (a *myInboundAdapter) Start() error {
} }
} }
if a.setSystemProxy { if a.setSystemProxy {
a.clearSystemProxy, err = settings.SetSystemProxy(a.router, M.SocksaddrFromNet(a.tcpListener.Addr()).Port, a.protocol == C.TypeMixed) listenPort := M.SocksaddrFromNet(a.tcpListener.Addr()).Port
var listenAddrString string
listenAddr := a.listenOptions.Listen.Build()
if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1"
} else {
listenAddrString = listenAddr.String()
}
a.systemProxy, err = settings.NewSystemProxy(a.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), a.protocol == C.TypeMixed)
if err != nil {
return E.Cause(err, "initialize system proxy")
}
err = a.systemProxy.Enable()
if err != nil { if err != nil {
return E.Cause(err, "set system proxy") return E.Cause(err, "set system proxy")
} }
@ -102,8 +114,8 @@ func (a *myInboundAdapter) Start() error {
func (a *myInboundAdapter) Close() error { func (a *myInboundAdapter) Close() error {
a.inShutdown.Store(true) a.inShutdown.Store(true)
var err error var err error
if a.clearSystemProxy != nil { if a.systemProxy != nil && a.systemProxy.IsEnabled() {
err = a.clearSystemProxy() err = a.systemProxy.Disable()
} }
return E.Errors(err, common.Close( return E.Errors(err, common.Close(
a.tcpListener, a.tcpListener,