diff --git a/common/wininet/wininet_stub.go b/common/wininet/wininet_stub.go new file mode 100644 index 00000000..a624c023 --- /dev/null +++ b/common/wininet/wininet_stub.go @@ -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 +} diff --git a/common/wininet/wininet_windows.go b/common/wininet/wininet_windows.go new file mode 100644 index 00000000..2d32f6d6 --- /dev/null +++ b/common/wininet/wininet_windows.go @@ -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) +} diff --git a/inbound/default.go b/inbound/default.go index 1879d60e..094036bc 100644 --- a/inbound/default.go +++ b/inbound/default.go @@ -9,6 +9,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/wininet" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -16,6 +17,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" + F "github.com/sagernet/sing/common/format" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" @@ -36,6 +38,10 @@ type myInboundAdapter struct { packetHandler adapter.PacketHandler packetUpstream any + // http mixed + + setSystemProxy bool + // internal tcpListener *net.TCPListener @@ -88,14 +94,24 @@ func (a *myInboundAdapter) Start() error { go a.loopUDPOut() 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 } 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.udpConn), - ) + )) } func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { diff --git a/inbound/http.go b/inbound/http.go index dd263dd3..48650aa4 100644 --- a/inbound/http.go +++ b/inbound/http.go @@ -21,16 +21,17 @@ type HTTP struct { 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{ myInboundAdapter{ - protocol: C.TypeHTTP, - network: []string{C.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, + protocol: C.TypeHTTP, + network: []string{C.NetworkTCP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + setSystemProxy: options.SetSystemProxy, }, auth.NewAuthenticator(options.Users), } diff --git a/inbound/mixed.go b/inbound/mixed.go index 27baab16..7f172c03 100644 --- a/inbound/mixed.go +++ b/inbound/mixed.go @@ -27,16 +27,17 @@ type Mixed struct { 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{ myInboundAdapter{ - protocol: C.TypeMixed, - network: []string{C.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, + protocol: C.TypeMixed, + network: []string{C.NetworkTCP}, + ctx: ctx, + router: router, + logger: logger, + tag: tag, + listenOptions: options.ListenOptions, + setSystemProxy: options.SetSystemProxy, }, auth.NewAuthenticator(options.Users), } diff --git a/inbound/socks.go b/inbound/socks.go index 91c12f01..b837efb9 100644 --- a/inbound/socks.go +++ b/inbound/socks.go @@ -20,7 +20,7 @@ type Socks struct { 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{ myInboundAdapter{ protocol: C.TypeSocks, diff --git a/log/format.go b/log/format.go index 583be463..7df870a7 100644 --- a/log/format.go +++ b/log/format.go @@ -42,26 +42,30 @@ func (f Formatter) Format(ctx context.Context, level Level, tag string, message id, hasId = IDFromContext(ctx) } if hasId { - var color aurora.Color - color = aurora.Color(uint8(id)) - color %= 215 - row := uint(color / 36) - column := uint(color % 36) + if !f.DisableColors { + var color aurora.Color + color = aurora.Color(uint8(id)) + color %= 215 + row := uint(color / 36) + column := uint(color % 36) - var r, g, b float32 - r = float32(row * 51) - g = float32(column / 6 * 51) - b = float32((column % 6) * 51) - luma := 0.2126*r + 0.7152*g + 0.0722*b - if luma < 60 { - row = 5 - row - column = 35 - column - color = aurora.Color(row*36 + column) + var r, g, b float32 + r = float32(row * 51) + g = float32(column / 6 * 51) + b = float32((column % 6) * 51) + luma := 0.2126*r + 0.7152*g + 0.0722*b + if luma < 60 { + row = 5 - row + column = 35 - 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 { case f.DisableTimestamp: diff --git a/option/inbound.go b/option/inbound.go index 0f685940..f1c3f713 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -13,9 +13,9 @@ type _Inbound struct { Type string `json:"type"` Tag string `json:"tag,omitempty"` DirectOptions DirectInboundOptions `json:"-"` - SocksOptions SimpleInboundOptions `json:"-"` - HTTPOptions SimpleInboundOptions `json:"-"` - MixedOptions SimpleInboundOptions `json:"-"` + SocksOptions SocksInboundOptions `json:"-"` + HTTPOptions HTTPMixedInboundOptions `json:"-"` + MixedOptions HTTPMixedInboundOptions `json:"-"` ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` TunOptions TunInboundOptions `json:"-"` } @@ -97,16 +97,28 @@ type ListenOptions struct { InboundOptions } -type SimpleInboundOptions struct { +type SocksInboundOptions struct { ListenOptions 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 && 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 { ListenOptions Network NetworkList `json:"network,omitempty"` diff --git a/test/go.mod b/test/go.mod index b402bf17..c9c1d758 100644 --- a/test/go.mod +++ b/test/go.mod @@ -5,7 +5,7 @@ go 1.18 require ( github.com/docker/docker v20.10.17+incompatible 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/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.8.0 diff --git a/test/go.sum b/test/go.sum index 21a1a98f..fe836c61 100644 --- a/test/go.sum +++ b/test/go.sum @@ -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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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-20220712060558-029ab1ce4f91/go.mod h1:3ZmoGNg/nNJTyHAZFNRSPaXpNIwpDvyIiAUd0KIWV5c= +github.com/sagernet/sing v0.0.0-20220714062657-6685f65aac21 h1:PaX9VR0gVUagiRHwp9imiVZ9VW169WoEyToJg3UCKBI= +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/go.mod h1:y2fpvoxukw3G7eApIZwkcpcG/NE4AB8pCQI0Qd8rMqk= github.com/sagernet/sing-shadowsocks v0.0.0-20220701084835-2208da1d8649 h1:whNDUGOAX5GPZkSy4G3Gv9QyIgk5SXRyjkRuP7ohF8k= diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index ce8bbc9c..5fcbfe69 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -93,7 +93,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Inbounds: []option.Inbound{ { Type: C.TypeMixed, - MixedOptions: option.SimpleInboundOptions{ + MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ Listen: option.ListenAddress(netip.IPv4Unspecified()), ListenPort: clientPort, @@ -131,7 +131,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { { Type: C.TypeMixed, Tag: "mixed-in", - MixedOptions: option.SimpleInboundOptions{ + MixedOptions: option.HTTPMixedInboundOptions{ ListenOptions: option.ListenOptions{ Listen: option.ListenAddress(netip.IPv4Unspecified()), ListenPort: clientPort,