Add set_system_proxy option for windows

This commit is contained in:
世界 2022-07-14 14:04:57 +08:00
parent e7d557fd9e
commit 238afda9da
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
11 changed files with 203 additions and 47 deletions

View file

@ -0,0 +1,13 @@
//go:build !windows
package wininet
import "os"
func ClearSystemProxy() error {
return os.ErrInvalid
}
func SetSystemProxy(proxy string, bypass string) error {
return os.ErrInvalid
}

View file

@ -0,0 +1,109 @@
package wininet
import (
"os"
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var (
modwininet = windows.NewLazySystemDLL("wininet.dll")
procInternetSetOptionW = modwininet.NewProc("InternetSetOptionW")
)
const (
internetOptionPerConnectionOption = 75
internetOptionSettingsChanged = 39
internetOptionRefresh = 37
internetOptionProxySettingsChanged = 95
)
const (
internetPerConnFlags = 1
internetPerConnProxyServer = 2
internetPerConnProxyBypass = 3
internetPerConnAutoconfigUrl = 4
internetPerConnAutodiscoveryFlags = 5
internetPerConnAutoconfigSecondaryUrl = 6
internetPerConnAutoconfigReloadDelayMins = 7
internetPerConnAutoconfigLastDetectTime = 8
internetPerConnAutoconfigLastDetectUrl = 9
internetPerConnFlagsUi = 10
internetOptionProxyUsername = 43
internetOptionProxyPassword = 44
)
const (
proxyTypeDirect = 1
proxyTypeProxy = 2
proxyTypeAutoProxyUrl = 4
proxyTypeAutoDetect = 8
)
type internetPerConnOptionList struct {
dwSize uint32
pszConnection uintptr
dwOptionCount uint32
dwOptionError uint32
pOptions uintptr
}
type internetPerConnOption struct {
dwOption uint32
value [8]byte
}
func internetSetOption(option uintptr, lpBuffer uintptr, dwBufferSize uintptr) error {
r0, _, err := syscall.SyscallN(procInternetSetOptionW.Addr(), 0, option, lpBuffer, dwBufferSize)
if r0 != 1 {
return err
}
return nil
}
func setOptions(options ...internetPerConnOption) error {
var optionList internetPerConnOptionList
optionList.dwSize = uint32(unsafe.Sizeof(optionList))
optionList.dwOptionCount = uint32(len(options))
optionList.dwOptionError = 0
optionList.pOptions = uintptr(unsafe.Pointer(&options[0]))
err := internetSetOption(internetOptionPerConnectionOption, uintptr(unsafe.Pointer(&optionList)), uintptr(optionList.dwSize))
if err != nil {
return os.NewSyscallError("InternetSetOption(Direct)", err)
}
err = internetSetOption(internetOptionSettingsChanged, 0, 0)
if err != nil {
return os.NewSyscallError("InternetSetOption(SettingsChanged)", err)
}
err = internetSetOption(internetOptionProxySettingsChanged, 0, 0)
if err != nil {
return os.NewSyscallError("InternetSetOption(ProxySettingsChanged)", err)
}
err = internetSetOption(internetOptionRefresh, 0, 0)
if err != nil {
return os.NewSyscallError("InternetSetOption(Refresh)", err)
}
return nil
}
func ClearSystemProxy() error {
var flagsOption internetPerConnOption
flagsOption.dwOption = internetPerConnFlags
*((*uint32)(unsafe.Pointer(&flagsOption.value))) = proxyTypeDirect | proxyTypeAutoDetect
return setOptions(flagsOption)
}
func SetSystemProxy(proxy string, bypass string) error {
var flagsOption internetPerConnOption
flagsOption.dwOption = internetPerConnFlags
*((*uint32)(unsafe.Pointer(&flagsOption.value))) = proxyTypeProxy | proxyTypeDirect
var proxyOption internetPerConnOption
proxyOption.dwOption = internetPerConnProxyServer
*((*uintptr)(unsafe.Pointer(&proxyOption.value))) = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(proxy)))
var bypassOption internetPerConnOption
bypassOption.dwOption = internetPerConnProxyBypass
*((*uintptr)(unsafe.Pointer(&bypassOption.value))) = uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(bypass)))
return setOptions(flagsOption, proxyOption, bypassOption)
}

View file

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/wininet"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
@ -16,6 +17,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
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" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
@ -36,6 +38,10 @@ type myInboundAdapter struct {
packetHandler adapter.PacketHandler packetHandler adapter.PacketHandler
packetUpstream any packetUpstream any
// http mixed
setSystemProxy bool
// internal // internal
tcpListener *net.TCPListener tcpListener *net.TCPListener
@ -88,14 +94,24 @@ func (a *myInboundAdapter) Start() error {
go a.loopUDPOut() go a.loopUDPOut()
a.logger.Info("udp server started at ", udpConn.LocalAddr()) a.logger.Info("udp server started at ", udpConn.LocalAddr())
} }
if a.setSystemProxy {
err := wininet.SetSystemProxy(F.ToString("http://127.0.0.1:", M.SocksaddrFromNet(a.tcpListener.Addr()).Port), "local")
if err != nil {
return E.Cause(err, "set system proxy")
}
}
return nil return nil
} }
func (a *myInboundAdapter) Close() error { func (a *myInboundAdapter) Close() error {
return common.Close( var err error
if a.setSystemProxy {
err = wininet.ClearSystemProxy()
}
return E.Errors(err, common.Close(
common.PtrOrNil(a.tcpListener), common.PtrOrNil(a.tcpListener),
common.PtrOrNil(a.udpConn), common.PtrOrNil(a.udpConn),
) ))
} }
func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter {

View file

@ -21,16 +21,17 @@ type HTTP struct {
authenticator auth.Authenticator authenticator auth.Authenticator
} }
func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SimpleInboundOptions) *HTTP { func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *HTTP {
inbound := &HTTP{ inbound := &HTTP{
myInboundAdapter{ myInboundAdapter{
protocol: C.TypeHTTP, protocol: C.TypeHTTP,
network: []string{C.NetworkTCP}, network: []string{C.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: router,
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
setSystemProxy: options.SetSystemProxy,
}, },
auth.NewAuthenticator(options.Users), auth.NewAuthenticator(options.Users),
} }

View file

@ -27,16 +27,17 @@ type Mixed struct {
authenticator auth.Authenticator authenticator auth.Authenticator
} }
func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SimpleInboundOptions) *Mixed { func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *Mixed {
inbound := &Mixed{ inbound := &Mixed{
myInboundAdapter{ myInboundAdapter{
protocol: C.TypeMixed, protocol: C.TypeMixed,
network: []string{C.NetworkTCP}, network: []string{C.NetworkTCP},
ctx: ctx, ctx: ctx,
router: router, router: router,
logger: logger, logger: logger,
tag: tag, tag: tag,
listenOptions: options.ListenOptions, listenOptions: options.ListenOptions,
setSystemProxy: options.SetSystemProxy,
}, },
auth.NewAuthenticator(options.Users), auth.NewAuthenticator(options.Users),
} }

View file

@ -20,7 +20,7 @@ type Socks struct {
authenticator auth.Authenticator authenticator auth.Authenticator
} }
func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SimpleInboundOptions) *Socks { func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks {
inbound := &Socks{ inbound := &Socks{
myInboundAdapter{ myInboundAdapter{
protocol: C.TypeSocks, protocol: C.TypeSocks,

View file

@ -42,26 +42,30 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message
id, hasId = IDFromContext(ctx) id, hasId = IDFromContext(ctx)
} }
if hasId { if hasId {
var color aurora.Color if !f.DisableColors {
color = aurora.Color(uint8(id)) var color aurora.Color
color %= 215 color = aurora.Color(uint8(id))
row := uint(color / 36) color %= 215
column := uint(color % 36) row := uint(color / 36)
column := uint(color % 36)
var r, g, b float32 var r, g, b float32
r = float32(row * 51) r = float32(row * 51)
g = float32(column / 6 * 51) g = float32(column / 6 * 51)
b = float32((column % 6) * 51) b = float32((column % 6) * 51)
luma := 0.2126*r + 0.7152*g + 0.0722*b luma := 0.2126*r + 0.7152*g + 0.0722*b
if luma < 60 { if luma < 60 {
row = 5 - row row = 5 - row
column = 35 - column column = 35 - column
color = aurora.Color(row*36 + column) color = aurora.Color(row*36 + column)
}
color += 16
color = color << 16
color |= 1 << 14
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
} else {
message = F.ToString("[", id, "] ", message)
} }
color += 16
color = color << 16
color |= 1 << 14
message = F.ToString("[", aurora.Colorize(id, color).String(), "] ", message)
} }
switch { switch {
case f.DisableTimestamp: case f.DisableTimestamp:

View file

@ -13,9 +13,9 @@ type _Inbound struct {
Type string `json:"type"` Type string `json:"type"`
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
DirectOptions DirectInboundOptions `json:"-"` DirectOptions DirectInboundOptions `json:"-"`
SocksOptions SimpleInboundOptions `json:"-"` SocksOptions SocksInboundOptions `json:"-"`
HTTPOptions SimpleInboundOptions `json:"-"` HTTPOptions HTTPMixedInboundOptions `json:"-"`
MixedOptions SimpleInboundOptions `json:"-"` MixedOptions HTTPMixedInboundOptions `json:"-"`
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
TunOptions TunInboundOptions `json:"-"` TunOptions TunInboundOptions `json:"-"`
} }
@ -97,16 +97,28 @@ type ListenOptions struct {
InboundOptions InboundOptions
} }
type SimpleInboundOptions struct { type SocksInboundOptions struct {
ListenOptions ListenOptions
Users []auth.User `json:"users,omitempty"` Users []auth.User `json:"users,omitempty"`
} }
func (o SimpleInboundOptions) Equals(other SimpleInboundOptions) bool { func (o SocksInboundOptions) Equals(other SocksInboundOptions) bool {
return o.ListenOptions == other.ListenOptions && return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Users, other.Users) common.ComparableSliceEquals(o.Users, other.Users)
} }
type HTTPMixedInboundOptions struct {
ListenOptions
Users []auth.User `json:"users,omitempty"`
SetSystemProxy bool `json:"set_system_proxy,omitempty"`
}
func (o HTTPMixedInboundOptions) Equals(other HTTPMixedInboundOptions) bool {
return o.ListenOptions == other.ListenOptions &&
common.ComparableSliceEquals(o.Users, other.Users) &&
o.SetSystemProxy == other.SetSystemProxy
}
type DirectInboundOptions struct { type DirectInboundOptions struct {
ListenOptions ListenOptions
Network NetworkList `json:"network,omitempty"` Network NetworkList `json:"network,omitempty"`

View file

@ -5,7 +5,7 @@ go 1.18
require ( require (
github.com/docker/docker v20.10.17+incompatible github.com/docker/docker v20.10.17+incompatible
github.com/docker/go-connections v0.4.0 github.com/docker/go-connections v0.4.0
github.com/sagernet/sing v0.0.0-20220712060558-029ab1ce4f91 github.com/sagernet/sing v0.0.0-20220714062657-6685f65aac21
github.com/sagernet/sing-box v0.0.0 github.com/sagernet/sing-box v0.0.0
github.com/sirupsen/logrus v1.8.1 github.com/sirupsen/logrus v1.8.1
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0

View file

@ -52,8 +52,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sagernet/sing v0.0.0-20220712060558-029ab1ce4f91 h1:fYsRChEViZHDvrOLp7fbswYCH3txaVyAl1zB0cnSNlc= github.com/sagernet/sing v0.0.0-20220714062657-6685f65aac21 h1:PaX9VR0gVUagiRHwp9imiVZ9VW169WoEyToJg3UCKBI=
github.com/sagernet/sing v0.0.0-20220712060558-029ab1ce4f91/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= github.com/sagernet/sing v0.0.0-20220714062657-6685f65aac21/go.mod h1:wbwi++q4pI7qbFYMbteUOakZUBdc4NmL+OQ08C3hTqc=
github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZmXp6WpxzyB2xeyRIA1/L8EJKuNntfY= github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619 h1:oHbOmq1WS0XaZmXp6WpxzyB2xeyRIA1/L8EJKuNntfY=
github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk= github.com/sagernet/sing-dns v0.0.0-20220711062726-c64e938e4619/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk=
github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k=

View file

@ -93,7 +93,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
Inbounds: []option.Inbound{ Inbounds: []option.Inbound{
{ {
Type: C.TypeMixed, Type: C.TypeMixed,
MixedOptions: option.SimpleInboundOptions{ MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{ ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()), Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort, ListenPort: clientPort,
@ -131,7 +131,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
{ {
Type: C.TypeMixed, Type: C.TypeMixed,
Tag: "mixed-in", Tag: "mixed-in",
MixedOptions: option.SimpleInboundOptions{ MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{ ListenOptions: option.ListenOptions{
Listen: option.ListenAddress(netip.IPv4Unspecified()), Listen: option.ListenAddress(netip.IPv4Unspecified()),
ListenPort: clientPort, ListenPort: clientPort,