diff --git a/adapter/inbound.go b/adapter/inbound.go index 300f57e3..ca3e9e59 100644 --- a/adapter/inbound.go +++ b/adapter/inbound.go @@ -5,6 +5,7 @@ import ( "net/netip" "github.com/sagernet/sing-box/common/process" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" M "github.com/sagernet/sing/common/metadata" ) @@ -25,6 +26,11 @@ type UDPInjectableInbound interface { PacketConnectionHandlerEx } +type InboundRegistry interface { + option.InboundOptionsRegistry + CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error) +} + type InboundContext struct { Inbound string InboundType string @@ -44,6 +50,7 @@ type InboundContext struct { // cache + // Deprecated: implement in rule action InboundDetour string LastInbound string OriginDestination M.Socksaddr diff --git a/adapter/inbound/adapter.go b/adapter/inbound/adapter.go new file mode 100644 index 00000000..1426104a --- /dev/null +++ b/adapter/inbound/adapter.go @@ -0,0 +1,21 @@ +package inbound + +type Adapter struct { + inboundType string + inboundTag string +} + +func NewAdapter(inboundType string, inboundTag string) Adapter { + return Adapter{ + inboundType: inboundType, + inboundTag: inboundTag, + } +} + +func (a *Adapter) Type() string { + return a.inboundType +} + +func (a *Adapter) Tag() string { + return a.inboundTag +} diff --git a/adapter/inbound/registry.go b/adapter/inbound/registry.go new file mode 100644 index 00000000..9f678c90 --- /dev/null +++ b/adapter/inbound/registry.go @@ -0,0 +1,68 @@ +package inbound + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) { + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }) +} + +var _ adapter.InboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *Registry) CreateOptions(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/adapter/outbound.go b/adapter/outbound.go index 312cdf3e..df11ed61 100644 --- a/adapter/outbound.go +++ b/adapter/outbound.go @@ -1,6 +1,10 @@ package adapter import ( + "context" + + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" N "github.com/sagernet/sing/common/network" ) @@ -13,3 +17,8 @@ type Outbound interface { Dependencies() []string N.Dialer } + +type OutboundRegistry interface { + option.OutboundOptionsRegistry + CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error) +} diff --git a/adapter/outbound/adapter.go b/adapter/outbound/adapter.go new file mode 100644 index 00000000..481bb619 --- /dev/null +++ b/adapter/outbound/adapter.go @@ -0,0 +1,45 @@ +package outbound + +import ( + "github.com/sagernet/sing-box/option" +) + +type Adapter struct { + protocol string + network []string + tag string + dependencies []string +} + +func NewAdapter(protocol string, network []string, tag string, dependencies []string) Adapter { + return Adapter{ + protocol: protocol, + network: network, + tag: tag, + dependencies: dependencies, + } +} + +func NewAdapterWithDialerOptions(protocol string, network []string, tag string, dialOptions option.DialerOptions) Adapter { + var dependencies []string + if dialOptions.Detour != "" { + dependencies = []string{dialOptions.Detour} + } + return NewAdapter(protocol, network, tag, dependencies) +} + +func (a *Adapter) Type() string { + return a.protocol +} + +func (a *Adapter) Tag() string { + return a.tag +} + +func (a *Adapter) Network() []string { + return a.network +} + +func (a *Adapter) Dependencies() []string { + return a.dependencies +} diff --git a/outbound/default.go b/adapter/outbound/default.go similarity index 87% rename from outbound/default.go rename to adapter/outbound/default.go index a34ac97a..78b9bfd8 100644 --- a/outbound/default.go +++ b/adapter/outbound/default.go @@ -9,8 +9,6 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/buf" @@ -21,42 +19,6 @@ import ( N "github.com/sagernet/sing/common/network" ) -type myOutboundAdapter struct { - protocol string - network []string - router adapter.Router - logger log.ContextLogger - tag string - dependencies []string -} - -func (a *myOutboundAdapter) Type() string { - return a.protocol -} - -func (a *myOutboundAdapter) Tag() string { - return a.tag -} - -func (a *myOutboundAdapter) Network() []string { - return a.network -} - -func (a *myOutboundAdapter) Dependencies() []string { - return a.dependencies -} - -func (a *myOutboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -func withDialerDependency(options option.DialerOptions) []string { - if options.Detour != "" { - return []string{options.Detour} - } - return nil -} - func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error { ctx = adapter.WithContext(ctx, &metadata) var outConn net.Conn @@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro } return bufio.CopyConn(ctx, conn, serverConn) } - -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/adapter/outbound/registry.go b/adapter/outbound/registry.go new file mode 100644 index 00000000..f25631cf --- /dev/null +++ b/adapter/outbound/registry.go @@ -0,0 +1,68 @@ +package outbound + +import ( + "context" + "sync" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Outbound, error) + +func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) { + registry.register(outboundType, func() any { + return new(Options) + }, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) { + return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options))) + }) +} + +var _ adapter.OutboundRegistry = (*Registry)(nil) + +type ( + optionsConstructorFunc func() any + constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Outbound, error) +) + +type Registry struct { + access sync.Mutex + optionsType map[string]optionsConstructorFunc + constructors map[string]constructorFunc +} + +func NewRegistry() *Registry { + return &Registry{ + optionsType: make(map[string]optionsConstructorFunc), + constructors: make(map[string]constructorFunc), + } +} + +func (r *Registry) CreateOptions(outboundType string) (any, bool) { + r.access.Lock() + defer r.access.Unlock() + optionsConstructor, loaded := r.optionsType[outboundType] + if !loaded { + return nil, false + } + return optionsConstructor(), true +} + +func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) { + r.access.Lock() + defer r.access.Unlock() + constructor, loaded := r.constructors[outboundType] + if !loaded { + return nil, E.New("outbound type not found: " + outboundType) + } + return constructor(ctx, router, logger, tag, options) +} + +func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) { + r.access.Lock() + defer r.access.Unlock() + r.optionsType[outboundType] = optionsConstructor + r.constructors[outboundType] = constructor +} diff --git a/box.go b/box.go index 716b1b09..bc714e5a 100644 --- a/box.go +++ b/box.go @@ -14,10 +14,9 @@ import ( "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/inbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/route" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" @@ -44,16 +43,37 @@ type Box struct { type Options struct { option.Options Context context.Context - PlatformInterface platform.Interface PlatformLogWriter log.PlatformWriter } +func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context { + if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.InboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) + ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry) + } + if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil || + service.FromContext[adapter.OutboundRegistry](ctx) == nil { + ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry) + ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry) + } + return ctx +} + func New(options Options) (*Box, error) { createdAt := time.Now() ctx := options.Context if ctx == nil { ctx = context.Background() } + inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) + if inboundRegistry == nil { + return nil, E.New("missing inbound registry in context") + } + outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) + if outboundRegistry == nil { + return nil, E.New("missing outbound registry in context") + } ctx = service.ContextWithDefaultRegistry(ctx) ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) @@ -70,8 +90,9 @@ func New(options Options) (*Box, error) { if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } + platformInterface := service.FromContext[platform.Interface](ctx) var defaultLogWriter io.Writer - if options.PlatformInterface != nil { + if platformInterface != nil { defaultLogWriter = io.Discard } logFactory, err := log.New(log.Options{ @@ -92,64 +113,70 @@ func New(options Options) (*Box, error) { common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP), options.Inbounds, - options.PlatformInterface, ) if err != nil { return nil, E.Cause(err, "parse route options") } - inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) - outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) for i, inboundOptions := range options.Inbounds { - var in adapter.Inbound + var currentInbound adapter.Inbound var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } - in, err = inbound.New( + currentInbound, err = inboundRegistry.CreateInbound( ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, - inboundOptions, - options.PlatformInterface, + inboundOptions.Type, + inboundOptions.Options, ) if err != nil { return nil, E.Cause(err, "parse inbound[", i, "]") } - inbounds = append(inbounds, in) + inbounds = append(inbounds, currentInbound) } for i, outboundOptions := range options.Outbounds { - var out adapter.Outbound + var currentOutbound adapter.Outbound var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag } else { tag = F.ToString(i) } - out, err = outbound.New( - ctx, + outboundCtx := ctx + if tag != "" { + // TODO: remove this + outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{ + Outbound: tag, + }) + } + currentOutbound, err = outboundRegistry.CreateOutbound( + outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), tag, - outboundOptions) + outboundOptions.Type, + outboundOptions.Options, + ) if err != nil { return nil, E.Cause(err, "parse outbound[", i, "]") } - outbounds = append(outbounds, out) + outbounds = append(outbounds, currentOutbound) } err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { - out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"}) - common.Must(oErr) - outbounds = append(outbounds, out) - return out + defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}) + common.Must(cErr) + outbounds = append(outbounds, defaultOutbound) + return defaultOutbound }) if err != nil { return nil, err } - if options.PlatformInterface != nil { - err = options.PlatformInterface.Initialize(ctx, router) + if platformInterface != nil { + err = platformInterface.Initialize(ctx, router) if err != nil { return nil, E.Cause(err, "initialize platform interface") } diff --git a/cmd/sing-box/cmd.go b/cmd/sing-box/cmd.go index a2d4a00b..dc7a8309 100644 --- a/cmd/sing-box/cmd.go +++ b/cmd/sing-box/cmd.go @@ -7,8 +7,9 @@ import ( "strconv" "time" + "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/experimental/deprecated" - _ "github.com/sagernet/sing-box/include" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/filemanager" @@ -68,4 +69,5 @@ func preRun(cmd *cobra.Command, args []string) { configPaths = append(configPaths, "config.json") } globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())) + globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry()) } diff --git a/cmd/sing-box/cmd_format.go b/cmd/sing-box/cmd_format.go index fc47c5a8..9856c763 100644 --- a/cmd/sing-box/cmd_format.go +++ b/cmd/sing-box/cmd_format.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "os" "path/filepath" @@ -38,7 +39,7 @@ func format() error { return err } for _, optionsEntry := range optionsList { - optionsEntry.options, err = badjson.Omitempty(optionsEntry.options) + optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options) if err != nil { return err } diff --git a/cmd/sing-box/cmd_merge.go b/cmd/sing-box/cmd_merge.go index 10dd38a1..fa194ed3 100644 --- a/cmd/sing-box/cmd_merge.go +++ b/cmd/sing-box/cmd_merge.go @@ -68,29 +68,19 @@ func merge(outputPath string) error { } func mergePathResources(options *option.Options) error { - for index, inbound := range options.Inbounds { - rawOptions, err := inbound.RawOptions() - if err != nil { - return err - } - if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions { + for _, inbound := range options.Inbounds { + if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions())) } - options.Inbounds[index] = inbound } - for index, outbound := range options.Outbounds { - rawOptions, err := outbound.RawOptions() - if err != nil { - return err - } + for _, outbound := range options.Outbounds { switch outbound.Type { case C.TypeSSH: - outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions) + mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions)) } - if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions { + if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions { tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions())) } - options.Outbounds[index] = outbound } return nil } @@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun return options } -func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions { +func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) { if options.PrivateKeyPath != "" { if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil { options.PrivateKey = trimStringArray(strings.Split(string(content), "\n")) } } - return options } func trimStringArray(array []string) []string { diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index 6850cd19..f31db9dc 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) { if err != nil { return nil, E.Cause(err, "read config at ", path) } - options, err := json.UnmarshalExtended[option.Options](configContent) + options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent) if err != nil { return nil, E.Cause(err, "decode config at ", path) } @@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) { } var mergedMessage json.RawMessage for _, options := range optionsList { - mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false) + mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false) if err != nil { return option.Options{}, E.Cause(err, "merge config at ", options.path) } } var mergedOptions option.Options - err = mergedOptions.UnmarshalJSON(mergedMessage) + err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage) if err != nil { return option.Options{}, E.Cause(err, "unmarshal merged config") } diff --git a/common/dialer/default.go b/common/dialer/default.go index b4bca55e..b8a0d5f4 100644 --- a/common/dialer/default.go +++ b/common/dialer/default.go @@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi setMultiPathTCP(&dialer4) } if options.IsWireGuardListener { - for _, controlFn := range wgControlFns { + for _, controlFn := range WgControlFns { listener.Control = control.Append(listener.Control, controlFn) } } diff --git a/common/dialer/wireguard.go b/common/dialer/wireguard.go index 195133c6..fbd323d8 100644 --- a/common/dialer/wireguard.go +++ b/common/dialer/wireguard.go @@ -2,8 +2,12 @@ package dialer import ( "net" + + "github.com/sagernet/sing/common/control" ) type WireGuardListener interface { ListenPacketCompat(network, address string) (net.PacketConn, error) } + +var WgControlFns []control.Func diff --git a/common/dialer/wireguard_control.go b/common/dialer/wireguard_control.go deleted file mode 100644 index def86411..00000000 --- a/common/dialer/wireguard_control.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build with_wireguard - -package dialer - -import ( - "github.com/sagernet/wireguard-go/conn" -) - -var _ WireGuardListener = (conn.Listener)(nil) - -var wgControlFns = conn.ControlFns diff --git a/common/dialer/wiregurad_stub.go b/common/dialer/wiregurad_stub.go deleted file mode 100644 index d30c223a..00000000 --- a/common/dialer/wiregurad_stub.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_wireguard - -package dialer - -import ( - "github.com/sagernet/sing/common/control" -) - -var wgControlFns []control.Func diff --git a/common/listener/listener.go b/common/listener/listener.go new file mode 100644 index 00000000..b42b0434 --- /dev/null +++ b/common/listener/listener.go @@ -0,0 +1,136 @@ +package listener + +import ( + "context" + "net" + "sync/atomic" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/common/settings" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +type Listener struct { + ctx context.Context + logger logger.ContextLogger + network []string + listenOptions option.ListenOptions + connHandler adapter.ConnectionHandlerEx + packetHandler adapter.PacketHandlerEx + oobPacketHandler adapter.OOBPacketHandlerEx + threadUnsafePacketWriter bool + disablePacketOutput bool + setSystemProxy bool + systemProxySOCKS bool + + tcpListener net.Listener + systemProxy settings.SystemProxy + udpConn *net.UDPConn + udpAddr M.Socksaddr + packetOutbound chan *N.PacketBuffer + packetOutboundClosed chan struct{} + shutdown atomic.Bool +} + +type Options struct { + Context context.Context + Logger logger.ContextLogger + Network []string + Listen option.ListenOptions + ConnectionHandler adapter.ConnectionHandlerEx + PacketHandler adapter.PacketHandlerEx + OOBPacketHandler adapter.OOBPacketHandlerEx + ThreadUnsafePacketWriter bool + DisablePacketOutput bool + SetSystemProxy bool + SystemProxySOCKS bool +} + +func New( + options Options, +) *Listener { + return &Listener{ + ctx: options.Context, + logger: options.Logger, + network: options.Network, + listenOptions: options.Listen, + connHandler: options.ConnectionHandler, + packetHandler: options.PacketHandler, + oobPacketHandler: options.OOBPacketHandler, + threadUnsafePacketWriter: options.ThreadUnsafePacketWriter, + disablePacketOutput: options.DisablePacketOutput, + setSystemProxy: options.SetSystemProxy, + systemProxySOCKS: options.SystemProxySOCKS, + } +} + +func (l *Listener) Start() error { + if common.Contains(l.network, N.NetworkTCP) { + _, err := l.ListenTCP() + if err != nil { + return err + } + go l.loopTCPIn() + } + if common.Contains(l.network, N.NetworkUDP) { + _, err := l.ListenUDP() + if err != nil { + return err + } + l.packetOutboundClosed = make(chan struct{}) + l.packetOutbound = make(chan *N.PacketBuffer, 64) + go l.loopUDPIn() + if !l.disablePacketOutput { + go l.loopUDPOut() + } + } + if l.setSystemProxy { + listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port + var listenAddrString string + listenAddr := l.listenOptions.Listen.Build() + if listenAddr.IsUnspecified() { + listenAddrString = "127.0.0.1" + } else { + listenAddrString = listenAddr.String() + } + systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS) + if err != nil { + return E.Cause(err, "initialize system proxy") + } + err = systemProxy.Enable() + if err != nil { + return E.Cause(err, "set system proxy") + } + l.systemProxy = systemProxy + } + return nil +} + +func (l *Listener) Close() error { + l.shutdown.Store(true) + var err error + if l.systemProxy != nil && l.systemProxy.IsEnabled() { + err = l.systemProxy.Disable() + } + return E.Errors(err, common.Close( + l.tcpListener, + common.PtrOrNil(l.udpConn), + )) +} + +func (l *Listener) TCPListener() net.Listener { + return l.tcpListener +} + +func (l *Listener) UDPConn() *net.UDPConn { + return l.udpConn +} + +func (l *Listener) ListenOptions() option.ListenOptions { + return l.listenOptions +} diff --git a/inbound/default_tcp_go1.21.go b/common/listener/listener_go121.go similarity index 90% rename from inbound/default_tcp_go1.21.go rename to common/listener/listener_go121.go index 906818cb..5af1b05a 100644 --- a/inbound/default_tcp_go1.21.go +++ b/common/listener/listener_go121.go @@ -1,6 +1,6 @@ //go:build go1.21 -package inbound +package listener import "net" diff --git a/common/listener/listener_go123.go b/common/listener/listener_go123.go new file mode 100644 index 00000000..2e1f4cf4 --- /dev/null +++ b/common/listener/listener_go123.go @@ -0,0 +1,16 @@ +//go:build go1.23 + +package listener + +import ( + "net" + "time" +) + +func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) { + listener.KeepAliveConfig = net.KeepAliveConfig{ + Enable: true, + Idle: idle, + Interval: interval, + } +} diff --git a/inbound/default_tcp_nongo1.21.go b/common/listener/listener_nongo121.go similarity index 87% rename from inbound/default_tcp_nongo1.21.go rename to common/listener/listener_nongo121.go index d19adb19..36073afe 100644 --- a/inbound/default_tcp_nongo1.21.go +++ b/common/listener/listener_nongo121.go @@ -1,6 +1,6 @@ //go:build !go1.21 -package inbound +package listener import "net" diff --git a/common/listener/listener_nongo123.go b/common/listener/listener_nongo123.go new file mode 100644 index 00000000..e1582981 --- /dev/null +++ b/common/listener/listener_nongo123.go @@ -0,0 +1,15 @@ +//go:build !go1.23 + +package listener + +import ( + "net" + "time" + + "github.com/sagernet/sing/common/control" +) + +func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) { + listener.KeepAlive = idle + listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval)) +} diff --git a/common/listener/listener_tcp.go b/common/listener/listener_tcp.go new file mode 100644 index 00000000..02cef3f0 --- /dev/null +++ b/common/listener/listener_tcp.go @@ -0,0 +1,85 @@ +package listener + +import ( + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + + "github.com/metacubex/tfo-go" +) + +func (l *Listener) ListenTCP() (net.Listener, error) { + var err error + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + var tcpListener net.Listener + var listenConfig net.ListenConfig + if l.listenOptions.TCPKeepAlive >= 0 { + keepIdle := time.Duration(l.listenOptions.TCPKeepAlive) + if keepIdle == 0 { + keepIdle = C.TCPKeepAliveInitial + } + keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval) + if keepInterval == 0 { + keepInterval = C.TCPKeepAliveInterval + } + setKeepAliveConfig(&listenConfig, keepIdle, keepInterval) + } + if l.listenOptions.TCPMultiPath { + if !go121Available { + return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") + } + setMultiPathTCP(&listenConfig) + } + if l.listenOptions.TCPFastOpen { + var tfoConfig tfo.ListenConfig + tfoConfig.ListenConfig = listenConfig + tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } else { + tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) + } + if err == nil { + l.logger.Info("tcp server started at ", tcpListener.Addr()) + } + //nolint:staticcheck + if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader { + return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") + } + l.tcpListener = tcpListener + return tcpListener, err +} + +func (l *Listener) loopTCPIn() { + tcpListener := l.tcpListener + var metadata adapter.InboundContext + for { + conn, err := tcpListener.Accept() + if err != nil { + //nolint:staticcheck + if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { + l.logger.Error(err) + continue + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.tcpListener.Close() + l.logger.Error("tcp listener closed: ", err) + continue + } + //nolint:staticcheck + metadata.InboundDetour = l.listenOptions.Detour + //nolint:staticcheck + metadata.InboundOptions = l.listenOptions.InboundOptions + metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + ctx := log.ContextWithNewID(l.ctx) + l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil) + } +} diff --git a/common/listener/listener_udp.go b/common/listener/listener_udp.go new file mode 100644 index 00000000..0c3220a7 --- /dev/null +++ b/common/listener/listener_udp.go @@ -0,0 +1,153 @@ +package listener + +import ( + "net" + "os" + + "github.com/sagernet/sing/common/buf" + "github.com/sagernet/sing/common/control" + E "github.com/sagernet/sing/common/exceptions" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func (l *Listener) ListenUDP() (net.PacketConn, error) { + bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort) + var lc net.ListenConfig + var udpFragment bool + if l.listenOptions.UDPFragment != nil { + udpFragment = *l.listenOptions.UDPFragment + } else { + udpFragment = l.listenOptions.UDPFragmentDefault + } + if !udpFragment { + lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) + } + udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) + if err != nil { + return nil, err + } + l.udpConn = udpConn.(*net.UDPConn) + l.udpAddr = bindAddr + l.logger.Info("udp server started at ", udpConn.LocalAddr()) + return udpConn, err +} + +func (l *Listener) UDPAddr() M.Socksaddr { + return l.udpAddr +} + +func (l *Listener) PacketWriter() N.PacketWriter { + return (*packetWriter)(l) +} + +func (l *Listener) loopUDPIn() { + defer close(l.packetOutboundClosed) + var buffer *buf.Buffer + if !l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + defer buffer.Release() + buffer.IncRef() + defer buffer.DecRef() + } + if l.oobPacketHandler != nil { + oob := make([]byte, 1024) + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) + } + } else { + for { + if l.threadUnsafePacketWriter { + buffer = buf.NewPacket() + } else { + buffer.Reset() + } + n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) + if err != nil { + if l.threadUnsafePacketWriter { + buffer.Release() + } + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener closed: ", err) + return + } + buffer.Truncate(n) + l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) + } + } +} + +func (l *Listener) loopUDPOut() { + for { + select { + case packet := <-l.packetOutbound: + destination := packet.Destination.AddrPort() + _, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination) + packet.Buffer.Release() + N.PutPacketBuffer(packet) + if err != nil { + if l.shutdown.Load() && E.IsClosed(err) { + return + } + l.udpConn.Close() + l.logger.Error("udp listener write back: ", destination, ": ", err) + return + } + continue + case <-l.packetOutboundClosed: + } + for { + select { + case packet := <-l.packetOutbound: + packet.Buffer.Release() + N.PutPacketBuffer(packet) + default: + return + } + } + } +} + +type packetWriter Listener + +func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { + packet := N.NewPacketBuffer() + packet.Buffer = buffer + packet.Destination = destination + select { + case w.packetOutbound <- packet: + return nil + default: + buffer.Release() + N.PutPacketBuffer(packet) + if w.shutdown.Load() { + return os.ErrClosed + } + w.logger.Trace("dropped packet to ", destination) + return nil + } +} + +func (w *packetWriter) WriteIsThreadUnsafe() { +} diff --git a/experimental/clashapi/api_meta_group.go b/experimental/clashapi/api_meta_group.go index 396dee7f..531311f4 100644 --- a/experimental/clashapi/api_meta_group.go +++ b/experimental/clashapi/api_meta_group.go @@ -10,7 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/json/badjson" @@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) { func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - group, ok := proxy.(adapter.OutboundGroup) + outboundGroup, ok := proxy.(adapter.OutboundGroup) if !ok { render.Status(r, http.StatusNotFound) render.JSON(w, r, ErrNotFound) @@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) defer cancel() var result map[string]uint16 - if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup { + if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup { result, err = urlTestGroup.URLTest(ctx) } else { - outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound { + outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound { itOutbound, _ := server.router.Outbound(it) return itOutbound })) @@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) var resultAccess sync.Mutex for _, detour := range outbounds { tag := detour.Tag() - realTag := outbound.RealTag(detour) + realTag := group.RealTag(detour) if checked[realTag] { continue } diff --git a/experimental/clashapi/proxies.go b/experimental/clashapi/proxies.go index 7a807c1f..4a9564ee 100644 --- a/experimental/clashapi/proxies.go +++ b/experimental/clashapi/proxies.go @@ -11,7 +11,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json/badjson" @@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) { } proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) - selector, ok := proxy.(*outbound.Selector) + selector, ok := proxy.(*group.Selector) if !ok { render.Status(r, http.StatusBadRequest) render.JSON(w, r, newError("Must be a Selector")) @@ -204,7 +204,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request) delay, err := urltest.URLTest(ctx, url, proxy) defer func() { - realTag := outbound.RealTag(proxy) + realTag := group.RealTag(proxy) if err != nil { server.urlTestHistory.DeleteURLTestHistory(realTag) } else { diff --git a/experimental/libbox/command_group.go b/experimental/libbox/command_group.go index 3a8d2a07..0915f56b 100644 --- a/experimental/libbox/command_group.go +++ b/experimental/libbox/command_group.go @@ -9,7 +9,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/service" @@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { } var groups []OutboundGroup for _, iGroup := range iGroups { - var group OutboundGroup - group.Tag = iGroup.Tag() - group.Type = iGroup.Type() - _, group.Selectable = iGroup.(*outbound.Selector) - group.Selected = iGroup.Now() + var outboundGroup OutboundGroup + outboundGroup.Tag = iGroup.Tag() + outboundGroup.Type = iGroup.Type() + _, outboundGroup.Selectable = iGroup.(*group.Selector) + outboundGroup.Selected = iGroup.Now() if cacheFile != nil { - if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded { - group.IsExpand = isExpand + if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded { + outboundGroup.IsExpand = isExpand } } @@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error { item.URLTestTime = history.Time.Unix() item.URLTestDelay = int32(history.Delay) } - group.ItemList = append(group.ItemList, &item) + outboundGroup.ItemList = append(outboundGroup.ItemList, &item) } - if len(group.ItemList) < 2 { + if len(outboundGroup.ItemList) < 2 { continue } - groups = append(groups, group) + groups = append(groups, outboundGroup) } return varbin.Write(writer, binary.BigEndian, groups) } diff --git a/experimental/libbox/command_select.go b/experimental/libbox/command_select.go index e1e67e60..f352005d 100644 --- a/experimental/libbox/command_select.go +++ b/experimental/libbox/command_select.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "net" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/varbin" ) @@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error { if !isLoaded { return writeError(conn, E.New("selector not found: ", groupTag)) } - selector, isSelector := outboundGroup.(*outbound.Selector) + selector, isSelector := outboundGroup.(*group.Selector) if !isSelector { return writeError(conn, E.New("outbound is not a selector: ", groupTag)) } diff --git a/experimental/libbox/command_urltest.go b/experimental/libbox/command_urltest.go index 6feda3f8..c72ded8a 100644 --- a/experimental/libbox/command_urltest.go +++ b/experimental/libbox/command_urltest.go @@ -7,7 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/urltest" - "github.com/sagernet/sing-box/outbound" + "github.com/sagernet/sing-box/protocol/group" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/batch" E "github.com/sagernet/sing/common/exceptions" @@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error { if !isOutboundGroup { return writeError(conn, E.New("outbound is not a group: ", groupTag)) } - urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest) + urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest) if isURLTest { go urlTest.CheckOutbounds() } else { diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 56c56189..53889d30 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common/control" @@ -17,10 +18,11 @@ import ( "github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" ) -func parseConfig(configContent string) (option.Options, error) { - options, err := json.UnmarshalExtended[option.Options]([]byte(configContent)) +func parseConfig(ctx context.Context, configContent string) (option.Options, error) { + options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent)) if err != nil { return option.Options{}, E.Cause(err, "decode config") } @@ -28,16 +30,17 @@ func parseConfig(configContent string) (option.Options, error) { } func CheckConfig(configContent string) error { - options, err := parseConfig(configContent) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + options, err := parseConfig(ctx, configContent) if err != nil { return err } - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) defer cancel() + ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil)) instance, err := box.New(box.Options{ - Context: ctx, - Options: options, - PlatformInterface: (*platformInterfaceStub)(nil), + Context: ctx, + Options: options, }) if err == nil { instance.Close() @@ -140,7 +143,7 @@ func (s *platformInterfaceStub) SendNotification(notification *platform.Notifica } func FormatConfig(configContent string) (string, error) { - options, err := parseConfig(configContent) + options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent) if err != nil { return "", err } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 20395e95..eadd4c27 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -17,6 +17,7 @@ import ( "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/platform" + "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-tun" @@ -41,21 +42,22 @@ type BoxService struct { } func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) { - options, err := parseConfig(configContent) + ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()) + options, err := parseConfig(ctx, configContent) if err != nil { return nil, err } runtimeDebug.FreeOSMemory() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(ctx) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) urlTestHistoryStorage := urltest.NewHistoryStorage() ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager)) platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()} + ctx = service.ContextWith[platform.Interface](ctx, platformWrapper) instance, err := box.New(box.Options{ Context: ctx, Options: options, - PlatformInterface: platformWrapper, PlatformLogWriter: platformWrapper, }) if err != nil { diff --git a/experimental/libbox/setup.go b/experimental/libbox/setup.go index eb5d7c4e..daafecca 100644 --- a/experimental/libbox/setup.go +++ b/experimental/libbox/setup.go @@ -9,7 +9,6 @@ import ( "github.com/sagernet/sing-box/common/humanize" C "github.com/sagernet/sing-box/constant" - _ "github.com/sagernet/sing-box/include" "github.com/sagernet/sing-box/log" ) diff --git a/go.mod b/go.mod index 7d4d8637..6cfb070c 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/sagernet/sing-box go 1.20 require ( - berty.tech/go-libtor v1.0.385 github.com/caddyserver/certmagic v0.20.0 github.com/cloudflare/circl v1.3.7 github.com/cretz/bine v0.2.0 @@ -17,7 +16,6 @@ require ( github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/mholt/acmez v1.2.0 github.com/miekg/dns v1.1.62 - github.com/ooni/go-libtor v1.1.8 github.com/oschwald/maxminddb-golang v1.12.0 github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 diff --git a/go.sum b/go.sum index b52cd461..a97d4c7c 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= -berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= @@ -9,7 +7,6 @@ github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NY github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,8 +78,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= -github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= @@ -146,7 +141,6 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= @@ -168,7 +162,6 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= @@ -182,7 +175,6 @@ golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/inbound/builder.go b/inbound/builder.go deleted file mode 100644 index ddfd361d..00000000 --- a/inbound/builder.go +++ /dev/null @@ -1,54 +0,0 @@ -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/experimental/libbox/platform" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) { - if options.Type == "" { - return nil, E.New("missing inbound type") - } - switch options.Type { - case C.TypeTun: - return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface) - case C.TypeRedirect: - return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil - case C.TypeTProxy: - return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil - case C.TypeDirect: - return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil - case C.TypeSOCKS: - return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeMixed: - return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeNaive: - return NewNaive(ctx, router, logger, tag, options.NaiveOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - default: - return nil, E.New("unknown inbound type: ", options.Type) - } -} diff --git a/inbound/default.go b/inbound/default.go deleted file mode 100644 index 880dd26f..00000000 --- a/inbound/default.go +++ /dev/null @@ -1,209 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/settings" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/atomic" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Inbound = (*myInboundAdapter)(nil) - -type myInboundAdapter struct { - protocol string - network []string - ctx context.Context - router adapter.ConnectionRouterEx - logger log.ContextLogger - tag string - listenOptions option.ListenOptions - connHandler adapter.ConnectionHandlerEx - packetHandler adapter.PacketHandlerEx - oobPacketHandler adapter.OOBPacketHandlerEx - packetUpstream any - - // http mixed - - setSystemProxy bool - systemProxy settings.SystemProxy - - // internal - - tcpListener net.Listener - udpConn *net.UDPConn - udpAddr M.Socksaddr - packetOutboundClosed chan struct{} - packetOutbound chan *myInboundPacket - - inShutdown atomic.Bool -} - -func (a *myInboundAdapter) Type() string { - return a.protocol -} - -func (a *myInboundAdapter) Tag() string { - return a.tag -} - -func (a *myInboundAdapter) Start() error { - var err error - if common.Contains(a.network, N.NetworkTCP) { - _, err = a.ListenTCP() - if err != nil { - return err - } - go a.loopTCPIn() - } - if common.Contains(a.network, N.NetworkUDP) { - _, err = a.ListenUDP() - if err != nil { - return err - } - a.packetOutboundClosed = make(chan struct{}) - a.packetOutbound = make(chan *myInboundPacket) - if a.oobPacketHandler != nil { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPOOBIn() - } else { - go a.loopUDPOOBInThreadSafe() - } - } else { - if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler { - go a.loopUDPIn() - } else { - go a.loopUDPInThreadSafe() - } - go a.loopUDPOut() - } - } - if a.setSystemProxy { - 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() - } - var systemProxy settings.SystemProxy - 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 = systemProxy.Enable() - if err != nil { - return E.Cause(err, "set system proxy") - } - a.systemProxy = systemProxy - } - return nil -} - -func (a *myInboundAdapter) Close() error { - a.inShutdown.Store(true) - var err error - if a.systemProxy != nil && a.systemProxy.IsEnabled() { - err = a.systemProxy.Disable() - } - return E.Errors(err, common.Close( - a.tcpListener, - common.PtrOrNil(a.udpConn), - )) -} - -func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamHandler(metadata, a.newConnection, a.streamPacketConnection, a) -} - -func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter { - return adapter.NewUpstreamContextHandler(a.newConnection, a.newPacketConnection, a) -} - -func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return a.router.RouteConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) streamPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return a.router.RoutePacketConnection(ctx, conn, metadata) -} - -func (a *myInboundAdapter) upstreamHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamHandlerEx(metadata, a.newConnectionEx, a.streamPacketConnectionEx) -} - -func (a *myInboundAdapter) upstreamContextHandlerEx() adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamContextHandlerEx(a.newConnectionEx, a.newPacketConnectionEx) -} - -func (a *myInboundAdapter) newConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) newPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - ctx = log.ContextWithNewID(ctx) - a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) streamPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Source.IsValid() { - metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap() - } - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr()).Unwrap() - } - return metadata -} - -// Deprecated: don't use -func (a *myInboundAdapter) newError(err error) { - a.logger.Error(err) -} - -// Deprecated: don't use -func (a *myInboundAdapter) NewError(ctx context.Context, err error) { - NewError(a.logger, ctx, err) -} - -// Deprecated: don't use -func NewError(logger log.ContextLogger, ctx context.Context, err error) { - common.Close(err) - if E.IsClosedOrCanceled(err) { - logger.DebugContext(ctx, "connection closed: ", err) - return - } - logger.ErrorContext(ctx, err) -} diff --git a/inbound/default_tcp.go b/inbound/default_tcp.go deleted file mode 100644 index d38f96fe..00000000 --- a/inbound/default_tcp.go +++ /dev/null @@ -1,84 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenTCP() (net.Listener, error) { - var err error - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var tcpListener net.Listener - var listenConfig net.ListenConfig - // TODO: Add an option to customize the keep alive period - listenConfig.KeepAlive = C.TCPKeepAliveInitial - listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval)) - if a.listenOptions.TCPMultiPath { - if !go121Available { - return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.") - } - setMultiPathTCP(&listenConfig) - } - if a.listenOptions.TCPFastOpen { - if !go120Available { - return nil, E.New("TCP Fast Open requires go1.20, please recompile your binary.") - } - tcpListener, err = listenTFO(listenConfig, a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } else { - tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String()) - } - if err == nil { - a.logger.Info("tcp server started at ", tcpListener.Addr()) - } - if a.listenOptions.ProxyProtocol || a.listenOptions.ProxyProtocolAcceptNoHeader { - return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0") - } - a.tcpListener = tcpListener - return tcpListener, err -} - -func (a *myInboundAdapter) loopTCPIn() { - tcpListener := a.tcpListener - for { - conn, err := tcpListener.Accept() - if err != nil { - //goland:noinspection GoDeprecation - //nolint:staticcheck - if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() { - a.logger.Error(err) - continue - } - if a.inShutdown.Load() && E.IsClosed(err) { - return - } - a.tcpListener.Close() - a.logger.Error("serve error: ", err) - continue - } - go a.injectTCP(conn, adapter.InboundContext{}) - } -} - -func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundContext) { - ctx := log.ContextWithNewID(a.ctx) - metadata = a.createMetadata(conn, metadata) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - a.connHandler.NewConnectionEx(ctx, conn, metadata, nil) -} - -func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - metadata := a.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - }) - a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) - a.connHandler.NewConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/default_tcp_go1.20.go b/inbound/default_tcp_go1.20.go deleted file mode 100644 index 23949b06..00000000 --- a/inbound/default_tcp_go1.20.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build go1.20 - -package inbound - -import ( - "context" - "net" - - "github.com/metacubex/tfo-go" -) - -const go120Available = true - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - var tfoConfig tfo.ListenConfig - tfoConfig.ListenConfig = listenConfig - return tfoConfig.Listen(ctx, network, address) -} diff --git a/inbound/default_tcp_nongo1.20.go b/inbound/default_tcp_nongo1.20.go deleted file mode 100644 index e7a026bc..00000000 --- a/inbound/default_tcp_nongo1.20.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !go1.20 - -package inbound - -import ( - "context" - "net" - "os" -) - -const go120Available = false - -func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) { - return nil, os.ErrInvalid -} diff --git a/inbound/default_udp.go b/inbound/default_udp.go deleted file mode 100644 index 6bcde79d..00000000 --- a/inbound/default_udp.go +++ /dev/null @@ -1,208 +0,0 @@ -package inbound - -import ( - "net" - "os" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - "github.com/sagernet/sing/common/control" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) { - bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort) - var lc net.ListenConfig - var udpFragment bool - if a.listenOptions.UDPFragment != nil { - udpFragment = *a.listenOptions.UDPFragment - } else { - udpFragment = a.listenOptions.UDPFragmentDefault - } - if !udpFragment { - lc.Control = control.Append(lc.Control, control.DisableUDPFragment()) - } - udpConn, err := lc.ListenPacket(a.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String()) - if err != nil { - return nil, err - } - a.udpConn = udpConn.(*net.UDPConn) - a.udpAddr = bindAddr - a.logger.Info("udp server started at ", udpConn.LocalAddr()) - return udpConn, err -} - -func (a *myInboundAdapter) loopUDPIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - for { - buffer.Reset() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return - } - buffer.Truncate(n) - a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOOBIn() { - defer close(a.packetOutboundClosed) - buffer := buf.NewPacket() - defer buffer.Release() - buffer.IncRef() - defer buffer.DecRef() - oob := make([]byte, 1024) - for { - buffer.Reset() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - return - } - buffer.Truncate(n) - a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPInThreadSafe() { - defer close(a.packetOutboundClosed) - for { - buffer := buf.NewPacket() - n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - a.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOOBInThreadSafe() { - defer close(a.packetOutboundClosed) - oob := make([]byte, 1024) - for { - buffer := buf.NewPacket() - n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob) - if err != nil { - buffer.Release() - return - } - buffer.Truncate(n) - a.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap()) - } -} - -func (a *myInboundAdapter) loopUDPOut() { - for { - select { - case packet := <-a.packetOutbound: - err := a.writePacket(packet.buffer, packet.destination) - if err != nil && !E.IsClosed(err) { - a.logger.Error(E.New("write back udp: ", err)) - } - continue - case <-a.packetOutboundClosed: - } - for { - select { - case packet := <-a.packetOutbound: - packet.buffer.Release() - default: - return - } - } - } -} - -func (a *myInboundAdapter) packetConn() N.PacketConn { - return (*myInboundPacketAdapter)(a) -} - -func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext { - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - if !metadata.Destination.IsValid() { - metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - } - metadata.OriginDestination = a.udpAddr - return metadata -} - -func (a *myInboundAdapter) createPacketMetadataEx(source M.Socksaddr, destination M.Socksaddr) adapter.InboundContext { - var metadata adapter.InboundContext - metadata.Inbound = a.tag - metadata.InboundType = a.protocol - metadata.InboundDetour = a.listenOptions.Detour - metadata.InboundOptions = a.listenOptions.InboundOptions - metadata.Source = source - metadata.Destination = destination - metadata.OriginDestination = a.udpAddr - return metadata -} - -func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - defer buffer.Release() - return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort())) -} - -type myInboundPacketAdapter myInboundAdapter - -func (s *myInboundPacketAdapter) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) { - n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes()) - if err != nil { - return M.Socksaddr{}, err - } - buffer.Truncate(n) - return M.SocksaddrFromNetIP(addr), nil -} - -func (s *myInboundPacketAdapter) WriteIsThreadUnsafe() { -} - -type myInboundPacket struct { - buffer *buf.Buffer - destination M.Socksaddr -} - -func (s *myInboundPacketAdapter) Upstream() any { - return s.udpConn -} - -func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error { - select { - case s.packetOutbound <- &myInboundPacket{buffer, destination}: - return nil - case <-s.packetOutboundClosed: - return os.ErrClosed - } -} - -func (s *myInboundPacketAdapter) Close() error { - return s.udpConn.Close() -} - -func (s *myInboundPacketAdapter) LocalAddr() net.Addr { - return s.udpConn.LocalAddr() -} - -func (s *myInboundPacketAdapter) SetDeadline(t time.Time) error { - return s.udpConn.SetDeadline(t) -} - -func (s *myInboundPacketAdapter) SetReadDeadline(t time.Time) error { - return s.udpConn.SetReadDeadline(t) -} - -func (s *myInboundPacketAdapter) SetWriteDeadline(t time.Time) error { - return s.udpConn.SetWriteDeadline(t) -} diff --git a/inbound/direct.go b/inbound/direct.go deleted file mode 100644 index b9a99f12..00000000 --- a/inbound/direct.go +++ /dev/null @@ -1,111 +0,0 @@ -package inbound - -import ( - "context" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/buf" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/udpnat2" -) - -var _ adapter.Inbound = (*Direct)(nil) - -type Direct struct { - myInboundAdapter - udpNat *udpnat.Service - overrideOption int - overrideDestination M.Socksaddr -} - -func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) *Direct { - options.UDPFragmentDefault = true - inbound := &Direct{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeDirect, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - if options.OverrideAddress != "" && options.OverridePort != 0 { - inbound.overrideOption = 1 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverrideAddress != "" { - inbound.overrideOption = 2 - inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) - } else if options.OverridePort != 0 { - inbound.overrideOption = 3 - inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} - } - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) - inbound.connHandler = inbound - inbound.packetHandler = inbound - return inbound -} - -func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - switch d.overrideOption { - case 1: - metadata.Destination = d.overrideDestination - case 2: - destination := d.overrideDestination - destination.Port = metadata.Destination.Port - metadata.Destination = destination - case 3: - metadata.Destination.Port = d.overrideDestination.Port - } - d.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return d.router.RouteConnection(ctx, conn, metadata) -} - -func (d *Direct) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - var destination M.Socksaddr - switch d.overrideOption { - case 1: - destination = d.overrideDestination - case 2: - destination = d.overrideDestination - destination.Port = source.Port - case 3: - destination = source - destination.Port = d.overrideDestination.Port - } - d.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) -} - -func (d *Direct) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - d.newConnectionEx(ctx, conn, metadata, onClose) -} - -func (d *Direct) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - d.newPacketConnectionEx(ctx, conn, d.createPacketMetadataEx(source, destination), onClose) -} - -func (d *Direct) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { - return true, d.ctx, &directPacketWriter{d.packetConn(), source}, nil -} - -type directPacketWriter struct { - writer N.PacketWriter - source M.Socksaddr -} - -func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { - return w.writer.WritePacket(buffer, w.source) -} diff --git a/inbound/http.go b/inbound/http.go deleted file mode 100644 index 20c8f690..00000000 --- a/inbound/http.go +++ /dev/null @@ -1,119 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" -) - -var ( - _ adapter.Inbound = (*HTTP)(nil) - _ adapter.TCPInjectableInbound = (*HTTP)(nil) -) - -type HTTP struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig -} - -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (*HTTP, error) { - inbound := &HTTP{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - inbound.connHandler = inbound - return inbound, nil -} - -func (h *HTTP) Start() error { - if h.tlsConfig != nil { - err := h.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - } - return h.myInboundAdapter.Start() -} - -func (h *HTTP) Close() error { - return common.Close( - &h.myInboundAdapter, - h.tlsConfig, - ) -} - -func (h *HTTP) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *HTTP) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - var err error - if h.tlsConfig != nil { - conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) - if err != nil { - return err - } - } - return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) -} - -func (a *myInboundAdapter) upstreamUserHandlerEx(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapterEx { - return adapter.NewUpstreamHandlerEx(metadata, a.newUserConnection, a.streamUserPacketConnection) -} - -func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) - return - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - a.router.RouteConnectionEx(ctx, conn, metadata, onClose) -} - -func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - user, loaded := auth.UserFromContext[string](ctx) - if !loaded { - a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) - return - } - metadata.User = user - a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - a.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/hysteria_stub.go b/inbound/hysteria_stub.go deleted file mode 100644 index fab86bb5..00000000 --- a/inbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/inbound/mixed.go b/inbound/mixed.go deleted file mode 100644 index 81f6a43a..00000000 --- a/inbound/mixed.go +++ /dev/null @@ -1,70 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/http" - "github.com/sagernet/sing/protocol/socks" - "github.com/sagernet/sing/protocol/socks/socks4" - "github.com/sagernet/sing/protocol/socks/socks5" -) - -var ( - _ adapter.Inbound = (*Mixed)(nil) - _ adapter.TCPInjectableInbound = (*Mixed)(nil) -) - -type Mixed struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -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{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - setSystemProxy: options.SetSystemProxy, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Mixed) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.newConnection(ctx, conn, metadata, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *Mixed) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { - reader := std_bufio.NewReader(conn) - headerBytes, err := reader.Peek(1) - if err != nil { - return E.Cause(err, "peek first byte") - } - switch headerBytes[0] { - case socks4.Version, socks5.Version: - return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) - default: - return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, onClose) - } -} diff --git a/inbound/naive_quic.go b/inbound/naive_quic.go deleted file mode 100644 index 9f99bf27..00000000 --- a/inbound/naive_quic.go +++ /dev/null @@ -1,47 +0,0 @@ -//go:build with_quic - -package inbound - -import ( - "github.com/sagernet/quic-go" - "github.com/sagernet/quic-go/http3" - "github.com/sagernet/sing-quic" - E "github.com/sagernet/sing/common/exceptions" -) - -func (n *Naive) configureHTTP3Listener() error { - err := qtls.ConfigureHTTP3(n.tlsConfig) - if err != nil { - return err - } - - udpConn, err := n.ListenUDP() - if err != nil { - return err - } - - quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{ - MaxIncomingStreams: 1 << 60, - Allow0RTT: true, - }) - if err != nil { - udpConn.Close() - return err - } - - h3Server := &http3.Server{ - Port: int(n.listenOptions.ListenPort), - Handler: n, - } - - go func() { - sErr := h3Server.ServeListener(quicListener) - udpConn.Close() - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http3 server serve error: ", sErr) - } - }() - - n.h3Server = h3Server - return nil -} diff --git a/inbound/naive_quic_stub.go b/inbound/naive_quic_stub.go deleted file mode 100644 index 90f697e4..00000000 --- a/inbound/naive_quic_stub.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - C "github.com/sagernet/sing-box/constant" -) - -func (n *Naive) configureHTTP3Listener() error { - return C.ErrQUICNotIncluded -} diff --git a/inbound/redirect.go b/inbound/redirect.go deleted file mode 100644 index c4c6faf3..00000000 --- a/inbound/redirect.go +++ /dev/null @@ -1,45 +0,0 @@ -package inbound - -import ( - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/redir" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -type Redirect struct { - myInboundAdapter -} - -func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect { - redirect := &Redirect{ - myInboundAdapter{ - protocol: C.TypeRedirect, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - redirect.connHandler = redirect - return redirect -} - -func (r *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - destination, err := redir.GetOriginalDestination(conn) - if err != nil { - conn.Close() - r.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) - return - } - metadata.Destination = M.SocksaddrFromNetIP(destination) - r.newConnectionEx(ctx, conn, metadata, onClose) -} diff --git a/inbound/shadowsocks.go b/inbound/shadowsocks.go deleted file mode 100644 index 3fff231d..00000000 --- a/inbound/shadowsocks.go +++ /dev/null @@ -1,114 +0,0 @@ -package inbound - -import ( - "context" - "net" - "time" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/mux" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-shadowsocks" - "github.com/sagernet/sing-shadowsocks/shadowaead" - "github.com/sagernet/sing-shadowsocks/shadowaead_2022" - "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/common/ntp" -) - -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { - if len(options.Users) > 0 && len(options.Destinations) > 0 { - return nil, E.New("users and destinations options must not be combined") - } - if len(options.Users) > 0 { - return newShadowsocksMulti(ctx, router, logger, tag, options) - } else if len(options.Destinations) > 0 { - return newShadowsocksRelay(ctx, router, logger, tag, options) - } else { - return newShadowsocks(ctx, router, logger, tag, options) - } -} - -var ( - _ adapter.Inbound = (*Shadowsocks)(nil) - _ adapter.TCPInjectableInbound = (*Shadowsocks)(nil) -) - -type Shadowsocks struct { - myInboundAdapter - service shadowsocks.Service -} - -func newShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) { - inbound := &Shadowsocks{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - } - - inbound.connHandler = inbound - inbound.packetHandler = inbound - var err error - inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) - if err != nil { - return nil, err - } - - var udpTimeout time.Duration - if options.UDPTimeout != 0 { - udpTimeout = time.Duration(options.UDPTimeout) - } else { - udpTimeout = C.UDPTimeout - } - switch { - case options.Method == shadowsocks.MethodNone: - inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) - case common.Contains(shadowaead.List, options.Method): - inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) - case common.Contains(shadowaead_2022.List, options.Method): - inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) - default: - err = E.New("unsupported method: ", options.Method) - } - inbound.packetUpstream = inbound.service - return inbound, err -} - -func (h *Shadowsocks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} - -func (h *Shadowsocks) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) - if err != nil { - h.logger.Error(E.Cause(err, "process packet from ", source)) - } -} - -func (h *Shadowsocks) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) -} - -func (h *Shadowsocks) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - ctx = log.ContextWithNewID(ctx) - h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) - h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) -} diff --git a/inbound/socks.go b/inbound/socks.go deleted file mode 100644 index 04b0a77d..00000000 --- a/inbound/socks.go +++ /dev/null @@ -1,52 +0,0 @@ -package inbound - -import ( - std_bufio "bufio" - "context" - "net" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing/common/auth" - E "github.com/sagernet/sing/common/exceptions" - N "github.com/sagernet/sing/common/network" - "github.com/sagernet/sing/protocol/socks" -) - -var ( - _ adapter.Inbound = (*Socks)(nil) - _ adapter.TCPInjectableInbound = (*Socks)(nil) -) - -type Socks struct { - myInboundAdapter - authenticator *auth.Authenticator -} - -func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks { - inbound := &Socks{ - myInboundAdapter{ - protocol: C.TypeSOCKS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - auth.NewAuthenticator(options.Users), - } - inbound.connHandler = inbound - return inbound -} - -func (h *Socks) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { - err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, h.upstreamUserHandlerEx(metadata), metadata.Source, metadata.Destination, onClose) - N.CloseOnHandshakeFailure(conn, onClose, err) - if err != nil { - h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) - } -} diff --git a/inbound/tuic_stub.go b/inbound/tuic_stub.go deleted file mode 100644 index bfd402ab..00000000 --- a/inbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package inbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/include/quic.go b/include/quic.go index 1bcc0fbc..980b4581 100644 --- a/include/quic.go +++ b/include/quic.go @@ -3,6 +3,24 @@ package include import ( + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/hysteria" + "github.com/sagernet/sing-box/protocol/hysteria2" + _ "github.com/sagernet/sing-box/protocol/naive/quic" + "github.com/sagernet/sing-box/protocol/tuic" _ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-dns/quic" ) + +func registerQUICInbounds(registry *inbound.Registry) { + hysteria.RegisterInbound(registry) + tuic.RegisterInbound(registry) + hysteria2.RegisterInbound(registry) +} + +func registerQUICOutbounds(registry *outbound.Registry) { + hysteria.RegisterOutbound(registry) + tuic.RegisterOutbound(registry) + hysteria2.RegisterOutbound(registry) +} diff --git a/include/quic_stub.go b/include/quic_stub.go index 43aa58d9..66c08590 100644 --- a/include/quic_stub.go +++ b/include/quic_stub.go @@ -4,11 +4,18 @@ package include import ( "context" + "io" + "net/http" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/naive" "github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/logger" @@ -29,3 +36,30 @@ func init() { }, ) } + +func registerQUICInbounds(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { + return nil, C.ErrQUICNotIncluded + }) + naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + return nil, C.ErrQUICNotIncluded + } +} + +func registerQUICOutbounds(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { + return nil, C.ErrQUICNotIncluded + }) +} diff --git a/include/registry.go b/include/registry.go new file mode 100644 index 00000000..03fb33f2 --- /dev/null +++ b/include/registry.go @@ -0,0 +1,95 @@ +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/block" + "github.com/sagernet/sing-box/protocol/direct" + "github.com/sagernet/sing-box/protocol/dns" + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing-box/protocol/http" + "github.com/sagernet/sing-box/protocol/mixed" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-box/protocol/redirect" + "github.com/sagernet/sing-box/protocol/shadowsocks" + "github.com/sagernet/sing-box/protocol/shadowtls" + "github.com/sagernet/sing-box/protocol/socks" + "github.com/sagernet/sing-box/protocol/ssh" + "github.com/sagernet/sing-box/protocol/tor" + "github.com/sagernet/sing-box/protocol/trojan" + "github.com/sagernet/sing-box/protocol/tun" + "github.com/sagernet/sing-box/protocol/vless" + "github.com/sagernet/sing-box/protocol/vmess" + E "github.com/sagernet/sing/common/exceptions" +) + +func InboundRegistry() *inbound.Registry { + registry := inbound.NewRegistry() + + tun.RegisterInbound(registry) + redirect.RegisterRedirect(registry) + redirect.RegisterTProxy(registry) + direct.RegisterInbound(registry) + + socks.RegisterInbound(registry) + http.RegisterInbound(registry) + mixed.RegisterInbound(registry) + + shadowsocks.RegisterInbound(registry) + vmess.RegisterInbound(registry) + trojan.RegisterInbound(registry) + naive.RegisterInbound(registry) + shadowtls.RegisterInbound(registry) + vless.RegisterInbound(registry) + + registerQUICInbounds(registry) + registerStubForRemovedInbounds(registry) + + return registry +} + +func OutboundRegistry() *outbound.Registry { + registry := outbound.NewRegistry() + + direct.RegisterOutbound(registry) + + block.RegisterOutbound(registry) + dns.RegisterOutbound(registry) + + group.RegisterSelector(registry) + group.RegisterURLTest(registry) + + socks.RegisterOutbound(registry) + http.RegisterOutbound(registry) + shadowsocks.RegisterOutbound(registry) + vmess.RegisterOutbound(registry) + trojan.RegisterOutbound(registry) + tor.RegisterOutbound(registry) + ssh.RegisterOutbound(registry) + shadowtls.RegisterOutbound(registry) + vless.RegisterOutbound(registry) + + registerQUICOutbounds(registry) + registerWireGuardOutbound(registry) + registerStubForRemovedOutbounds(registry) + + return registry +} + +func registerStubForRemovedInbounds(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) +} + +func registerStubForRemovedOutbounds(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksROutboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { + return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") + }) +} diff --git a/include/wireguard.go b/include/wireguard.go new file mode 100644 index 00000000..dfc3a242 --- /dev/null +++ b/include/wireguard.go @@ -0,0 +1,12 @@ +//go:build with_wireguard + +package include + +import ( + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/wireguard" +) + +func registerWireGuardOutbound(registry *outbound.Registry) { + wireguard.RegisterOutbound(registry) +} diff --git a/include/wireguard_stub.go b/include/wireguard_stub.go new file mode 100644 index 00000000..a9e84522 --- /dev/null +++ b/include/wireguard_stub.go @@ -0,0 +1,20 @@ +//go:build !with_wireguard + +package include + +import ( + "context" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + E "github.com/sagernet/sing/common/exceptions" +) + +func registerWireGuardOutbound(registry *outbound.Registry) { + outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) + }) +} diff --git a/option/inbound.go b/option/inbound.go index d3879904..651d0284 100644 --- a/option/inbound.go +++ b/option/inbound.go @@ -1,100 +1,49 @@ package option import ( + "context" "time" - C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" + "github.com/sagernet/sing/service" ) +type InboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Inbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - TunOptions TunInboundOptions `json:"-"` - RedirectOptions RedirectInboundOptions `json:"-"` - TProxyOptions TProxyInboundOptions `json:"-"` - DirectOptions DirectInboundOptions `json:"-"` - SocksOptions SocksInboundOptions `json:"-"` - HTTPOptions HTTPMixedInboundOptions `json:"-"` - MixedOptions HTTPMixedInboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksInboundOptions `json:"-"` - VMessOptions VMessInboundOptions `json:"-"` - TrojanOptions TrojanInboundOptions `json:"-"` - NaiveOptions NaiveInboundOptions `json:"-"` - HysteriaOptions HysteriaInboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSInboundOptions `json:"-"` - VLESSOptions VLESSInboundOptions `json:"-"` - TUICOptions TUICInboundOptions `json:"-"` - Hysteria2Options Hysteria2InboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Inbound _Inbound -func (h *Inbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeTun: - rawOptionsPtr = &h.TunOptions - case C.TypeRedirect: - rawOptionsPtr = &h.RedirectOptions - case C.TypeTProxy: - rawOptionsPtr = &h.TProxyOptions - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeMixed: - rawOptionsPtr = &h.MixedOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeNaive: - rawOptionsPtr = &h.NaiveOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case "": - return nil, E.New("missing inbound type") - default: - return nil, E.New("unknown inbound type: ", h.Type) - } - return rawOptionsPtr, nil +func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options) } -func (h Inbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((_Inbound)(h), rawOptions) -} - -func (h *Inbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Inbound)(h)) +func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.Unmarshal(content, (*_Inbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err - } - err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions) + registry := service.FromContext[InboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing inbound options registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown inbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options) if err != nil { return err } + h.Options = options return nil } @@ -105,19 +54,24 @@ type InboundOptions struct { SniffTimeout Duration `json:"sniff_timeout,omitempty"` DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"` UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"` + Detour string `json:"detour,omitempty"` } type ListenOptions struct { - Listen *ListenAddress `json:"listen,omitempty"` - ListenPort uint16 `json:"listen_port,omitempty"` - TCPFastOpen bool `json:"tcp_fast_open,omitempty"` - TCPMultiPath bool `json:"tcp_multi_path,omitempty"` - UDPFragment *bool `json:"udp_fragment,omitempty"` - UDPFragmentDefault bool `json:"-"` - UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` - ProxyProtocol bool `json:"proxy_protocol,omitempty"` - ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` - Detour string `json:"detour,omitempty"` + Listen *ListenAddress `json:"listen,omitempty"` + ListenPort uint16 `json:"listen_port,omitempty"` + TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"` + TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"` + TCPFastOpen bool `json:"tcp_fast_open,omitempty"` + TCPMultiPath bool `json:"tcp_multi_path,omitempty"` + UDPFragment *bool `json:"udp_fragment,omitempty"` + UDPFragmentDefault bool `json:"-"` + UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"` + + // Deprecated: removed + ProxyProtocol bool `json:"proxy_protocol,omitempty"` + // Deprecated: removed + ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"` InboundOptions } diff --git a/option/json.go b/option/json.go deleted file mode 100644 index 775141d5..00000000 --- a/option/json.go +++ /dev/null @@ -1,71 +0,0 @@ -package option - -import ( - "github.com/sagernet/sing/common" - E "github.com/sagernet/sing/common/exceptions" - "github.com/sagernet/sing/common/json" - "github.com/sagernet/sing/common/json/badjson" -) - -func ToMap(v any) (*badjson.JSONObject, error) { - inputContent, err := json.Marshal(v) - if err != nil { - return nil, err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return nil, err - } - return &content, nil -} - -func MergeObjects(objects ...any) (*badjson.JSONObject, error) { - var content badjson.JSONObject - for _, object := range objects { - objectMap, err := ToMap(object) - if err != nil { - return nil, err - } - content.PutAll(objectMap) - } - return &content, nil -} - -func MarshallObjects(objects ...any) ([]byte, error) { - objects = common.FilterNotNil(objects) - if len(objects) == 1 { - return json.Marshal(objects[0]) - } - content, err := MergeObjects(objects...) - if err != nil { - return nil, err - } - return content.MarshalJSON() -} - -func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error { - parentContent, err := ToMap(parentObject) - if err != nil { - return err - } - var content badjson.JSONObject - err = content.UnmarshalJSON(inputContent) - if err != nil { - return err - } - for _, key := range parentContent.Keys() { - content.Remove(key) - } - if object == nil { - if content.IsEmpty() { - return nil - } - return E.New("unexpected key: ", content.Keys()[0]) - } - inputContent, err = content.MarshalJSON() - if err != nil { - return err - } - return json.UnmarshalDisallowUnknownFields(inputContent, object) -} diff --git a/option/config.go b/option/options.go similarity index 84% rename from option/config.go rename to option/options.go index 3f5d7602..13a16c08 100644 --- a/option/config.go +++ b/option/options.go @@ -2,6 +2,7 @@ package option import ( "bytes" + "context" "github.com/sagernet/sing/common/json" ) @@ -20,8 +21,8 @@ type _Options struct { type Options _Options -func (o *Options) UnmarshalJSON(content []byte) error { - decoder := json.NewDecoder(bytes.NewReader(content)) +func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error { + decoder := json.NewDecoderContext(ctx, bytes.NewReader(content)) decoder.DisallowUnknownFields() err := decoder.Decode((*_Options)(o)) if err != nil { @@ -38,3 +39,5 @@ type LogOptions struct { Timestamp bool `json:"timestamp,omitempty"` DisableColor bool `json:"-"` } + +type StubOptions struct{} diff --git a/option/outbound.go b/option/outbound.go index 6c943cd9..00a20aa5 100644 --- a/option/outbound.go +++ b/option/outbound.go @@ -1,104 +1,49 @@ package option import ( - C "github.com/sagernet/sing-box/constant" + "context" + E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" M "github.com/sagernet/sing/common/metadata" + "github.com/sagernet/sing/service" ) +type OutboundOptionsRegistry interface { + CreateOptions(outboundType string) (any, bool) +} + type _Outbound struct { - Type string `json:"type"` - Tag string `json:"tag,omitempty"` - DirectOptions DirectOutboundOptions `json:"-"` - SocksOptions SocksOutboundOptions `json:"-"` - HTTPOptions HTTPOutboundOptions `json:"-"` - ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"` - VMessOptions VMessOutboundOptions `json:"-"` - TrojanOptions TrojanOutboundOptions `json:"-"` - WireGuardOptions WireGuardOutboundOptions `json:"-"` - HysteriaOptions HysteriaOutboundOptions `json:"-"` - TorOptions TorOutboundOptions `json:"-"` - SSHOptions SSHOutboundOptions `json:"-"` - ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"` - ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"` - VLESSOptions VLESSOutboundOptions `json:"-"` - TUICOptions TUICOutboundOptions `json:"-"` - Hysteria2Options Hysteria2OutboundOptions `json:"-"` - SelectorOptions SelectorOutboundOptions `json:"-"` - URLTestOptions URLTestOutboundOptions `json:"-"` + Type string `json:"type"` + Tag string `json:"tag,omitempty"` + Options any `json:"-"` } type Outbound _Outbound -func (h *Outbound) RawOptions() (any, error) { - var rawOptionsPtr any - switch h.Type { - case C.TypeDirect: - rawOptionsPtr = &h.DirectOptions - case C.TypeBlock, C.TypeDNS: - rawOptionsPtr = nil - case C.TypeSOCKS: - rawOptionsPtr = &h.SocksOptions - case C.TypeHTTP: - rawOptionsPtr = &h.HTTPOptions - case C.TypeShadowsocks: - rawOptionsPtr = &h.ShadowsocksOptions - case C.TypeVMess: - rawOptionsPtr = &h.VMessOptions - case C.TypeTrojan: - rawOptionsPtr = &h.TrojanOptions - case C.TypeWireGuard: - rawOptionsPtr = &h.WireGuardOptions - case C.TypeHysteria: - rawOptionsPtr = &h.HysteriaOptions - case C.TypeTor: - rawOptionsPtr = &h.TorOptions - case C.TypeSSH: - rawOptionsPtr = &h.SSHOptions - case C.TypeShadowTLS: - rawOptionsPtr = &h.ShadowTLSOptions - case C.TypeShadowsocksR: - rawOptionsPtr = &h.ShadowsocksROptions - case C.TypeVLESS: - rawOptionsPtr = &h.VLESSOptions - case C.TypeTUIC: - rawOptionsPtr = &h.TUICOptions - case C.TypeHysteria2: - rawOptionsPtr = &h.Hysteria2Options - case C.TypeSelector: - rawOptionsPtr = &h.SelectorOptions - case C.TypeURLTest: - rawOptionsPtr = &h.URLTestOptions - case "": - return nil, E.New("missing outbound type") - default: - return nil, E.New("unknown outbound type: ", h.Type) - } - return rawOptionsPtr, nil +func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) { + return badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options) } -func (h *Outbound) MarshalJSON() ([]byte, error) { - rawOptions, err := h.RawOptions() - if err != nil { - return nil, err - } - return MarshallObjects((*_Outbound)(h), rawOptions) -} - -func (h *Outbound) UnmarshalJSON(bytes []byte) error { - err := json.Unmarshal(bytes, (*_Outbound)(h)) +func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error { + err := json.Unmarshal(content, (*_Outbound)(h)) if err != nil { return err } - rawOptions, err := h.RawOptions() - if err != nil { - return err - } - err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions) + registry := service.FromContext[OutboundOptionsRegistry](ctx) + if registry == nil { + return E.New("missing outbound options registry in context") + } + options, loaded := registry.CreateOptions(h.Type) + if !loaded { + return E.New("unknown outbound type: ", h.Type) + } + err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(h), options) if err != nil { return err } + h.Options = options return nil } diff --git a/option/rule.go b/option/rule.go index 0b11cbdd..07e6ddbe 100644 --- a/option/rule.go +++ b/option/rule.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _Rule struct { @@ -28,7 +29,7 @@ func (r Rule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_Rule)(r), v) + return badjson.MarshallObjects((_Rule)(r), v) } func (r *Rule) UnmarshalJSON(bytes []byte) error { @@ -46,7 +47,7 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_Rule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v) if err != nil { return err } @@ -109,7 +110,7 @@ type DefaultRule struct { } func (r *DefaultRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r.RawDefaultRule, r.RuleAction) + return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction) } func (r *DefaultRule) UnmarshalJSON(data []byte) error { @@ -117,7 +118,7 @@ func (r *DefaultRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) } func (r *DefaultRule) IsValid() bool { @@ -139,7 +140,7 @@ type LogicalRule struct { } func (r *LogicalRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r._LogicalRule, r.RuleAction) + return badjson.MarshallObjects(r._LogicalRule, r.RuleAction) } func (r *LogicalRule) UnmarshalJSON(data []byte) error { @@ -147,7 +148,7 @@ func (r *LogicalRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) + return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) } func (r *LogicalRule) IsValid() bool { diff --git a/option/rule_action.go b/option/rule_action.go index f446d81d..e752a2be 100644 --- a/option/rule_action.go +++ b/option/rule_action.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _RuleAction struct { @@ -36,9 +37,9 @@ func (r RuleAction) MarshalJSON() ([]byte, error) { return nil, E.New("unknown rule action: " + r.Action) } if v == nil { - return MarshallObjects((_RuleAction)(r)) + return badjson.MarshallObjects((_RuleAction)(r)) } - return MarshallObjects((_RuleAction)(r), v) + return badjson.MarshallObjects((_RuleAction)(r), v) } func (r *RuleAction) UnmarshalJSON(data []byte) error { @@ -68,7 +69,7 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) } - return UnmarshallExcluded(data, (*_RuleAction)(r), v) + return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v) } type _DNSRuleAction struct { @@ -95,9 +96,9 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) { return nil, E.New("unknown DNS rule action: " + r.Action) } if v == nil { - return MarshallObjects((_DNSRuleAction)(r)) + return badjson.MarshallObjects((_DNSRuleAction)(r)) } - return MarshallObjects((_DNSRuleAction)(r), v) + return badjson.MarshallObjects((_DNSRuleAction)(r), v) } func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { @@ -121,7 +122,7 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error { // check unknown fields return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) } - return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) + return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) } type RouteActionOptions struct { diff --git a/option/rule_dns.go b/option/rule_dns.go index b328c45c..8c4b6ab8 100644 --- a/option/rule_dns.go +++ b/option/rule_dns.go @@ -7,6 +7,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _DNSRule struct { @@ -28,7 +29,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_DNSRule)(r), v) + return badjson.MarshallObjects((_DNSRule)(r), v) } func (r *DNSRule) UnmarshalJSON(bytes []byte) error { @@ -46,7 +47,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v) if err != nil { return err } @@ -111,7 +112,7 @@ type DefaultDNSRule struct { } func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction) } func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { @@ -119,7 +120,7 @@ func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) } func (r *DefaultDNSRule) IsValid() bool { @@ -141,7 +142,7 @@ type LogicalDNSRule struct { } func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { - return MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) + return badjson.MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction) } func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { @@ -149,7 +150,7 @@ func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error { if err != nil { return err } - return UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) + return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) } func (r *LogicalDNSRule) IsValid() bool { diff --git a/option/rule_set.go b/option/rule_set.go index e0f10bf1..3bf9aa5c 100644 --- a/option/rule_set.go +++ b/option/rule_set.go @@ -9,6 +9,7 @@ import ( E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" "go4.org/netipx" ) @@ -37,7 +38,7 @@ func (r RuleSet) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule-set type: " + r.Type) } - return MarshallObjects((_RuleSet)(r), v) + return badjson.MarshallObjects((_RuleSet)(r), v) } func (r *RuleSet) UnmarshalJSON(bytes []byte) error { @@ -71,7 +72,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error { } else { r.Format = "" } - err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v) if err != nil { return err } @@ -107,7 +108,7 @@ func (r HeadlessRule) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule type: " + r.Type) } - return MarshallObjects((_HeadlessRule)(r), v) + return badjson.MarshallObjects((_HeadlessRule)(r), v) } func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { @@ -125,7 +126,7 @@ func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule type: " + r.Type) } - err = UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) if err != nil { return err } @@ -203,7 +204,7 @@ func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown rule-set version: ", r.Version) } - return MarshallObjects((_PlainRuleSetCompat)(r), v) + return badjson.MarshallObjects((_PlainRuleSetCompat)(r), v) } func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { @@ -220,7 +221,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown rule-set version: ", r.Version) } - err = UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) + err = badjson.UnmarshallExcluded(bytes, (*_PlainRuleSetCompat)(r), v) if err != nil { return err } diff --git a/option/simple.go b/option/simple.go index ba9d6bf1..78171ce4 100644 --- a/option/simple.go +++ b/option/simple.go @@ -14,7 +14,7 @@ type HTTPMixedInboundOptions struct { InboundTLSOptionsContainer } -type SocksOutboundOptions struct { +type SOCKSOutboundOptions struct { DialerOptions ServerOptions Version string `json:"version,omitempty"` diff --git a/option/tls_acme.go b/option/tls_acme.go index 17d515e2..9c2e081f 100644 --- a/option/tls_acme.go +++ b/option/tls_acme.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type InboundACMEOptions struct { @@ -45,7 +46,7 @@ func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown provider type: " + o.Provider) } - return MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) + return badjson.MarshallObjects((_ACMEDNS01ChallengeOptions)(o), v) } func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { @@ -62,7 +63,7 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown provider type: " + o.Provider) } - err = UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) if err != nil { return err } diff --git a/option/v2ray_transport.go b/option/v2ray_transport.go index fcd81f94..f87b175d 100644 --- a/option/v2ray_transport.go +++ b/option/v2ray_transport.go @@ -4,6 +4,7 @@ import ( C "github.com/sagernet/sing-box/constant" E "github.com/sagernet/sing/common/exceptions" "github.com/sagernet/sing/common/json" + "github.com/sagernet/sing/common/json/badjson" ) type _V2RayTransportOptions struct { @@ -35,7 +36,7 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) { default: return nil, E.New("unknown transport type: " + o.Type) } - return MarshallObjects((_V2RayTransportOptions)(o), v) + return badjson.MarshallObjects((_V2RayTransportOptions)(o), v) } func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { @@ -58,7 +59,7 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error { default: return E.New("unknown transport type: " + o.Type) } - err = UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) + err = badjson.UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) if err != nil { return err } diff --git a/outbound/block.go b/outbound/block.go deleted file mode 100644 index b6ccefe2..00000000 --- a/outbound/block.go +++ /dev/null @@ -1,54 +0,0 @@ -package outbound - -import ( - "context" - "io" - "net" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" -) - -var _ adapter.Outbound = (*Block)(nil) - -type Block struct { - myOutboundAdapter -} - -func NewBlock(logger log.ContextLogger, tag string) *Block { - return &Block{ - myOutboundAdapter{ - protocol: C.TypeBlock, - network: []string{N.NetworkTCP, N.NetworkUDP}, - logger: logger, - tag: tag, - }, - } -} - -func (h *Block) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - h.logger.InfoContext(ctx, "blocked connection to ", destination) - return nil, io.EOF -} - -func (h *Block) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - h.logger.InfoContext(ctx, "blocked packet connection to ", destination) - return nil, io.EOF -} - -// Deprecated -func (h *Block) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked connection to ", metadata.Destination) - return nil -} - -// Deprecated -func (h *Block) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - conn.Close() - h.logger.InfoContext(ctx, "blocked packet connection to ", metadata.Destination) - return nil -} diff --git a/outbound/builder.go b/outbound/builder.go deleted file mode 100644 index d895b56d..00000000 --- a/outbound/builder.go +++ /dev/null @@ -1,65 +0,0 @@ -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Outbound) (adapter.Outbound, error) { - if tag != "" { - ctx = adapter.WithContext(ctx, &adapter.InboundContext{ - Outbound: tag, - }) - } - if options.Type == "" { - return nil, E.New("missing outbound type") - } - ctx = ContextWithTag(ctx, tag) - switch options.Type { - case C.TypeDirect: - return NewDirect(router, logger, tag, options.DirectOptions) - case C.TypeBlock: - return NewBlock(logger, tag), nil - case C.TypeDNS: - return NewDNS(router, tag), nil - case C.TypeSOCKS: - return NewSocks(router, logger, tag, options.SocksOptions) - case C.TypeHTTP: - return NewHTTP(ctx, router, logger, tag, options.HTTPOptions) - case C.TypeShadowsocks: - return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions) - case C.TypeVMess: - return NewVMess(ctx, router, logger, tag, options.VMessOptions) - case C.TypeTrojan: - return NewTrojan(ctx, router, logger, tag, options.TrojanOptions) - case C.TypeWireGuard: - return NewWireGuard(ctx, router, logger, tag, options.WireGuardOptions) - case C.TypeHysteria: - return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions) - case C.TypeTor: - return NewTor(ctx, router, logger, tag, options.TorOptions) - case C.TypeSSH: - return NewSSH(ctx, router, logger, tag, options.SSHOptions) - case C.TypeShadowTLS: - return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions) - case C.TypeShadowsocksR: - return NewShadowsocksR(ctx, router, logger, tag, options.ShadowsocksROptions) - case C.TypeVLESS: - return NewVLESS(ctx, router, logger, tag, options.VLESSOptions) - case C.TypeTUIC: - return NewTUIC(ctx, router, logger, tag, options.TUICOptions) - case C.TypeHysteria2: - return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options) - case C.TypeSelector: - return NewSelector(ctx, router, logger, tag, options.SelectorOptions) - case C.TypeURLTest: - return NewURLTest(ctx, router, logger, tag, options.URLTestOptions) - default: - return nil, E.New("unknown outbound type: ", options.Type) - } -} diff --git a/outbound/hysteria_stub.go b/outbound/hysteria_stub.go deleted file mode 100644 index 84db5305..00000000 --- a/outbound/hysteria_stub.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} - -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/lookback.go b/outbound/lookback.go deleted file mode 100644 index aeb7451d..00000000 --- a/outbound/lookback.go +++ /dev/null @@ -1,14 +0,0 @@ -package outbound - -import "context" - -type outboundTagKey struct{} - -func ContextWithTag(ctx context.Context, outboundTag string) context.Context { - return context.WithValue(ctx, outboundTagKey{}, outboundTag) -} - -func TagFromContext(ctx context.Context) (string, bool) { - value, loaded := ctx.Value(outboundTagKey{}).(string) - return value, loaded -} diff --git a/outbound/shadowsocksr.go b/outbound/shadowsocksr.go deleted file mode 100644 index 615a71e4..00000000 --- a/outbound/shadowsocksr.go +++ /dev/null @@ -1,18 +0,0 @@ -//go:build with_shadowsocksr - -package outbound - -import ( - "context" - "os" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -var _ int = "ShadowsocksR is deprecated and removed in sing-box 1.6.0" - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, os.ErrInvalid -} diff --git a/outbound/shadowsocksr_stub.go b/outbound/shadowsocksr_stub.go deleted file mode 100644 index 94971da0..00000000 --- a/outbound/shadowsocksr_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_shadowsocksr - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewShadowsocksR(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) { - return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0") -} diff --git a/outbound/tor_embed.go b/outbound/tor_embed.go deleted file mode 100644 index d80b49ae..00000000 --- a/outbound/tor_embed.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && !(android || ios) - -package outbound - -import ( - "berty.tech/go-libtor" - "github.com/cretz/bine/tor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_embed_mobile.go b/outbound/tor_embed_mobile.go deleted file mode 100644 index 0900d8c9..00000000 --- a/outbound/tor_embed_mobile.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build with_embedded_tor && (android || ios) - -package outbound - -import ( - "github.com/cretz/bine/tor" - "github.com/ooni/go-libtor" -) - -func newConfig() tor.StartConf { - return tor.StartConf{ - ProcessCreator: libtor.Creator, - UseEmbeddedControlConn: true, - } -} diff --git a/outbound/tor_external.go b/outbound/tor_external.go deleted file mode 100644 index 6bce95d1..00000000 --- a/outbound/tor_external.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build !with_embedded_tor - -package outbound - -import "github.com/cretz/bine/tor" - -func newConfig() tor.StartConf { - return tor.StartConf{} -} diff --git a/outbound/tuic_stub.go b/outbound/tuic_stub.go deleted file mode 100644 index a6372c9e..00000000 --- a/outbound/tuic_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_quic - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" -) - -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { - return nil, C.ErrQUICNotIncluded -} diff --git a/outbound/wireguard_stub.go b/outbound/wireguard_stub.go deleted file mode 100644 index 3a8b0e87..00000000 --- a/outbound/wireguard_stub.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build !with_wireguard - -package outbound - -import ( - "context" - - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - E "github.com/sagernet/sing/common/exceptions" -) - -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { - return nil, E.New(`WireGuard is not included in this build, rebuild with -tags with_wireguard`) -} diff --git a/protocol/block/outbound.go b/protocol/block/outbound.go new file mode 100644 index 00000000..75bc7797 --- /dev/null +++ b/protocol/block/outbound.go @@ -0,0 +1,42 @@ +package block + +import ( + "context" + "net" + "syscall" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.StubOptions](registry, C.TypeBlock, New) +} + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger +} + +func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, _ option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeBlock, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + logger: logger, + }, nil +} + +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + h.logger.InfoContext(ctx, "blocked connection to ", destination) + return nil, syscall.EPERM +} + +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + h.logger.InfoContext(ctx, "blocked packet connection to ", destination) + return nil, syscall.EPERM +} diff --git a/protocol/direct/inbound.go b/protocol/direct/inbound.go new file mode 100644 index 00000000..568a72cb --- /dev/null +++ b/protocol/direct/inbound.go @@ -0,0 +1,139 @@ +package direct + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/buf" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/udpnat2" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.DirectInboundOptions](registry, C.TypeDirect, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service + overrideOption int + overrideDestination M.Socksaddr +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) (adapter.Inbound, error) { + options.UDPFragmentDefault = true + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeDirect, tag), + ctx: ctx, + router: router, + logger: logger, + } + if options.OverrideAddress != "" && options.OverridePort != 0 { + inbound.overrideOption = 1 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverrideAddress != "" { + inbound.overrideOption = 2 + inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort) + } else if options.OverridePort != 0 { + inbound.overrideOption = 3 + inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort} + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + inbound.udpNat = udpnat.New(inbound, inbound.preparePacketConnection, udpTimeout, false) + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + }) + return inbound, nil +} + +func (i *Inbound) Start() error { + return i.listener.Start() +} + +func (i *Inbound) Close() error { + return i.listener.Close() +} + +func (i *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + switch i.overrideOption { + case 1: + metadata.Destination = i.overrideDestination + case 2: + destination := i.overrideDestination + destination.Port = metadata.Destination.Port + metadata.Destination = destination + case 3: + metadata.Destination.Port = i.overrideDestination.Port + } + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + return i.router.RouteConnection(ctx, conn, metadata) +} + +func (i *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + var destination M.Socksaddr + switch i.overrideOption { + case 1: + destination = i.overrideDestination + case 2: + destination = i.overrideDestination + destination.Port = source.Port + case 3: + destination = source + destination.Port = i.overrideDestination.Port + } + i.udpNat.NewPacket([][]byte{buffer.Bytes()}, source, destination, nil) +} + +func (i *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + i.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + i.logger.InfoContext(ctx, "inbound packet connection from ", source) + i.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = i.Tag() + metadata.InboundType = i.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = i.listener.UDPAddr() + i.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} + +func (i *Inbound) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { + return true, log.ContextWithNewID(i.ctx), &directPacketWriter{i.listener.PacketWriter(), source}, nil +} + +type directPacketWriter struct { + writer N.PacketWriter + source M.Socksaddr +} + +func (w *directPacketWriter) WritePacket(buffer *buf.Buffer, addr M.Socksaddr) error { + return w.writer.WritePacket(buffer, w.source) +} diff --git a/outbound/direct_loopback_detect.go b/protocol/direct/loopback_detect.go similarity index 99% rename from outbound/direct_loopback_detect.go rename to protocol/direct/loopback_detect.go index 1469b9d0..5a184e69 100644 --- a/outbound/direct_loopback_detect.go +++ b/protocol/direct/loopback_detect.go @@ -1,4 +1,4 @@ -package outbound +package direct import ( "net" diff --git a/outbound/direct.go b/protocol/direct/outbound.go similarity index 76% rename from outbound/direct.go rename to protocol/direct/outbound.go index 415a72f3..32c1ed8f 100644 --- a/outbound/direct.go +++ b/protocol/direct/outbound.go @@ -1,4 +1,4 @@ -package outbound +package direct import ( "context" @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -14,17 +15,20 @@ import ( "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Outbound = (*Direct)(nil) - _ N.ParallelDialer = (*Direct)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound) +} -type Direct struct { - myOutboundAdapter +var _ N.ParallelDialer = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer domainStrategy dns.DomainStrategy fallbackDelay time.Duration @@ -33,21 +37,15 @@ type Direct struct { // loopBack *loopBackDetector } -func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (*Direct, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &Direct{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeDirect, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions), + logger: logger, domainStrategy: dns.DomainStrategy(options.DomainStrategy), fallbackDelay: time.Duration(options.FallbackDelay), dialer: outboundDialer, @@ -69,9 +67,9 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti return outbound, nil } -func (h *Direct) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch h.overrideOption { case 1: @@ -98,9 +96,9 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M. return h.dialer.DialContext(ctx, network, destination) } -func (h *Direct) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { +func (h *Outbound) DialParallel(ctx context.Context, network string, destination M.Socksaddr, destinationAddresses []netip.Addr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch h.overrideOption { case 1, 2: @@ -125,9 +123,9 @@ func (h *Direct) DialParallel(ctx context.Context, network string, destination M return N.DialParallel(ctx, h.dialer, network, destination, destinationAddresses, domainStrategy == dns.DomainStrategyPreferIPv6, h.fallbackDelay) } -func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination originDestination := destination switch h.overrideOption { @@ -156,14 +154,14 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net return conn, nil } -/*func (h *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +/*func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback connection to ", metadata.Destination) } return NewConnection(ctx, h, conn, metadata) } -func (h *Direct) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) { return E.New("reject loopback packet connection to ", metadata.Destination) } diff --git a/outbound/dns.go b/protocol/dns/handle.go similarity index 83% rename from outbound/dns.go rename to protocol/dns/handle.go index d9c92f19..23ed1c0c 100644 --- a/outbound/dns.go +++ b/protocol/dns/handle.go @@ -1,11 +1,9 @@ -package outbound +package dns import ( "context" "encoding/binary" "net" - "os" - "time" "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" @@ -21,44 +19,6 @@ import ( mDNS "github.com/miekg/dns" ) -var _ adapter.Outbound = (*DNS)(nil) - -type DNS struct { - myOutboundAdapter -} - -func NewDNS(router adapter.Router, tag string) *DNS { - return &DNS{ - myOutboundAdapter{ - protocol: C.TypeDNS, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - tag: tag, - }, - } -} - -func (d *DNS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { - return nil, os.ErrInvalid -} - -func (d *DNS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { - return nil, os.ErrInvalid -} - -// Deprecated -func (d *DNS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - metadata.Destination = M.Socksaddr{} - defer conn.Close() - for { - conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) - if err != nil { - return err - } - } -} - func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error { var queryLength uint16 err := binary.Read(conn, binary.BigEndian, &queryLength) @@ -100,11 +60,6 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net return nil } -// Deprecated -func (d *DNS) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) -} - func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error { metadata.Destination = M.Socksaddr{} var reader N.PacketReader = conn diff --git a/protocol/dns/outbound.go b/protocol/dns/outbound.go new file mode 100644 index 00000000..7ce9fde2 --- /dev/null +++ b/protocol/dns/outbound.go @@ -0,0 +1,61 @@ +package dns + +import ( + "context" + "net" + "os" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.StubOptions](registry, C.TypeDNS, NewOutbound) +} + +type Outbound struct { + outbound.Adapter + router adapter.Router + logger logger.ContextLogger +} + +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.StubOptions) (adapter.Outbound, error) { + return &Outbound{ + Adapter: outbound.NewAdapter(C.TypeDNS, []string{N.NetworkTCP, N.NetworkUDP}, tag, nil), + router: router, + logger: logger, + }, nil +} + +func (d *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { + return nil, os.ErrInvalid +} + +func (d *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { + return nil, os.ErrInvalid +} + +// Deprecated +func (d *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + metadata.Destination = M.Socksaddr{} + defer conn.Close() + for { + conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) + err := HandleStreamDNSRequest(ctx, d.router, conn, metadata) + if err != nil { + return err + } + } +} + +// Deprecated +func (d *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return NewDNSPacketConnection(ctx, d.router, conn, nil, metadata) +} diff --git a/outbound/selector.go b/protocol/group/selector.go similarity index 82% rename from outbound/selector.go rename to protocol/group/selector.go index 64e6a2f9..8ade27a9 100644 --- a/outbound/selector.go +++ b/protocol/group/selector.go @@ -1,28 +1,33 @@ -package outbound +package group import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" ) -var ( - _ adapter.Outbound = (*Selector)(nil) - _ adapter.OutboundGroup = (*Selector)(nil) -) +func RegisterSelector(registry *outbound.Registry) { + outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector) +} + +var _ adapter.OutboundGroup = (*Selector)(nil) type Selector struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + router adapter.Router + logger logger.ContextLogger tags []string defaultTag string outbounds map[string]adapter.Outbound @@ -31,16 +36,12 @@ type Selector struct { interruptExternalConnections bool } -func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (*Selector, error) { +func NewSelector(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SelectorOutboundOptions) (adapter.Outbound, error) { outbound := &Selector{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSelector, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds), ctx: ctx, + router: router, + logger: logger, tags: options.Outbounds, defaultTag: options.Default, outbounds: make(map[string]adapter.Outbound), @@ -69,10 +70,10 @@ func (s *Selector) Start() error { s.outbounds[tag] = detour } - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - selected := cacheFile.LoadSelected(s.tag) + selected := cacheFile.LoadSelected(s.Tag()) if selected != "" { detour, loaded := s.outbounds[selected] if loaded { @@ -117,10 +118,10 @@ func (s *Selector) SelectOutbound(tag string) bool { return true } s.selected = detour - if s.tag != "" { + if s.Tag() != "" { cacheFile := service.FromContext[adapter.CacheFile](s.ctx) if cacheFile != nil { - err := cacheFile.StoreSelected(s.tag, tag) + err := cacheFile.StoreSelected(s.Tag(), tag) if err != nil { s.logger.Error("store selected: ", err) } @@ -153,7 +154,7 @@ func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata ad if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { return legacyHandler.NewConnection(ctx, conn, metadata) } else { - return NewConnection(ctx, s.selected, conn, metadata) + return outbound.NewConnection(ctx, s.selected, conn, metadata) } } @@ -164,7 +165,7 @@ func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, m if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { return legacyHandler.NewPacketConnection(ctx, conn, metadata) } else { - return NewPacketConnection(ctx, s.selected, conn, metadata) + return outbound.NewPacketConnection(ctx, s.selected, conn, metadata) } } diff --git a/outbound/urltest.go b/protocol/group/urltest.go similarity index 94% rename from outbound/urltest.go rename to protocol/group/urltest.go index 564a0ddc..ccdf809d 100644 --- a/outbound/urltest.go +++ b/protocol/group/urltest.go @@ -1,4 +1,4 @@ -package outbound +package group import ( "context" @@ -7,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/interrupt" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" @@ -22,15 +23,20 @@ import ( "github.com/sagernet/sing/service/pause" ) +func RegisterURLTest(registry *outbound.Registry) { + outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest) +} + var ( - _ adapter.Outbound = (*URLTest)(nil) _ adapter.OutboundGroup = (*URLTest)(nil) _ adapter.InterfaceUpdateListener = (*URLTest)(nil) ) type URLTest struct { - myOutboundAdapter + outbound.Adapter ctx context.Context + router adapter.Router + logger log.ContextLogger tags []string link string interval time.Duration @@ -40,17 +46,12 @@ type URLTest struct { interruptExternalConnections bool } -func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (*URLTest, error) { +func NewURLTest(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.URLTestOutboundOptions) (adapter.Outbound, error) { outbound := &URLTest{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeURLTest, - network: []string{N.NetworkTCP, N.NetworkUDP}, - router: router, - logger: logger, - tag: tag, - dependencies: options.Outbounds, - }, + Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds), ctx: ctx, + router: router, + logger: logger, tags: options.Outbounds, link: options.URL, interval: time.Duration(options.Interval), @@ -171,14 +172,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne // Deprecated func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewConnection(ctx, s, conn, metadata) + return outbound.NewConnection(ctx, s, conn, metadata) } // TODO // Deprecated func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = interrupt.ContextWithIsExternalConnection(ctx) - return NewPacketConnection(ctx, s, conn, metadata) + return outbound.NewPacketConnection(ctx, s, conn, metadata) } func (s *URLTest) InterfaceUpdated() { diff --git a/protocol/http/inbound.go b/protocol/http/inbound.go new file mode 100644 index 00000000..87ed9a10 --- /dev/null +++ b/protocol/http/inbound.go @@ -0,0 +1,122 @@ +package http + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeHTTP, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHTTP, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: false, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + if h.tlsConfig != nil { + err := h.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + } + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return common.Close( + &h.listener, + h.tlsConfig, + ) +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + var err error + if h.tlsConfig != nil { + conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) + if err != nil { + return err + } + } + return http.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/http.go b/protocol/http/outbound.go similarity index 56% rename from outbound/http.go rename to protocol/http/outbound.go index 6f15afb5..4c930591 100644 --- a/outbound/http.go +++ b/protocol/http/outbound.go @@ -1,4 +1,4 @@ -package outbound +package http import ( "context" @@ -6,25 +6,30 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" sHTTP "github.com/sagernet/sing/protocol/http" ) -var _ adapter.Outbound = (*HTTP)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HTTPOutboundOptions](registry, C.TypeHTTP, NewOutbound) +} -type HTTP struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *sHTTP.Client } -func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (*HTTP, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err @@ -33,16 +38,10 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &HTTP{ - myOutboundAdapter{ - protocol: C.TypeHTTP, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - sHTTP.NewClient(sHTTP.Options{ + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, []string{N.NetworkTCP}, tag, options.DialerOptions), + logger: logger, + client: sHTTP.NewClient(sHTTP.Options{ Dialer: detour, Server: options.ServerOptions.Build(), Username: options.Username, @@ -53,14 +52,14 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge }, nil } -func (h *HTTP) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination h.logger.InfoContext(ctx, "outbound connection to ", destination) return h.client.DialContext(ctx, network, destination) } -func (h *HTTP) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/inbound/hysteria.go b/protocol/hysteria/inbound.go similarity index 71% rename from inbound/hysteria.go rename to protocol/hysteria/inbound.go index e415d570..8127106b 100644 --- a/inbound/hysteria.go +++ b/protocol/hysteria/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria import ( "context" @@ -8,7 +6,9 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/humanize" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -20,16 +20,21 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, NewInbound) +} -type Hysteria struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria.Service[int] userNameList []string } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (*Hysteria, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -38,16 +43,15 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - inbound := &Hysteria{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var sendBps, receiveBps uint64 @@ -113,9 +117,12 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL return inbound, nil } -func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -127,9 +134,13 @@ func (h *Hysteria) newConnection(ctx context.Context, conn net.Conn, metadata ad return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -141,23 +152,23 @@ func (h *Hysteria) newPacketConnection(ctx context.Context, conn N.PacketConn, m return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *Hysteria) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria.go b/protocol/hysteria/outbound.go similarity index 76% rename from outbound/hysteria.go rename to protocol/hysteria/outbound.go index f513cf64..4722f4f0 100644 --- a/outbound/hysteria.go +++ b/protocol/hysteria/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package hysteria import ( "context" @@ -8,31 +6,39 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria.Client } -func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (*Hysteria, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -88,20 +94,14 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL if err != nil { return nil, err } - return &Hysteria{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, networkList, tag, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -117,15 +117,15 @@ func (h *Hysteria) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx, destination) } -func (h *Hysteria) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/inbound/hysteria2.go b/protocol/hysteria2/inbound.go similarity index 74% rename from inbound/hysteria2.go rename to protocol/hysteria2/inbound.go index c13e9531..cbf81109 100644 --- a/inbound/hysteria2.go +++ b/protocol/hysteria2/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package hysteria2 import ( "context" @@ -11,6 +9,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -23,16 +23,21 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Inbound = (*Hysteria2)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, NewInbound) +} -type Hysteria2 struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig service *hysteria2.Service[int] userNameList []string } -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (*Hysteria2, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -76,16 +81,15 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context return nil, E.New("unknown masquerade URL scheme: ", masqueradeURL.Scheme) } } - inbound := &Hysteria2{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeHysteria2, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeHysteria2, tag), + router: router, + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -124,9 +128,12 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context return inbound, nil } -func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -138,9 +145,13 @@ func (h *Hysteria2) newConnection(ctx context.Context, conn net.Conn, metadata a return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -152,23 +163,23 @@ func (h *Hysteria2) newPacketConnection(ctx context.Context, conn N.PacketConn, return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *Hysteria2) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.service.Start(packetConn) } -func (h *Hysteria2) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.service), ) diff --git a/outbound/hysteria2.go b/protocol/hysteria2/outbound.go similarity index 69% rename from outbound/hysteria2.go rename to protocol/hysteria2/outbound.go index 5e46f6a8..5ebc6c91 100644 --- a/outbound/hysteria2.go +++ b/protocol/hysteria2/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package hysteria2 import ( "context" @@ -8,31 +6,39 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/protocol/tuic" "github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing-quic/hysteria2" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, NewOutbound) +} + var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) + _ adapter.Outbound = (*tuic.Outbound)(nil) + _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil) ) -type Hysteria2 struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *hysteria2.Client } -func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (*Hysteria2, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -74,20 +80,14 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context if err != nil { return nil, err } - return &Hysteria2{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeHysteria2, - network: networkList, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, - client: client, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, networkList, tag, options.DialerOptions), + logger: logger, + client: client, }, nil } -func (h *Hysteria2) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -103,15 +103,15 @@ func (h *Hysteria2) DialContext(ctx context.Context, network string, destination } } -func (h *Hysteria2) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return h.client.ListenPacket(ctx) } -func (h *Hysteria2) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { h.client.CloseWithError(E.New("network changed")) } -func (h *Hysteria2) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/protocol/mixed/inbound.go b/protocol/mixed/inbound.go new file mode 100644 index 00000000..e57b791f --- /dev/null +++ b/protocol/mixed/inbound.go @@ -0,0 +1,109 @@ +package mixed + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/http" + "github.com/sagernet/sing/protocol/socks" + "github.com/sagernet/sing/protocol/socks/socks4" + "github.com/sagernet/sing/protocol/socks/socks5" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.HTTPMixedInboundOptions](registry, C.TypeMixed, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeMixed, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + SetSystemProxy: options.SetSystemProxy, + SystemProxySOCKS: true, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.newConnection(ctx, conn, metadata, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) error { + reader := std_bufio.NewReader(conn) + headerBytes, err := reader.Peek(1) + if err != nil { + return E.Cause(err, "peek first byte") + } + switch headerBytes[0] { + case socks4.Version, socks5.Version: + return socks.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + default: + return http.HandleConnectionEx(ctx, conn, reader, h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, onClose) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/protocol/naive/inbound.go b/protocol/naive/inbound.go new file mode 100644 index 00000000..1a561aea --- /dev/null +++ b/protocol/naive/inbound.go @@ -0,0 +1,248 @@ +package naive + +import ( + "context" + "io" + "math/rand" + "net" + "net/http" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-box/transport/v2rayhttp" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + sHttp "github.com/sagernet/sing/protocol/http" +) + +var ConfigureHTTP3ListenerFunc func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.NaiveInboundOptions](registry, C.TypeNaive, NewInbound) +} + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + network []string + networkIsDefault bool + authenticator *auth.Authenticator + tlsConfig tls.ServerConfig + httpServer *http.Server + h3Server io.Closer +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeNaive, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), + networkIsDefault: options.Network == "", + network: options.Network.Build(), + authenticator: auth.NewAuthenticator(options.Users), + } + if common.Contains(inbound.network, N.NetworkUDP) { + if options.TLS == nil || !options.TLS.Enabled { + return nil, E.New("TLS is required for QUIC server") + } + } + if len(options.Users) == 0 { + return nil, E.New("missing users") + } + if options.TLS != nil { + tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) + if err != nil { + return nil, err + } + inbound.tlsConfig = tlsConfig + } + return inbound, nil +} + +func (n *Inbound) Start() error { + var tlsConfig *tls.STDConfig + if n.tlsConfig != nil { + err := n.tlsConfig.Start() + if err != nil { + return E.Cause(err, "create TLS config") + } + tlsConfig, err = n.tlsConfig.Config() + if err != nil { + return err + } + } + if common.Contains(n.network, N.NetworkTCP) { + tcpListener, err := n.listener.ListenTCP() + if err != nil { + return err + } + n.httpServer = &http.Server{ + Handler: n, + TLSConfig: tlsConfig, + BaseContext: func(listener net.Listener) context.Context { + return n.ctx + }, + } + go func() { + var sErr error + if tlsConfig != nil { + sErr = n.httpServer.ServeTLS(tcpListener, "", "") + } else { + sErr = n.httpServer.Serve(tcpListener) + } + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + n.logger.Error("http server serve error: ", sErr) + } + }() + } + + if common.Contains(n.network, N.NetworkUDP) { + http3Server, err := ConfigureHTTP3ListenerFunc(n.listener, n, n.tlsConfig, n.logger) + if err == nil { + n.h3Server = http3Server + } else if len(n.network) > 1 { + n.logger.Warn(E.Cause(err, "naive http3 disabled")) + } else { + return err + } + } + + return nil +} + +func (n *Inbound) Close() error { + return common.Close( + &n.listener, + common.PtrOrNil(n.httpServer), + n.h3Server, + n.tlsConfig, + ) +} + +func (n *Inbound) ServeHTTP(writer http.ResponseWriter, request *http.Request) { + ctx := log.ContextWithNewID(request.Context()) + if request.Method != "CONNECT" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("not CONNECT request")) + return + } else if request.Header.Get("Padding") == "" { + rejectHTTP(writer, http.StatusBadRequest) + n.badRequest(ctx, request, E.New("missing naive padding")) + return + } + userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) + if authOk { + authOk = n.authenticator.Verify(userName, password) + } + if !authOk { + rejectHTTP(writer, http.StatusProxyAuthRequired) + n.badRequest(ctx, request, E.New("authorization failed")) + return + } + writer.Header().Set("Padding", generateNaivePaddingHeader()) + writer.WriteHeader(http.StatusOK) + writer.(http.Flusher).Flush() + + hostPort := request.URL.Host + if hostPort == "" { + hostPort = request.Host + } + source := sHttp.SourceAddress(request) + destination := M.ParseSocksaddr(hostPort) + + if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { + conn, _, err := hijacker.Hijack() + if err != nil { + n.badRequest(ctx, request, E.New("hijack failed")) + return + } + n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) + } else { + n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) + } +} + +func (n *Inbound) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { + if userName != "" { + n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) + n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) + } else { + n.logger.InfoContext(ctx, "inbound connection from ", source) + n.logger.InfoContext(ctx, "inbound connection to ", destination) + } + var metadata adapter.InboundContext + metadata.Inbound = n.Tag() + metadata.InboundType = n.Type() + metadata.InboundDetour = n.listener.ListenOptions().Detour + metadata.InboundOptions = n.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() + metadata.User = userName + if !waitForClose { + n.router.RouteConnectionEx(ctx, conn, metadata, nil) + } else { + done := make(chan struct{}) + wrapper := v2rayhttp.NewHTTP2Wrapper(conn) + n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { + close(done) + })) + <-done + wrapper.CloseWrapper() + } +} + +func (n *Inbound) badRequest(ctx context.Context, request *http.Request, err error) { + n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) +} + +func rejectHTTP(writer http.ResponseWriter, statusCode int) { + hijacker, ok := writer.(http.Hijacker) + if !ok { + writer.WriteHeader(statusCode) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + writer.WriteHeader(statusCode) + return + } + if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { + tcpConn.SetLinger(0) + } + conn.Close() +} + +func generateNaivePaddingHeader() string { + paddingLen := rand.Intn(32) + 30 + padding := make([]byte, paddingLen) + bits := rand.Uint64() + for i := 0; i < 16; i++ { + // Codes that won't be Huffman coded. + padding[i] = "!#$()+<>?@[]^`{}"[bits&15] + bits >>= 4 + } + for i := 16; i < paddingLen; i++ { + padding[i] = '~' + } + return string(padding) +} diff --git a/inbound/naive.go b/protocol/naive/inbound_conn.go similarity index 58% rename from inbound/naive.go rename to protocol/naive/inbound_conn.go index 498e823c..16944cba 100644 --- a/inbound/naive.go +++ b/protocol/naive/inbound_conn.go @@ -1,7 +1,6 @@ -package inbound +package naive import ( - "context" "encoding/binary" "io" "math/rand" @@ -11,228 +10,12 @@ import ( "strings" "time" - "github.com/sagernet/sing-box/adapter" - "github.com/sagernet/sing-box/common/tls" - "github.com/sagernet/sing-box/common/uot" - C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/log" - "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/transport/v2rayhttp" "github.com/sagernet/sing/common" - "github.com/sagernet/sing/common/auth" "github.com/sagernet/sing/common/buf" - E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" - N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" - sHttp "github.com/sagernet/sing/protocol/http" ) -var _ adapter.Inbound = (*Naive)(nil) - -type Naive struct { - myInboundAdapter - authenticator *auth.Authenticator - tlsConfig tls.ServerConfig - httpServer *http.Server - h3Server any -} - -func NewNaive(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.NaiveInboundOptions) (*Naive, error) { - inbound := &Naive{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeNaive, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - authenticator: auth.NewAuthenticator(options.Users), - } - if common.Contains(inbound.network, N.NetworkUDP) { - if options.TLS == nil || !options.TLS.Enabled { - return nil, E.New("TLS is required for QUIC server") - } - } - if len(options.Users) == 0 { - return nil, E.New("missing users") - } - if options.TLS != nil { - tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) - if err != nil { - return nil, err - } - inbound.tlsConfig = tlsConfig - } - return inbound, nil -} - -func (n *Naive) Start() error { - var tlsConfig *tls.STDConfig - if n.tlsConfig != nil { - err := n.tlsConfig.Start() - if err != nil { - return E.Cause(err, "create TLS config") - } - tlsConfig, err = n.tlsConfig.Config() - if err != nil { - return err - } - } - - if common.Contains(n.network, N.NetworkTCP) { - tcpListener, err := n.ListenTCP() - if err != nil { - return err - } - n.httpServer = &http.Server{ - Handler: n, - TLSConfig: tlsConfig, - BaseContext: func(listener net.Listener) context.Context { - return n.ctx - }, - } - go func() { - var sErr error - if tlsConfig != nil { - sErr = n.httpServer.ServeTLS(tcpListener, "", "") - } else { - sErr = n.httpServer.Serve(tcpListener) - } - if sErr != nil && !E.IsClosedOrCanceled(sErr) { - n.logger.Error("http server serve error: ", sErr) - } - }() - } - - if common.Contains(n.network, N.NetworkUDP) { - err := n.configureHTTP3Listener() - if !C.WithQUIC && len(n.network) > 1 { - n.logger.Warn(E.Cause(err, "naive http3 disabled")) - } else if err != nil { - return err - } - } - - return nil -} - -func (n *Naive) Close() error { - return common.Close( - &n.myInboundAdapter, - common.PtrOrNil(n.httpServer), - n.h3Server, - n.tlsConfig, - ) -} - -func (n *Naive) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - ctx := log.ContextWithNewID(request.Context()) - if request.Method != "CONNECT" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("not CONNECT request")) - return - } else if request.Header.Get("Padding") == "" { - rejectHTTP(writer, http.StatusBadRequest) - n.badRequest(ctx, request, E.New("missing naive padding")) - return - } - userName, password, authOk := sHttp.ParseBasicAuth(request.Header.Get("Proxy-Authorization")) - if authOk { - authOk = n.authenticator.Verify(userName, password) - } - if !authOk { - rejectHTTP(writer, http.StatusProxyAuthRequired) - n.badRequest(ctx, request, E.New("authorization failed")) - return - } - writer.Header().Set("Padding", generateNaivePaddingHeader()) - writer.WriteHeader(http.StatusOK) - writer.(http.Flusher).Flush() - - hostPort := request.URL.Host - if hostPort == "" { - hostPort = request.Host - } - source := sHttp.SourceAddress(request) - destination := M.ParseSocksaddr(hostPort) - - if hijacker, isHijacker := writer.(http.Hijacker); isHijacker { - conn, _, err := hijacker.Hijack() - if err != nil { - n.badRequest(ctx, request, E.New("hijack failed")) - return - } - n.newConnection(ctx, false, &naiveH1Conn{Conn: conn}, userName, source, destination) - } else { - n.newConnection(ctx, true, &naiveH2Conn{reader: request.Body, writer: writer, flusher: writer.(http.Flusher)}, userName, source, destination) - } -} - -func (n *Naive) newConnection(ctx context.Context, waitForClose bool, conn net.Conn, userName string, source M.Socksaddr, destination M.Socksaddr) { - if userName != "" { - n.logger.InfoContext(ctx, "[", userName, "] inbound connection from ", source) - n.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", destination) - } else { - n.logger.InfoContext(ctx, "inbound connection from ", source) - n.logger.InfoContext(ctx, "inbound connection to ", destination) - } - metadata := n.createMetadata(conn, adapter.InboundContext{ - Source: source, - Destination: destination, - User: userName, - }) - if !waitForClose { - n.router.RouteConnectionEx(ctx, conn, metadata, nil) - } else { - done := make(chan struct{}) - wrapper := v2rayhttp.NewHTTP2Wrapper(conn) - n.router.RouteConnectionEx(ctx, conn, metadata, N.OnceClose(func(it error) { - close(done) - })) - <-done - wrapper.CloseWrapper() - } -} - -func (n *Naive) badRequest(ctx context.Context, request *http.Request, err error) { - n.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", request.RemoteAddr)) -} - -func rejectHTTP(writer http.ResponseWriter, statusCode int) { - hijacker, ok := writer.(http.Hijacker) - if !ok { - writer.WriteHeader(statusCode) - return - } - conn, _, err := hijacker.Hijack() - if err != nil { - writer.WriteHeader(statusCode) - return - } - if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP { - tcpConn.SetLinger(0) - } - conn.Close() -} - -func generateNaivePaddingHeader() string { - paddingLen := rand.Intn(32) + 30 - padding := make([]byte, paddingLen) - bits := rand.Uint64() - for i := 0; i < 16; i++ { - // Codes that won't be Huffman coded. - padding[i] = "!#$()+<>?@[]^`{}"[bits&15] - bits >>= 4 - } - for i := 16; i < paddingLen; i++ { - padding[i] = '~' - } - return string(padding) -} - const kFirstPaddings = 8 type naiveH1Conn struct { diff --git a/protocol/naive/quic/inbound_init.go b/protocol/naive/quic/inbound_init.go new file mode 100644 index 00000000..f495c860 --- /dev/null +++ b/protocol/naive/quic/inbound_init.go @@ -0,0 +1,52 @@ +package quic + +import ( + "io" + "net/http" + + "github.com/sagernet/quic-go" + "github.com/sagernet/quic-go/http3" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/tls" + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-quic" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" +) + +func init() { + naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) { + err := qtls.ConfigureHTTP3(tlsConfig) + if err != nil { + return nil, err + } + + udpConn, err := listener.ListenUDP() + if err != nil { + return nil, err + } + + quicListener, err := qtls.ListenEarly(udpConn, tlsConfig, &quic.Config{ + MaxIncomingStreams: 1 << 60, + Allow0RTT: true, + }) + if err != nil { + udpConn.Close() + return nil, err + } + + h3Server := &http3.Server{ + Handler: handler, + } + + go func() { + sErr := h3Server.ServeListener(quicListener) + udpConn.Close() + if sErr != nil && !E.IsClosedOrCanceled(sErr) { + logger.Error("http3 server closed: ", sErr) + } + }() + + return quicListener, nil + } +} diff --git a/protocol/redirect/redirect.go b/protocol/redirect/redirect.go new file mode 100644 index 00000000..71e1fced --- /dev/null +++ b/protocol/redirect/redirect.go @@ -0,0 +1,65 @@ +package redirect + +import ( + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/redir" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" +) + +func RegisterRedirect(registry *inbound.Registry) { + inbound.Register[option.RedirectInboundOptions](registry, C.TypeRedirect, NewRedirect) +} + +type Redirect struct { + inbound.Adapter + router adapter.Router + logger log.ContextLogger + listener *listener.Listener +} + +func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) (adapter.Inbound, error) { + redirect := &Redirect{ + Adapter: inbound.NewAdapter(C.TypeRedirect, tag), + router: router, + logger: logger, + } + redirect.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: redirect, + }) + return redirect, nil +} + +func (h *Redirect) Start() error { + return h.listener.Start() +} + +func (h *Redirect) Close() error { + return h.listener.Close() +} + +func (h *Redirect) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + destination, err := redir.GetOriginalDestination(conn) + if err != nil { + conn.Close() + h.logger.ErrorContext(ctx, "process connection from ", conn.RemoteAddr(), ": get redirect destination: ", err) + return + } + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.Destination = M.SocksaddrFromNetIP(destination) + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/inbound/tproxy.go b/protocol/redirect/tproxy.go similarity index 60% rename from inbound/tproxy.go rename to protocol/redirect/tproxy.go index 40653c79..dee40ec5 100644 --- a/inbound/tproxy.go +++ b/protocol/redirect/tproxy.go @@ -1,4 +1,4 @@ -package inbound +package redirect import ( "context" @@ -8,6 +8,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/redir" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -21,22 +23,25 @@ import ( "github.com/sagernet/sing/common/udpnat2" ) -type TProxy struct { - myInboundAdapter - udpNat *udpnat.Service +func RegisterTProxy(registry *inbound.Registry) { + inbound.Register[option.TProxyInboundOptions](registry, C.TypeTProxy, NewTProxy) } -func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy { +type TProxy struct { + inbound.Adapter + ctx context.Context + router adapter.Router + logger log.ContextLogger + listener *listener.Listener + udpNat *udpnat.Service +} + +func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) (adapter.Inbound, error) { tproxy := &TProxy{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTProxy, - network: options.Network.Build(), - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + Adapter: inbound.NewAdapter(C.TypeTProxy, tag), + ctx: ctx, + router: router, + logger: logger, } var udpTimeout time.Duration if options.UDPTimeout != 0 { @@ -44,28 +49,34 @@ func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLog } else { udpTimeout = C.UDPTimeout } - tproxy.connHandler = tproxy - tproxy.oobPacketHandler = tproxy tproxy.udpNat = udpnat.New(tproxy, tproxy.preparePacketConnection, udpTimeout, false) - return tproxy + tproxy.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: tproxy, + OOBPacketHandler: tproxy, + }) + return tproxy, nil } func (t *TProxy) Start() error { - err := t.myInboundAdapter.Start() + err := t.listener.Start() if err != nil { return err } - if t.tcpListener != nil { - err = control.Conn(common.MustCast[syscall.Conn](t.tcpListener), func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.tcpListener.Addr()).Addr.Is6()) + if listener := t.listener.TCPListener(); listener != nil { + err = control.Conn(common.MustCast[syscall.Conn](listener), func(fd uintptr) error { + return redir.TProxy(fd, M.SocksaddrFromNet(listener.Addr()).Addr.Is6()) }) if err != nil { return E.Cause(err, "configure tproxy TCP listener") } } - if t.udpConn != nil { - err = control.Conn(t.udpConn, func(fd uintptr) error { - return redir.TProxy(fd, M.SocksaddrFromNet(t.udpConn.LocalAddr()).Addr.Is6()) + if conn := t.listener.UDPConn(); conn != nil { + err = control.Conn(conn, func(fd uintptr) error { + return redir.TProxy(fd, M.SocksaddrFromNet(conn.LocalAddr()).Addr.Is6()) }) if err != nil { return E.Cause(err, "configure tproxy UDP listener") @@ -74,13 +85,26 @@ func (t *TProxy) Start() error { return nil } +func (t *TProxy) Close() error { + return t.listener.Close() +} + func (t *TProxy) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap() - t.newConnectionEx(ctx, conn, metadata, onClose) + t.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } func (t *TProxy) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - t.newPacketConnectionEx(ctx, conn, t.createPacketMetadataEx(source, destination), onClose) + t.logger.InfoContext(ctx, "inbound packet connection from ", source) + t.logger.InfoContext(ctx, "inbound packet connection to ", destination) + var metadata adapter.InboundContext + metadata.Inbound = t.Tag() + metadata.InboundType = t.Type() + metadata.Source = source + metadata.Destination = destination + metadata.OriginDestination = t.listener.UDPAddr() + t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } func (t *TProxy) NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr) { @@ -100,8 +124,9 @@ type tproxyPacketWriter struct { } func (t *TProxy) preparePacketConnection(source M.Socksaddr, destination M.Socksaddr, userData any) (bool, context.Context, N.PacketWriter, N.CloseHandlerFunc) { - writer := &tproxyPacketWriter{ctx: t.ctx, source: source.AddrPort(), destination: destination} - return true, t.ctx, writer, func(it error) { + ctx := log.ContextWithNewID(t.ctx) + writer := &tproxyPacketWriter{ctx: ctx, source: source.AddrPort(), destination: destination} + return true, ctx, writer, func(it error) { common.Close(common.PtrOrNil(writer.conn)) } } diff --git a/protocol/shadowsocks/inbound.go b/protocol/shadowsocks/inbound.go new file mode 100644 index 00000000..b23516d9 --- /dev/null +++ b/protocol/shadowsocks/inbound.go @@ -0,0 +1,179 @@ +package shadowsocks + +import ( + "context" + "net" + "time" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/mux" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing-shadowsocks" + "github.com/sagernet/sing-shadowsocks/shadowaead" + "github.com/sagernet/sing-shadowsocks/shadowaead_2022" + "github.com/sagernet/sing/common" + "github.com/sagernet/sing/common/buf" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + M "github.com/sagernet/sing/common/metadata" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/common/ntp" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocks, NewInbound) +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) { + if len(options.Users) > 0 && len(options.Destinations) > 0 { + return nil, E.New("users and destinations options must not be combined") + } + if len(options.Users) > 0 { + return newMultiInbound(ctx, router, logger, tag, options) + } else if len(options.Destinations) > 0 { + return newRelayInbound(ctx, router, logger, tag, options) + } else { + return newInbound(ctx, router, logger, tag, options) + } +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.Service +} + +func newInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + } + var err error + inbound.router, err = mux.NewRouterWithOptions(router, logger, common.PtrValueOrDefault(options.Multiplex)) + if err != nil { + return nil, err + } + var udpTimeout time.Duration + if options.UDPTimeout != 0 { + udpTimeout = time.Duration(options.UDPTimeout) + } else { + udpTimeout = C.UDPTimeout + } + switch { + case options.Method == shadowsocks.MethodNone: + inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead.List, options.Method): + inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound)) + case common.Contains(shadowaead_2022.List, options.Method): + inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), adapter.NewUpstreamHandler(adapter.InboundContext{}, inbound.newConnection, inbound.newPacketConnection, inbound), ntp.TimeFuncFromContext(ctx)) + default: + err = E.New("unsupported method: ", options.Method) + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) + return inbound, err +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) + if err != nil { + h.logger.Error(E.Cause(err, "process packet from ", source)) + } +} + +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) +} + +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + ctx = log.ContextWithNewID(ctx) + h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +var _ N.PacketConn = (*stubPacketConn)(nil) + +type stubPacketConn struct { + N.PacketWriter +} + +func (c *stubPacketConn) ReadPacket(buffer *buf.Buffer) (destination M.Socksaddr, err error) { + panic("stub!") +} + +func (c *stubPacketConn) Close() error { + return nil +} + +func (c *stubPacketConn) LocalAddr() net.Addr { + panic("stub!") +} + +func (c *stubPacketConn) SetDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetReadDeadline(t time.Time) error { + panic("stub!") +} + +func (c *stubPacketConn) SetWriteDeadline(t time.Time) error { + panic("stub!") +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} + +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) +} diff --git a/inbound/shadowsocks_multi.go b/protocol/shadowsocks/inbound_multi.go similarity index 59% rename from inbound/shadowsocks_multi.go rename to protocol/shadowsocks/inbound_multi.go index 29534194..0e1efedf 100644 --- a/inbound/shadowsocks_multi.go +++ b/protocol/shadowsocks/inbound_multi.go @@ -1,4 +1,4 @@ -package inbound +package shadowsocks import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -20,36 +22,31 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) -var ( - _ adapter.Inbound = (*ShadowsocksMulti)(nil) - _ adapter.TCPInjectableInbound = (*ShadowsocksMulti)(nil) -) +var _ adapter.TCPInjectableInbound = (*MultiInbound)(nil) -type ShadowsocksMulti struct { - myInboundAdapter - service shadowsocks.MultiService[int] - users []option.ShadowsocksUser +type MultiInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + service shadowsocks.MultiService[int] + users []option.ShadowsocksUser } -func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksMulti, error) { - inbound := &ShadowsocksMulti{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +func newMultiInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*MultiInbound, error) { + inbound := &MultiInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, } - inbound.connHandler = inbound - inbound.packetHandler = inbound var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { @@ -91,12 +88,28 @@ func newShadowsocksMulti(ctx context.Context, router adapter.Router, logger log. return nil, err } inbound.service = service - inbound.packetUpstream = service inbound.users = options.Users + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) return inbound, err } -func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *MultiInbound) Start() error { + return h.listener.Start() +} + +func (h *MultiInbound) Close() error { + return h.listener.Close() +} + +func (h *MultiInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -104,14 +117,14 @@ func (h *ShadowsocksMulti) NewConnectionEx(ctx context.Context, conn net.Conn, m } } -func (h *ShadowsocksMulti) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) +func (h *MultiInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { h.logger.Error(E.Cause(err, "process packet from ", source)) } } -func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *MultiInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -123,10 +136,12 @@ func (h *ShadowsocksMulti) newConnection(ctx context.Context, conn net.Conn, met metadata.User = user } h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *MultiInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -140,5 +155,13 @@ func (h *ShadowsocksMulti) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *MultiInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) } diff --git a/inbound/shadowsocks_relay.go b/protocol/shadowsocks/inbound_relay.go similarity index 57% rename from inbound/shadowsocks_relay.go rename to protocol/shadowsocks/inbound_relay.go index 02246a3f..5818ca29 100644 --- a/inbound/shadowsocks_relay.go +++ b/protocol/shadowsocks/inbound_relay.go @@ -1,4 +1,4 @@ -package inbound +package shadowsocks import ( "context" @@ -7,6 +7,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -18,36 +20,31 @@ import ( "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*ShadowsocksRelay)(nil) - _ adapter.TCPInjectableInbound = (*ShadowsocksRelay)(nil) -) +var _ adapter.TCPInjectableInbound = (*RelayInbound)(nil) -type ShadowsocksRelay struct { - myInboundAdapter +type RelayInbound struct { + inbound.Adapter + ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *shadowaead_2022.RelayService[int] destinations []option.ShadowsocksDestination } -func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksRelay, error) { - inbound := &ShadowsocksRelay{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +func newRelayInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*RelayInbound, error) { + inbound := &RelayInbound{ + Adapter: inbound.NewAdapter(C.TypeShadowsocks, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, destinations: options.Destinations, } - inbound.connHandler = inbound - inbound.packetHandler = inbound var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) if err != nil { @@ -77,11 +74,27 @@ func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log. return nil, err } inbound.service = service - inbound.packetUpstream = service + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: options.Network.Build(), + Listen: options.ListenOptions, + ConnectionHandler: inbound, + PacketHandler: inbound, + ThreadUnsafePacketWriter: true, + }) return inbound, err } -func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *RelayInbound) Start() error { + return h.listener.Start() +} + +func (h *RelayInbound) Close() error { + return h.listener.Close() +} + +func (h *RelayInbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.service.NewConnection(ctx, conn, adapter.UpstreamMetadata(metadata)) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -89,14 +102,14 @@ func (h *ShadowsocksRelay) NewConnectionEx(ctx context.Context, conn net.Conn, m } } -func (h *ShadowsocksRelay) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { - err := h.service.NewPacket(h.ctx, h.packetConn(), buffer, M.Metadata{Source: source}) +func (h *RelayInbound) NewPacketEx(buffer *buf.Buffer, source M.Socksaddr) { + err := h.service.NewPacket(h.ctx, &stubPacketConn{h.listener.PacketWriter()}, buffer, M.Metadata{Source: source}) if err != nil { h.logger.Error(E.Cause(err, "process packet from ", source)) } } -func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *RelayInbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { destinationIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -108,10 +121,12 @@ func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, met metadata.User = destination } h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination) - return h.router.RouteConnection(ctx, conn, h.createMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *RelayInbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { destinationIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -125,5 +140,13 @@ func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.Packe ctx = log.ContextWithNewID(ctx) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source) h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination) - return h.router.RoutePacketConnection(ctx, conn, h.createPacketMetadata(conn, metadata)) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + return h.router.RoutePacketConnection(ctx, conn, metadata) +} + +func (h *RelayInbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) } diff --git a/outbound/shadowsocks.go b/protocol/shadowsocks/outbound.go similarity index 80% rename from outbound/shadowsocks.go rename to protocol/shadowsocks/outbound.go index 15354274..73b38385 100644 --- a/outbound/shadowsocks.go +++ b/protocol/shadowsocks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package shadowsocks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" C "github.com/sagernet/sing-box/constant" @@ -15,15 +16,19 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" ) -var _ adapter.Outbound = (*Shadowsocks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowsocksOutboundOptions](registry, C.TypeShadowsocks, NewOutbound) +} -type Shadowsocks struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer method shadowsocks.Method serverAddr M.Socksaddr @@ -32,7 +37,7 @@ type Shadowsocks struct { multiplexDialer *mux.Client } -func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (*Shadowsocks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksOutboundOptions) (adapter.Outbound, error) { method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{ Password: options.Password, }) @@ -43,15 +48,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte if err != nil { return nil, err } - outbound := &Shadowsocks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowsocks, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, method: method, serverAddr: options.ServerOptions.Build(), @@ -78,9 +77,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte return outbound, nil } -func (h *Shadowsocks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { switch N.NetworkName(network) { @@ -106,9 +105,9 @@ func (h *Shadowsocks) DialContext(ctx context.Context, network string, destinati } } -func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.multiplexDialer == nil { if h.uotClient != nil { @@ -125,24 +124,24 @@ func (h *Shadowsocks) ListenPacket(ctx context.Context, destination M.Socksaddr) } } -func (h *Shadowsocks) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.multiplexDialer != nil { h.multiplexDialer.Reset() } return } -func (h *Shadowsocks) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer)) } var _ N.Dialer = (*shadowsocksDialer)(nil) -type shadowsocksDialer Shadowsocks +type shadowsocksDialer Outbound func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -170,7 +169,7 @@ func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, des func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) if err != nil { diff --git a/inbound/shadowtls.go b/protocol/shadowtls/inbound.go similarity index 62% rename from inbound/shadowtls.go rename to protocol/shadowtls/inbound.go index ca142286..6887e838 100644 --- a/inbound/shadowtls.go +++ b/protocol/shadowtls/inbound.go @@ -1,11 +1,13 @@ -package inbound +package shadowtls import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/sing-box/common/listener" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" @@ -13,25 +15,27 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" N "github.com/sagernet/sing/common/network" ) -type ShadowTLS struct { - myInboundAdapter - service *shadowtls.Service +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.ShadowTLSInboundOptions](registry, C.TypeShadowTLS, NewInbound) } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (*ShadowTLS, error) { - inbound := &ShadowTLS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, +type Inbound struct { + inbound.Adapter + router adapter.Router + logger logger.ContextLogger + listener *listener.Listener + service *shadowtls.Service +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeShadowTLS, tag), + router: router, + logger: logger, } if options.Version == 0 { @@ -68,22 +72,36 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context }, HandshakeForServerName: handshakeForServerName, StrictMode: options.StrictMode, - Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, inbound), + Handler: adapter.NewUpstreamContextHandler(inbound.newConnection, nil, nil), Logger: logger, }) if err != nil { return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *ShadowTLS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if userName, _ := auth.UserFromContext[string](ctx); userName != "" { metadata.User = userName h.logger.InfoContext(ctx, "[", userName, "] inbound connection to ", metadata.Destination) @@ -93,7 +111,7 @@ func (h *ShadowTLS) newConnection(ctx context.Context, conn net.Conn, metadata a return h.router.RouteConnection(ctx, conn, metadata) } -func (h *ShadowTLS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { diff --git a/outbound/shadowtls.go b/protocol/shadowtls/outbound.go similarity index 74% rename from outbound/shadowtls.go rename to protocol/shadowtls/outbound.go index ff1b9d6c..7d46a8f6 100644 --- a/outbound/shadowtls.go +++ b/protocol/shadowtls/outbound.go @@ -1,4 +1,4 @@ -package outbound +package shadowtls import ( "context" @@ -6,6 +6,7 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -17,23 +18,18 @@ import ( N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*ShadowTLS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.ShadowTLSOutboundOptions](registry, C.TypeShadowTLS, NewOutbound) +} -type ShadowTLS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter client *shadowtls.Client } -func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) { - outbound := &ShadowTLS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeShadowTLS, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, []string{N.NetworkTCP}, tag, options.DialerOptions), } if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -91,9 +87,9 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -103,6 +99,6 @@ func (h *ShadowTLS) DialContext(ctx context.Context, network string, destination } } -func (h *ShadowTLS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/protocol/socks/inbound.go b/protocol/socks/inbound.go new file mode 100644 index 00000000..29649a88 --- /dev/null +++ b/protocol/socks/inbound.go @@ -0,0 +1,91 @@ +package socks + +import ( + std_bufio "bufio" + "context" + "net" + + "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" + "github.com/sagernet/sing-box/common/uot" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/log" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/auth" + E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" + N "github.com/sagernet/sing/common/network" + "github.com/sagernet/sing/protocol/socks" +) + +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.SocksInboundOptions](registry, C.TypeSOCKS, NewInbound) +} + +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener + authenticator *auth.Authenticator +} + +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeSOCKS, tag), + router: uot.NewRouter(router, logger), + logger: logger, + authenticator: auth.NewAuthenticator(options.Users), + } + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) + return inbound, nil +} + +func (h *Inbound) Start() error { + return h.listener.Start() +} + +func (h *Inbound) Close() error { + return h.listener.Close() +} + +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + err := socks.HandleConnectionEx(ctx, conn, std_bufio.NewReader(conn), h.authenticator, nil, adapter.NewUpstreamHandlerEx(metadata, h.newUserConnection, h.streamUserPacketConnection), metadata.Source, metadata.Destination, onClose) + N.CloseOnHandshakeFailure(conn, onClose, err) + if err != nil { + h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source)) + } +} + +func (h *Inbound) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination) + h.router.RouteConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { + user, loaded := auth.UserFromContext[string](ctx) + if !loaded { + h.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) + return + } + metadata.User = user + h.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination) + h.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) +} diff --git a/outbound/socks.go b/protocol/socks/outbound.go similarity index 65% rename from outbound/socks.go rename to protocol/socks/outbound.go index 575d6eb3..0194800a 100644 --- a/outbound/socks.go +++ b/protocol/socks/outbound.go @@ -1,10 +1,11 @@ -package outbound +package socks import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -12,22 +13,29 @@ import ( "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/protocol/socks" ) -var _ adapter.Outbound = (*Socks)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SOCKSOutboundOptions](registry, C.TypeSOCKS, NewOutbound) +} -type Socks struct { - myOutboundAdapter +var _ adapter.Outbound = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + router adapter.Router + logger logger.ContextLogger client *socks.Client resolve bool uotClient *uot.Client } -func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, options option.SocksOutboundOptions) (*Socks, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SOCKSOutboundOptions) (adapter.Outbound, error) { var version socks.Version var err error if options.Version != "" { @@ -42,15 +50,10 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio if err != nil { return nil, err } - outbound := &Socks{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSOCKS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, options.Network.Build(), tag, options.DialerOptions), + router: router, + logger: logger, client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), resolve: version == socks.Version4, } @@ -64,9 +67,9 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio return outbound, nil } -func (h *Socks) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination switch N.NetworkName(network) { case N.NetworkTCP: @@ -90,9 +93,9 @@ func (h *Socks) DialContext(ctx context.Context, network string, destination M.S return h.client.DialContext(ctx, network, destination) } -func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination if h.uotClient != nil { h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) @@ -115,20 +118,20 @@ func (h *Socks) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. // TODO // Deprecated -func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { if h.resolve { - return NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) + return outbound.NewDirectConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) } else { - return NewConnection(ctx, h, conn, metadata) + return outbound.NewConnection(ctx, h, conn, metadata) } } // TODO // Deprecated -func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { if h.resolve { - return NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) + return outbound.NewDirectPacketConnection(ctx, h.router, h, conn, metadata, dns.DomainStrategyUseIPv4) } else { - return NewPacketConnection(ctx, h, conn, metadata) + return outbound.NewPacketConnection(ctx, h, conn, metadata) } } diff --git a/outbound/ssh.go b/protocol/ssh/outbound.go similarity index 80% rename from outbound/ssh.go rename to protocol/ssh/outbound.go index 28abe9a5..62a2a8d9 100644 --- a/outbound/ssh.go +++ b/protocol/ssh/outbound.go @@ -1,4 +1,4 @@ -package outbound +package ssh import ( "bytes" @@ -12,26 +12,30 @@ import ( "sync" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "golang.org/x/crypto/ssh" ) -var ( - _ adapter.Outbound = (*SSH)(nil) - _ adapter.InterfaceUpdateListener = (*SSH)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound) +} -type SSH struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr user string @@ -44,21 +48,15 @@ type SSH struct { client *ssh.Client } -func NewSSH(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (*SSH, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SSHOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &SSH{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeSSH, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, []string{N.NetworkTCP}, tag, options.DialerOptions), ctx: ctx, + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), user: options.User, @@ -122,7 +120,7 @@ func randomVersion() string { return version } -func (s *SSH) connect() (*ssh.Client, error) { +func (s *Outbound) connect() (*ssh.Client, error) { if s.client != nil { return s.client, nil } @@ -179,16 +177,16 @@ func (s *SSH) connect() (*ssh.Client, error) { return client, nil } -func (s *SSH) InterfaceUpdated() { +func (s *Outbound) InterfaceUpdated() { common.Close(s.clientConn) return } -func (s *SSH) Close() error { +func (s *Outbound) Close() error { return common.Close(s.clientConn) } -func (s *SSH) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (s *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { client, err := s.connect() if err != nil { return nil, err @@ -196,6 +194,6 @@ func (s *SSH) DialContext(ctx context.Context, network string, destination M.Soc return client.Dial(network, destination.String()) } -func (s *SSH) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (s *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/outbound/tor.go b/protocol/tor/outbound.go similarity index 83% rename from outbound/tor.go rename to protocol/tor/outbound.go index ccc0c0cf..89a295b8 100644 --- a/outbound/tor.go +++ b/protocol/tor/outbound.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -8,6 +8,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -15,6 +16,7 @@ import ( "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" @@ -24,11 +26,14 @@ import ( "github.com/cretz/bine/tor" ) -var _ adapter.Outbound = (*Tor)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TorOutboundOptions](registry, C.TypeTor, NewOutbound) +} -type Tor struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter ctx context.Context + logger logger.ContextLogger proxy *ProxyListener startConf *tor.StartConf options map[string]string @@ -37,8 +42,8 @@ type Tor struct { socksClient *socks.Client } -func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) { - startConf := newConfig() +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) { + var startConf tor.StartConf startConf.DataDir = os.ExpandEnv(options.DataDirectory) startConf.TempDataDirBase = os.TempDir() startConf.ExtraArgs = options.ExtraArgs @@ -74,23 +79,17 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger if err != nil { return nil, err } - return &Tor{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTor, - network: []string{N.NetworkTCP}, - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, []string{N.NetworkTCP}, tag, options.DialerOptions), ctx: ctx, + logger: logger, proxy: NewProxyListener(ctx, logger, outboundDialer), startConf: &startConf, options: options.Options, }, nil } -func (t *Tor) Start() error { +func (t *Outbound) Start() error { err := t.start() if err != nil { t.Close() @@ -106,7 +105,7 @@ var torLogEvents = []control.EventCode{ control.EventCodeLogWarn, } -func (t *Tor) start() error { +func (t *Outbound) start() error { torInstance, err := tor.Start(t.ctx, t.startConf) if err != nil { return E.New(strings.ToLower(err.Error())) @@ -168,7 +167,7 @@ func (t *Tor) start() error { return nil } -func (t *Tor) recvLoop() { +func (t *Outbound) recvLoop() { for rawEvent := range t.events { switch event := rawEvent.(type) { case *control.LogEvent: @@ -191,7 +190,7 @@ func (t *Tor) recvLoop() { } } -func (t *Tor) Close() error { +func (t *Outbound) Close() error { err := common.Close( common.PtrOrNil(t.proxy), common.PtrOrNil(t.instance), @@ -203,11 +202,11 @@ func (t *Tor) Close() error { return err } -func (t *Tor) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (t *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { t.logger.InfoContext(ctx, "outbound connection to ", destination) return t.socksClient.DialContext(ctx, network, destination) } -func (t *Tor) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (t *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } diff --git a/outbound/proxy.go b/protocol/tor/proxy.go similarity index 94% rename from outbound/proxy.go rename to protocol/tor/proxy.go index 38c18453..ef60bd1f 100644 --- a/outbound/proxy.go +++ b/protocol/tor/proxy.go @@ -1,4 +1,4 @@ -package outbound +package tor import ( "context" @@ -7,6 +7,7 @@ import ( "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/auth" @@ -106,7 +107,7 @@ func (l *ProxyListener) NewConnection(ctx context.Context, conn net.Conn, upstre metadata.Network = N.NetworkTCP metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy connection to ", metadata.Destination) - return NewConnection(ctx, l.dialer, conn, metadata) + return outbound.NewConnection(ctx, l.dialer, conn, metadata) } func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketConn, upstreamMetadata M.Metadata) error { @@ -114,5 +115,5 @@ func (l *ProxyListener) NewPacketConnection(ctx context.Context, conn N.PacketCo metadata.Network = N.NetworkUDP metadata.Destination = upstreamMetadata.Destination l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.Destination) - return NewPacketConnection(ctx, l.dialer, conn, metadata) + return outbound.NewPacketConnection(ctx, l.dialer, conn, metadata) } diff --git a/inbound/trojan.go b/protocol/trojan/inbound.go similarity index 68% rename from inbound/trojan.go rename to protocol/trojan/inbound.go index ce003dda..010ae8ba 100644 --- a/inbound/trojan.go +++ b/protocol/trojan/inbound.go @@ -1,4 +1,4 @@ -package inbound +package trojan import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -21,13 +23,17 @@ import ( N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*Trojan)(nil) - _ adapter.TCPInjectableInbound = (*Trojan)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TrojanInboundOptions](registry, C.TypeTrojan, NewInbound) +} -type Trojan struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener service *trojan.Service[int] users []option.TrojanUser tlsConfig tls.ServerConfig @@ -36,18 +42,12 @@ type Trojan struct { transport adapter.V2RayServerTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (*Trojan, error) { - inbound := &Trojan{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTrojan, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: router, - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTrojan, tag), + router: router, + logger: logger, + users: options.Users, } if options.TLS != nil { tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) @@ -80,7 +80,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog } fallbackHandler = adapter.NewUpstreamContextHandler(inbound.fallbackConnection, nil, nil) } - service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound), fallbackHandler) + service := trojan.NewService[int](adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, nil), fallbackHandler, logger) err := service.UpdateUsers(common.MapIndexed(options.Users, func(index int, it option.TrojanUser) int { return index }), common.Map(options.Users, func(it option.TrojanUser) string { @@ -90,7 +90,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*trojanTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } @@ -100,11 +100,17 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return nil, err } inbound.service = service - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *Trojan) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -112,10 +118,10 @@ func (h *Trojan) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -127,7 +133,7 @@ func (h *Trojan) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -141,15 +147,15 @@ func (h *Trojan) Start() error { return nil } -func (h *Trojan) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -160,7 +166,7 @@ func (h *Trojan) NewConnection(ctx context.Context, conn net.Conn, metadata adap return h.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -168,7 +174,7 @@ func (h *Trojan) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ad } } -func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -183,7 +189,7 @@ func (h *Trojan) newConnection(ctx context.Context, conn net.Conn, metadata adap return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) fallbackConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var fallbackAddr M.Socksaddr if len(h.fallbackAddrTLSNextProto) > 0 { if tlsConn, loaded := common.Cast[tls.Conn](conn); loaded { @@ -206,7 +212,7 @@ func (h *Trojan) fallbackConnection(ctx context.Context, conn net.Conn, metadata return h.router.RouteConnection(ctx, conn, metadata) } -func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -221,10 +227,18 @@ func (h *Trojan) newPacketConnection(ctx context.Context, conn N.PacketConn, met return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*trojanTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type trojanTransportHandler Trojan +type inboundTransportHandler Inbound -func (t *trojanTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - (*Trojan)(t).routeTCP(ctx, conn, source, destination, onClose) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) } diff --git a/outbound/trojan.go b/protocol/trojan/outbound.go similarity index 79% rename from outbound/trojan.go rename to protocol/trojan/outbound.go index ee0b2a4b..f64c48c3 100644 --- a/outbound/trojan.go +++ b/protocol/trojan/outbound.go @@ -1,10 +1,11 @@ -package outbound +package trojan import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,14 +17,18 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*Trojan)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound) +} -type Trojan struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer serverAddr M.Socksaddr key [56]byte @@ -32,20 +37,14 @@ type Trojan struct { transport adapter.V2RayClientTransport } -func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (*Trojan, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TrojanOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &Trojan{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTrojan, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), key: trojan.Key(options.Password), @@ -69,7 +68,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog return outbound, nil } -func (h *Trojan) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -89,7 +88,7 @@ func (h *Trojan) DialContext(ctx context.Context, network string, destination M. } } -func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*trojanDialer)(h).ListenPacket(ctx, destination) @@ -99,7 +98,7 @@ func (h *Trojan) ListenPacket(ctx context.Context, destination M.Socksaddr) (net } } -func (h *Trojan) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -109,15 +108,15 @@ func (h *Trojan) InterfaceUpdated() { return } -func (h *Trojan) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type trojanDialer Trojan +type trojanDialer Outbound func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/inbound/tuic.go b/protocol/tuic/inbound.go similarity index 68% rename from inbound/tuic.go rename to protocol/tuic/inbound.go index b067c43c..33de10d5 100644 --- a/inbound/tuic.go +++ b/protocol/tuic/inbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package inbound +package tuic import ( "context" @@ -8,6 +6,8 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" C "github.com/sagernet/sing-box/constant" @@ -22,16 +22,21 @@ import ( "github.com/gofrs/uuid/v5" ) -var _ adapter.Inbound = (*TUIC)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, NewInbound) +} -type TUIC struct { - myInboundAdapter +type Inbound struct { + inbound.Adapter + router adapter.ConnectionRouterEx + logger log.ContextLogger + listener *listener.Listener tlsConfig tls.ServerConfig server *tuic.Service[int] userNameList []string } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (*TUIC, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -40,16 +45,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - inbound := &TUIC{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeTUIC, - network: []string{N.NetworkUDP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeTUIC, tag), + router: uot.NewRouter(router, logger), + logger: logger, + listener: listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Listen: options.ListenOptions, + }), tlsConfig: tlsConfig, } var udpTimeout time.Duration @@ -95,9 +99,12 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge return inbound, nil } -func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -109,9 +116,13 @@ func (h *TUIC) newConnection(ctx context.Context, conn net.Conn, metadata adapte return h.router.RouteConnection(ctx, conn, metadata) } -func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { ctx = log.ContextWithNewID(ctx) - metadata = h.createPacketMetadata(conn, metadata) + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.OriginDestination = h.listener.UDPAddr() h.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source) userID, _ := auth.UserFromContext[int](ctx) if userName := h.userNameList[userID]; userName != "" { @@ -123,23 +134,23 @@ func (h *TUIC) newPacketConnection(ctx context.Context, conn N.PacketConn, metad return h.router.RoutePacketConnection(ctx, conn, metadata) } -func (h *TUIC) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { return err } } - packetConn, err := h.myInboundAdapter.ListenUDP() + packetConn, err := h.listener.ListenUDP() if err != nil { return err } return h.server.Start(packetConn) } -func (h *TUIC) Close() error { +func (h *Inbound) Close() error { return common.Close( - &h.myInboundAdapter, + &h.listener, h.tlsConfig, common.PtrOrNil(h.server), ) diff --git a/outbound/tuic.go b/protocol/tuic/outbound.go similarity index 76% rename from outbound/tuic.go rename to protocol/tuic/outbound.go index aaf998b1..691d1658 100644 --- a/outbound/tuic.go +++ b/protocol/tuic/outbound.go @@ -1,6 +1,4 @@ -//go:build with_quic - -package outbound +package tuic import ( "context" @@ -9,6 +7,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/tls" C "github.com/sagernet/sing-box/constant" @@ -18,6 +17,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/uot" @@ -25,18 +25,20 @@ import ( "github.com/gofrs/uuid/v5" ) -var ( - _ adapter.Outbound = (*TUIC)(nil) - _ adapter.InterfaceUpdateListener = (*TUIC)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound) +} -type TUIC struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger client *tuic.Client udpStream bool } -func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (*TUIC, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) { options.UDPFragmentDefault = true if options.TLS == nil || !options.TLS.Enabled { return nil, C.ErrTLSRequired @@ -77,21 +79,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge if err != nil { return nil, err } - return &TUIC{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeTUIC, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + return &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, options.Network.Build(), tag, options.DialerOptions), + logger: logger, client: client, udpStream: options.UDPOverStream, }, nil } -func (h *TUIC) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch N.NetworkName(network) { case N.NetworkTCP: h.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -119,7 +115,7 @@ func (h *TUIC) DialContext(ctx context.Context, network string, destination M.So } } -func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.udpStream { h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination) streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version)) @@ -136,10 +132,10 @@ func (h *TUIC) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.P } } -func (h *TUIC) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { _ = h.client.CloseWithError(E.New("network changed")) } -func (h *TUIC) Close() error { +func (h *Outbound) Close() error { return h.client.CloseWithError(os.ErrClosed) } diff --git a/inbound/tun.go b/protocol/tun/inbound.go similarity index 92% rename from inbound/tun.go rename to protocol/tun/inbound.go index 11b16428..ff679c8e 100644 --- a/inbound/tun.go +++ b/protocol/tun/inbound.go @@ -1,4 +1,4 @@ -package inbound +package tun import ( "context" @@ -11,6 +11,7 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/common/taskmonitor" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental/deprecated" @@ -24,13 +25,16 @@ import ( N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ranges" "github.com/sagernet/sing/common/x/list" + "github.com/sagernet/sing/service" "go4.org/netipx" ) -var _ adapter.Inbound = (*TUN)(nil) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.TunInboundOptions](registry, C.TypeTun, NewInbound) +} -type TUN struct { +type Inbound struct { tag string ctx context.Context router adapter.Router @@ -55,7 +59,7 @@ type TUN struct { routeExcludeAddressSet []*netipx.IPSet } -func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions, platformInterface platform.Interface) (*TUN, error) { +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TunInboundOptions) (adapter.Inbound, error) { address := options.Address var deprecatedAddressUsed bool //nolint:staticcheck @@ -164,7 +168,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger outputMark = tun.DefaultAutoRedirectOutputMark } - inbound := &TUN{ + inbound := &Inbound{ tag: tag, ctx: ctx, router: router, @@ -198,7 +202,7 @@ func NewTun(ctx context.Context, router adapter.Router, logger log.ContextLogger endpointIndependentNat: options.EndpointIndependentNat, udpTimeout: udpTimeout, stack: options.Stack, - platformInterface: platformInterface, + platformInterface: service.FromContext[platform.Interface](ctx), platformOptions: common.PtrValueOrDefault(options.Platform), } if options.AutoRedirect { @@ -285,15 +289,15 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges. return uidRanges, nil } -func (t *TUN) Type() string { +func (t *Inbound) Type() string { return C.TypeTun } -func (t *TUN) Tag() string { +func (t *Inbound) Tag() string { return t.tag } -func (t *TUN) Start() error { +func (t *Inbound) Start() error { if C.IsAndroid && t.platformInterface == nil { t.tunOptions.BuildAndroidRules(t.router.PackageManager()) } @@ -350,7 +354,7 @@ func (t *TUN) Start() error { return nil } -func (t *TUN) PostStart() error { +func (t *Inbound) PostStart() error { monitor := taskmonitor.New(t.logger, C.StartTimeout) if t.autoRedirect != nil { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) @@ -389,7 +393,7 @@ func (t *TUN) PostStart() error { return nil } -func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { +func (t *Inbound) updateRouteAddressSet(it adapter.RuleSet) { t.routeAddressSet = common.FlatMap(t.routeRuleSet, adapter.RuleSet.ExtractIPSet) t.routeExcludeAddressSet = common.FlatMap(t.routeExcludeRuleSet, adapter.RuleSet.ExtractIPSet) t.autoRedirect.UpdateRouteAddressSet() @@ -397,7 +401,7 @@ func (t *TUN) updateRouteAddressSet(it adapter.RuleSet) { t.routeExcludeAddressSet = nil } -func (t *TUN) Close() error { +func (t *Inbound) Close() error { return common.Close( t.tunStack, t.tunIf, @@ -405,7 +409,7 @@ func (t *TUN) Close() error { ) } -func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { +func (t *Inbound) PrepareConnection(network string, source M.Socksaddr, destination M.Socksaddr) error { return t.router.PreMatch(adapter.InboundContext{ Inbound: t.tag, InboundType: C.TypeTun, @@ -416,7 +420,7 @@ func (t *TUN) PrepareConnection(network string, source M.Socksaddr, destination }) } -func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { +func (t *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag @@ -429,7 +433,7 @@ func (t *TUN) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socks t.router.RouteConnectionEx(ctx, conn, metadata, onClose) } -func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { +func (t *Inbound) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) var metadata adapter.InboundContext metadata.Inbound = t.tag @@ -442,7 +446,7 @@ func (t *TUN) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, sour t.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose) } -type autoRedirectHandler TUN +type autoRedirectHandler Inbound func (t *autoRedirectHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { ctx = log.ContextWithNewID(ctx) diff --git a/inbound/vless.go b/protocol/vless/inbound.go similarity index 60% rename from inbound/vless.go rename to protocol/vless/inbound.go index ec26bd88..0641549b 100644 --- a/inbound/vless.go +++ b/protocol/vless/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vless import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -20,37 +22,36 @@ import ( "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var ( - _ adapter.Inbound = (*VLESS)(nil) - _ adapter.TCPInjectableInbound = (*VLESS)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VLESSInboundOptions](registry, C.TypeVLESS, NewInbound) +} -type VLESS struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener users []option.VLESSUser service *vless.Service[int] tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (*VLESS, error) { - inbound := &VLESS{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVLESS, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVLESS, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -73,16 +74,22 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vlessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VLESS) Start() error { +func (h *Inbound) Start() error { if h.tlsConfig != nil { err := h.tlsConfig.Start() if err != nil { @@ -90,10 +97,10 @@ func (h *VLESS) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -105,7 +112,7 @@ func (h *VLESS) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -119,16 +126,16 @@ func (h *VLESS) Start() error { return nil } -func (h *VLESS) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -139,7 +146,7 @@ func (h *VLESS) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -147,7 +154,7 @@ func (h *VLESS) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ada } } -func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -162,7 +169,7 @@ func (h *VLESS) newConnection(ctx context.Context, conn net.Conn, metadata adapt return h.router.RouteConnection(ctx, conn, metadata) } -func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -183,10 +190,32 @@ func (h *VLESS) newPacketConnection(ctx context.Context, conn N.PacketConn, meta return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*vlessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type vlessTransportHandler VLESS +type inboundTransportHandler Inbound -func (t *vlessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - t.routeTCP(ctx, conn, source, destination, onClose) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} + +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) } diff --git a/outbound/vless.go b/protocol/vless/outbound.go similarity index 84% rename from outbound/vless.go rename to protocol/vless/outbound.go index 536a1e8f..1074549e 100644 --- a/outbound/vless.go +++ b/protocol/vless/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vless import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -17,14 +18,18 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) -var _ adapter.Outbound = (*VLESS)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound) +} -type VLESS struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vless.Client serverAddr M.Socksaddr @@ -35,20 +40,14 @@ type VLESS struct { xudp bool } -func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (*VLESS, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VLESSOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &VLESS{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVLESS, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } @@ -88,7 +87,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VLESS) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -108,7 +107,7 @@ func (h *VLESS) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vlessDialer)(h).ListenPacket(ctx, destination) @@ -118,7 +117,7 @@ func (h *VLESS) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -func (h *VLESS) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -128,15 +127,15 @@ func (h *VLESS) InterfaceUpdated() { return } -func (h *VLESS) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -type vlessDialer VLESS +type vlessDialer Outbound func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error @@ -179,7 +178,7 @@ func (h *vlessDialer) DialContext(ctx context.Context, network string, destinati func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/inbound/vmess.go b/protocol/vmess/inbound.go similarity index 62% rename from inbound/vmess.go rename to protocol/vmess/inbound.go index 9099bd62..1c80f376 100644 --- a/inbound/vmess.go +++ b/protocol/vmess/inbound.go @@ -1,4 +1,4 @@ -package inbound +package vmess import ( "context" @@ -6,6 +6,8 @@ import ( "os" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/common/listener" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/uot" @@ -19,38 +21,37 @@ import ( "github.com/sagernet/sing/common/auth" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) -var ( - _ adapter.Inbound = (*VMess)(nil) - _ adapter.TCPInjectableInbound = (*VMess)(nil) -) +func RegisterInbound(registry *inbound.Registry) { + inbound.Register[option.VMessInboundOptions](registry, C.TypeVMess, NewInbound) +} -type VMess struct { - myInboundAdapter +var _ adapter.TCPInjectableInbound = (*Inbound)(nil) + +type Inbound struct { + inbound.Adapter ctx context.Context + router adapter.ConnectionRouterEx + logger logger.ContextLogger + listener *listener.Listener service *vmess.Service[int] users []option.VMessUser tlsConfig tls.ServerConfig transport adapter.V2RayServerTransport } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (*VMess, error) { - inbound := &VMess{ - myInboundAdapter: myInboundAdapter{ - protocol: C.TypeVMess, - network: []string{N.NetworkTCP}, - ctx: ctx, - router: uot.NewRouter(router, logger), - logger: logger, - tag: tag, - listenOptions: options.ListenOptions, - }, - ctx: ctx, - users: options.Users, +func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessInboundOptions) (adapter.Inbound, error) { + inbound := &Inbound{ + Adapter: inbound.NewAdapter(C.TypeVMess, tag), + ctx: ctx, + router: uot.NewRouter(router, logger), + logger: logger, + users: options.Users, } var err error inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) @@ -83,16 +84,22 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg } } if options.Transport != nil { - inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*vmessTransportHandler)(inbound)) + inbound.transport, err = v2ray.NewServerTransport(ctx, logger, common.PtrValueOrDefault(options.Transport), inbound.tlsConfig, (*inboundTransportHandler)(inbound)) if err != nil { return nil, E.Cause(err, "create server transport: ", options.Transport.Type) } } - inbound.connHandler = inbound + inbound.listener = listener.New(listener.Options{ + Context: ctx, + Logger: logger, + Network: []string{N.NetworkTCP}, + Listen: options.ListenOptions, + ConnectionHandler: inbound, + }) return inbound, nil } -func (h *VMess) Start() error { +func (h *Inbound) Start() error { err := h.service.Start() if err != nil { return err @@ -104,10 +111,10 @@ func (h *VMess) Start() error { } } if h.transport == nil { - return h.myInboundAdapter.Start() + return h.listener.Start() } if common.Contains(h.transport.Network(), N.NetworkTCP) { - tcpListener, err := h.myInboundAdapter.ListenTCP() + tcpListener, err := h.listener.ListenTCP() if err != nil { return err } @@ -119,7 +126,7 @@ func (h *VMess) Start() error { }() } if common.Contains(h.transport.Network(), N.NetworkUDP) { - udpConn, err := h.myInboundAdapter.ListenUDP() + udpConn, err := h.listener.ListenUDP() if err != nil { return err } @@ -133,16 +140,16 @@ func (h *VMess) Start() error { return nil } -func (h *VMess) Close() error { +func (h *Inbound) Close() error { return common.Close( h.service, - &h.myInboundAdapter, + &h.listener, h.tlsConfig, h.transport, ) } -func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { var err error if h.tlsConfig != nil && h.transport == nil { conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig) @@ -153,7 +160,7 @@ func (h *VMess) NewConnection(ctx context.Context, conn net.Conn, metadata adapt return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata)) } -func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { +func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { err := h.NewConnection(ctx, conn, metadata) N.CloseOnHandshakeFailure(conn, onClose, err) if err != nil { @@ -161,7 +168,7 @@ func (h *VMess) NewConnectionEx(ctx context.Context, conn net.Conn, metadata ada } } -func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { +func (h *Inbound) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -176,7 +183,7 @@ func (h *VMess) newConnection(ctx context.Context, conn net.Conn, metadata adapt return h.router.RouteConnection(ctx, conn, metadata) } -func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { +func (h *Inbound) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { userIndex, loaded := auth.UserFromContext[int](ctx) if !loaded { return os.ErrInvalid @@ -197,10 +204,32 @@ func (h *VMess) newPacketConnection(ctx context.Context, conn N.PacketConn, meta return h.router.RoutePacketConnection(ctx, conn, metadata) } -var _ adapter.V2RayServerTransportHandler = (*vmessTransportHandler)(nil) +var _ adapter.V2RayServerTransportHandler = (*inboundTransportHandler)(nil) -type vmessTransportHandler VMess +type inboundTransportHandler Inbound -func (t *vmessTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { - (*VMess)(t).routeTCP(ctx, conn, source, destination, onClose) +func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) { + var metadata adapter.InboundContext + metadata.Inbound = h.Tag() + metadata.InboundType = h.Type() + metadata.InboundDetour = h.listener.ListenOptions().Detour + metadata.InboundOptions = h.listener.ListenOptions().InboundOptions + metadata.Source = source + metadata.Destination = destination + h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) + (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) +} + +func (h *Inbound) NewError(ctx context.Context, err error) { + NewError(h.logger, ctx, err) +} + +// Deprecated: remove +func NewError(logger logger.ContextLogger, ctx context.Context, err error) { + common.Close(err) + if E.IsClosedOrCanceled(err) { + logger.DebugContext(ctx, "connection closed: ", err) + return + } + logger.ErrorContext(ctx, err) } diff --git a/outbound/vmess.go b/protocol/vmess/outbound.go similarity index 84% rename from outbound/vmess.go rename to protocol/vmess/outbound.go index 126d2fd0..759ea8ba 100644 --- a/outbound/vmess.go +++ b/protocol/vmess/outbound.go @@ -1,10 +1,11 @@ -package outbound +package vmess import ( "context" "net" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/tls" @@ -16,15 +17,19 @@ import ( "github.com/sagernet/sing-vmess/packetaddr" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/ntp" ) -var _ adapter.Outbound = (*VMess)(nil) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.VMessOutboundOptions](registry, C.TypeVMess, NewOutbound) +} -type VMess struct { - myOutboundAdapter +type Outbound struct { + outbound.Adapter + logger logger.ContextLogger dialer N.Dialer client *vmess.Client serverAddr M.Socksaddr @@ -35,20 +40,14 @@ type VMess struct { xudp bool } -func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (*VMess, error) { +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.VMessOutboundOptions) (adapter.Outbound, error) { outboundDialer, err := dialer.New(router, options.DialerOptions) if err != nil { return nil, err } - outbound := &VMess{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeVMess, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, options.Network.Build(), tag, options.DialerOptions), + logger: logger, dialer: outboundDialer, serverAddr: options.ServerOptions.Build(), } @@ -102,7 +101,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg return outbound, nil } -func (h *VMess) InterfaceUpdated() { +func (h *Outbound) InterfaceUpdated() { if h.transport != nil { h.transport.Close() } @@ -112,11 +111,11 @@ func (h *VMess) InterfaceUpdated() { return } -func (h *VMess) Close() error { +func (h *Outbound) Close() error { return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) } -func (h *VMess) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if h.multiplexDialer == nil { switch N.NetworkName(network) { case N.NetworkTCP: @@ -136,7 +135,7 @@ func (h *VMess) DialContext(ctx context.Context, network string, destination M.S } } -func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { if h.multiplexDialer == nil { h.logger.InfoContext(ctx, "outbound packet connection to ", destination) return (*vmessDialer)(h).ListenPacket(ctx, destination) @@ -146,11 +145,11 @@ func (h *VMess) ListenPacket(ctx context.Context, destination M.Socksaddr) (net. } } -type vmessDialer VMess +type vmessDialer Outbound func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error @@ -178,7 +177,7 @@ func (h *vmessDialer) DialContext(ctx context.Context, network string, destinati func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ctx, metadata := adapter.ExtendContext(ctx) - metadata.Outbound = h.tag + metadata.Outbound = h.Tag() metadata.Destination = destination var conn net.Conn var err error diff --git a/protocol/wireguard/init.go b/protocol/wireguard/init.go new file mode 100644 index 00000000..848c113b --- /dev/null +++ b/protocol/wireguard/init.go @@ -0,0 +1,10 @@ +package wireguard + +import ( + "github.com/sagernet/sing-box/common/dialer" + "github.com/sagernet/wireguard-go/conn" +) + +func init() { + dialer.WgControlFns = conn.ControlFns +} diff --git a/outbound/wireguard.go b/protocol/wireguard/outbound.go similarity index 74% rename from outbound/wireguard.go rename to protocol/wireguard/outbound.go index 8eb043f4..7251de9e 100644 --- a/outbound/wireguard.go +++ b/protocol/wireguard/outbound.go @@ -1,6 +1,4 @@ -//go:build with_wireguard - -package outbound +package wireguard import ( "context" @@ -12,6 +10,7 @@ import ( "strings" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/dialer" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/log" @@ -21,6 +20,7 @@ import ( "github.com/sagernet/sing-tun" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/x/list" @@ -30,14 +30,17 @@ import ( "github.com/sagernet/wireguard-go/device" ) -var ( - _ adapter.Outbound = (*WireGuard)(nil) - _ adapter.InterfaceUpdateListener = (*WireGuard)(nil) -) +func RegisterOutbound(registry *outbound.Registry) { + outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound) +} -type WireGuard struct { - myOutboundAdapter +var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) + +type Outbound struct { + outbound.Adapter ctx context.Context + router adapter.Router + logger logger.ContextLogger workers int peers []wireguard.PeerConfig useStdNetBind bool @@ -51,17 +54,12 @@ type WireGuard struct { tunDevice wireguard.Device } -func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { - outbound := &WireGuard{ - myOutboundAdapter: myOutboundAdapter{ - protocol: C.TypeWireGuard, - network: options.Network.Build(), - router: router, - logger: logger, - tag: tag, - dependencies: withDialerDependency(options.DialerOptions), - }, +func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) { + outbound := &Outbound{ + Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, options.Network.Build(), tag, options.DialerOptions), ctx: ctx, + router: router, + logger: logger, workers: options.Workers, pauseManager: service.FromContext[pause.Manager](ctx), } @@ -111,7 +109,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context return outbound, nil } -func (w *WireGuard) Start() error { +func (w *Outbound) Start() error { if common.Any(w.peers, func(peer wireguard.PeerConfig) bool { return !peer.Endpoint.IsValid() }) { @@ -121,7 +119,7 @@ func (w *WireGuard) Start() error { return w.start() } -func (w *WireGuard) PostStart() error { +func (w *Outbound) PostStart() error { if common.All(w.peers, func(peer wireguard.PeerConfig) bool { return peer.Endpoint.IsValid() }) { @@ -130,7 +128,7 @@ func (w *WireGuard) PostStart() error { return w.start() } -func (w *WireGuard) start() error { +func (w *Outbound) start() error { err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) if err != nil { return err @@ -150,7 +148,7 @@ func (w *WireGuard) start() error { connectAddr = w.peers[0].Endpoint reserved = w.peers[0].Reserved } - bind = wireguard.NewClientBind(w.ctx, w, w.listener, isConnect, connectAddr, reserved) + bind = wireguard.NewClientBind(w.ctx, w.logger, w.listener, isConnect, connectAddr, reserved) } err = w.tunDevice.Start() if err != nil { @@ -177,7 +175,7 @@ func (w *WireGuard) start() error { return nil } -func (w *WireGuard) Close() error { +func (w *Outbound) Close() error { if w.device != nil { w.device.Close() } @@ -187,12 +185,12 @@ func (w *WireGuard) Close() error { return nil } -func (w *WireGuard) InterfaceUpdated() { +func (w *Outbound) InterfaceUpdated() { w.device.BindUpdate() return } -func (w *WireGuard) onPauseUpdated(event int) { +func (w *Outbound) onPauseUpdated(event int) { switch event { case pause.EventDevicePaused: w.device.Down() @@ -201,7 +199,7 @@ func (w *WireGuard) onPauseUpdated(event int) { } } -func (w *WireGuard) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { +func (w *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { switch network { case N.NetworkTCP: w.logger.InfoContext(ctx, "outbound connection to ", destination) @@ -218,7 +216,7 @@ func (w *WireGuard) DialContext(ctx context.Context, network string, destination return w.tunDevice.DialContext(ctx, network, destination) } -func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { +func (w *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { w.logger.InfoContext(ctx, "outbound packet connection to ", destination) if destination.IsFqdn() { destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) @@ -236,12 +234,12 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) ( // TODO // Deprecated -func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { - return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) +func (w *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { + return outbound.NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } // TODO // Deprecated -func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { - return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) +func (w *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { + return outbound.NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) } diff --git a/route/dns.go b/route/dns.go index 34299ebf..a0c376c2 100644 --- a/route/dns.go +++ b/route/dns.go @@ -7,7 +7,7 @@ import ( "github.com/sagernet/sing-box/adapter" C "github.com/sagernet/sing-box/constant" - "github.com/sagernet/sing-box/outbound" + dnsOutbound "github.com/sagernet/sing-box/protocol/dns" "github.com/sagernet/sing-dns" "github.com/sagernet/sing/common/buf" E "github.com/sagernet/sing/common/exceptions" @@ -22,7 +22,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad metadata.Destination = M.Socksaddr{} for { conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) - err := outbound.HandleStreamDNSRequest(ctx, r, conn, metadata) + err := dnsOutbound.HandleStreamDNSRequest(ctx, r, conn, metadata) if err != nil { return err } @@ -46,7 +46,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB }) return } - err := outbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) + err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) if err != nil && !E.IsClosedOrCanceled(err) { r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) } diff --git a/route/route.go b/route/route.go index ebffdddd..6c68cf79 100644 --- a/route/route.go +++ b/route/route.go @@ -12,12 +12,12 @@ import ( "time" "github.com/sagernet/sing-box/adapter" + "github.com/sagernet/sing-box/adapter/outbound" "github.com/sagernet/sing-box/common/conntrack" "github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/sniff" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - "github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-dns" "github.com/sagernet/sing-mux" diff --git a/route/router.go b/route/router.go index 6308127f..ce480749 100644 --- a/route/router.go +++ b/route/router.go @@ -99,7 +99,6 @@ func NewRouter( dnsOptions option.DNSOptions, ntpOptions option.NTPOptions, inbounds []option.Inbound, - platformInterface platform.Interface, ) (*Router, error) { router := &Router{ ctx: ctx, @@ -122,10 +121,13 @@ func NewRouter( defaultInterface: options.DefaultInterface, defaultMark: options.DefaultMark, pauseManager: service.FromContext[pause.Manager](ctx), - platformInterface: platformInterface, + platformInterface: service.FromContext[platform.Interface](ctx), needWIFIState: hasRule(options.Rules, isWIFIRule) || hasDNSRule(dnsOptions.Rules, isWIFIDNSRule), needPackageManager: common.Any(inbounds, func(inbound option.Inbound) bool { - return len(inbound.TunOptions.IncludePackage) > 0 || len(inbound.TunOptions.ExcludePackage) > 0 + if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { + return true + } + return false }), } router.dnsClient = dns.NewClient(dns.ClientOptions{ @@ -324,9 +326,15 @@ func NewRouter( router.fakeIPStore = fakeip.NewStore(ctx, router.logger, inet4Range, inet6Range) } - usePlatformDefaultInterfaceMonitor := platformInterface != nil && platformInterface.UsePlatformDefaultInterfaceMonitor() + usePlatformDefaultInterfaceMonitor := router.platformInterface != nil && router.platformInterface.UsePlatformDefaultInterfaceMonitor() needInterfaceMonitor := options.AutoDetectInterface || common.Any(inbounds, func(inbound option.Inbound) bool { - return inbound.HTTPOptions.SetSystemProxy || inbound.MixedOptions.SetSystemProxy || inbound.TunOptions.AutoRoute + if httpMixedOptions, isHTTPMixed := inbound.Options.(*option.HTTPMixedInboundOptions); isHTTPMixed && httpMixedOptions.SetSystemProxy { + return true + } + if tunOptions, isTUN := inbound.Options.(*option.TunInboundOptions); isTUN && tunOptions.AutoRoute { + return true + } + return false }) if !usePlatformDefaultInterfaceMonitor { @@ -342,7 +350,7 @@ func NewRouter( interfaceMonitor, err := tun.NewDefaultInterfaceMonitor(router.networkMonitor, router.logger, tun.DefaultInterfaceMonitorOptions{ InterfaceFinder: router.interfaceFinder, OverrideAndroidVPN: options.OverrideAndroidVPN, - UnderNetworkExtension: platformInterface != nil && platformInterface.UnderNetworkExtension(), + UnderNetworkExtension: router.platformInterface != nil && router.platformInterface.UnderNetworkExtension(), }) if err != nil { return nil, E.New("auto_detect_interface unsupported on current platform") @@ -351,7 +359,7 @@ func NewRouter( router.interfaceMonitor = interfaceMonitor } } else { - interfaceMonitor := platformInterface.CreateDefaultInterfaceMonitor(router.logger) + interfaceMonitor := router.platformInterface.CreateDefaultInterfaceMonitor(router.logger) interfaceMonitor.RegisterCallback(router.notifyNetworkUpdate) router.interfaceMonitor = interfaceMonitor } diff --git a/test/brutal_test.go b/test/brutal_test.go index 18aae2e2..ce1d2c2a 100644 --- a/test/brutal_test.go +++ b/test/brutal_test.go @@ -15,7 +15,7 @@ func TestBrutalShadowsocks(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestBrutalShadowsocks(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -100,7 +100,7 @@ func TestBrutalTrojan(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -138,7 +138,7 @@ func TestBrutalTrojan(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -197,7 +197,7 @@ func TestBrutalTrojan(t *testing.T) { func TestBrutalVMess(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -227,7 +227,7 @@ func TestBrutalVMess(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -279,7 +279,7 @@ func TestBrutalVMess(t *testing.T) { func TestBrutalVLESS(t *testing.T) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -326,7 +326,7 @@ func TestBrutalVLESS(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/direct_test.go b/test/direct_test.go index 1dbf1de1..c4fd8c5e 100644 --- a/test/direct_test.go +++ b/test/direct_test.go @@ -11,7 +11,7 @@ import ( // Since this is a feature one-off added by outsiders, I won't address these anymore. func _TestProxyProtocol(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -33,7 +33,7 @@ func _TestProxyProtocol(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/domain_inbound_test.go b/test/domain_inbound_test.go index 1ca2121d..c82b0d29 100644 --- a/test/domain_inbound_test.go +++ b/test/domain_inbound_test.go @@ -6,7 +6,7 @@ import ( C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" "github.com/gofrs/uuid/v5" ) @@ -14,7 +14,7 @@ import ( func TestTUICDomainUDP(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -49,7 +49,7 @@ func TestTUICDomainUDP(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/ech_test.go b/test/ech_test.go index 90eae1f4..eeac1acb 100644 --- a/test/ech_test.go +++ b/test/ech_test.go @@ -16,7 +16,7 @@ func TestECH(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -55,7 +55,7 @@ func TestECH(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -109,7 +109,7 @@ func TestECHQUIC(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -145,7 +145,7 @@ func TestECHQUIC(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -199,7 +199,7 @@ func TestECHHysteria2(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") echConfig, echKey := common.Must2(tls.ECHKeygenDefault("not.example.org", false)) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -235,7 +235,7 @@ func TestECHHysteria2(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/http_test.go b/test/http_test.go index 88385c27..7e724005 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -10,7 +10,7 @@ import ( func TestHTTPSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -31,7 +31,7 @@ func TestHTTPSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/hysteria2_test.go b/test/hysteria2_test.go index 9ca2f5d3..665da552 100644 --- a/test/hysteria2_test.go +++ b/test/hysteria2_test.go @@ -28,7 +28,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { } } startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -63,7 +63,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -115,7 +115,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) { func TestHysteria2Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2InboundOptions{ @@ -167,7 +167,7 @@ func TestHysteria2Outbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -178,7 +178,7 @@ func TestHysteria2Outbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeHysteria2, Hysteria2Options: option.Hysteria2OutboundOptions{ diff --git a/test/hysteria_test.go b/test/hysteria_test.go index bde1b9fa..dce00390 100644 --- a/test/hysteria_test.go +++ b/test/hysteria_test.go @@ -11,7 +11,7 @@ import ( func TestHysteriaSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestHysteriaSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -98,7 +98,7 @@ func TestHysteriaSelf(t *testing.T) { func TestHysteriaInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaInboundOptions{ @@ -149,7 +149,7 @@ func TestHysteriaOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -160,7 +160,7 @@ func TestHysteriaOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeHysteria, HysteriaOptions: option.HysteriaOutboundOptions{ diff --git a/test/inbound_detour_test.go b/test/inbound_detour_test.go index 9505c217..c26c81a7 100644 --- a/test/inbound_detour_test.go +++ b/test/inbound_detour_test.go @@ -13,7 +13,7 @@ func TestChainedInbound(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -49,7 +49,7 @@ func TestChainedInbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/mux_cool_test.go b/test/mux_cool_test.go index 81130fad..e72f244f 100644 --- a/test/mux_cool_test.go +++ b/test/mux_cool_test.go @@ -37,7 +37,7 @@ func TestMuxCoolServer(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -81,7 +81,7 @@ func TestMuxCoolClient(t *testing.T) { }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -92,7 +92,7 @@ func TestMuxCoolClient(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, VMessOptions: option.VMessOutboundOptions{ @@ -112,7 +112,7 @@ func TestMuxCoolClient(t *testing.T) { func TestMuxCoolSelf(t *testing.T) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -139,7 +139,7 @@ func TestMuxCoolSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/mux_test.go b/test/mux_test.go index 8d755185..335def2e 100644 --- a/test/mux_test.go +++ b/test/mux_test.go @@ -55,7 +55,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -81,7 +81,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -125,7 +125,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) { func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { user, _ := uuid.NewV4() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -154,7 +154,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/naive_test.go b/test/naive_test.go index 1a1547da..fe3e7dce 100644 --- a/test/naive_test.go +++ b/test/naive_test.go @@ -13,7 +13,7 @@ import ( func TestNaiveInboundWithNginx(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ @@ -59,7 +59,7 @@ func TestNaiveInboundWithNginx(t *testing.T) { func TestNaiveInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ @@ -103,7 +103,7 @@ func TestNaiveInbound(t *testing.T) { func TestNaiveHTTP3Inbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeNaive, NaiveOptions: option.NaiveInboundOptions{ diff --git a/test/shadowsocks_legacy_test.go b/test/shadowsocks_legacy_test.go index 8075a7df..ae6f38e4 100644 --- a/test/shadowsocks_legacy_test.go +++ b/test/shadowsocks_legacy_test.go @@ -24,7 +24,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -35,7 +35,7 @@ func testShadowsocksLegacy(t *testing.T, method string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/shadowsocks_test.go b/test/shadowsocks_test.go index 4ef1ee9d..0f7af765 100644 --- a/test/shadowsocks_test.go +++ b/test/shadowsocks_test.go @@ -99,7 +99,7 @@ func testShadowsocksInboundWithShadowsocksRust(t *testing.T, method string, pass Cmd: []string{"-s", F.ToString("127.0.0.1:", serverPort), "-b", F.ToString("0.0.0.0:", clientPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksInboundOptions{ @@ -124,7 +124,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas Cmd: []string{"-s", F.ToString("0.0.0.0:", serverPort), "-m", method, "-k", password, "-U"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -135,7 +135,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ @@ -154,7 +154,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas func testShadowsocksSelf(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -177,7 +177,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -221,7 +221,7 @@ func TestShadowsocksUoT(t *testing.T) { method := shadowaead_2022.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -244,7 +244,7 @@ func TestShadowsocksUoT(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -289,7 +289,7 @@ func TestShadowsocksUoT(t *testing.T) { func testShadowsocks2022EIH(t *testing.T, method string, password string) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -317,7 +317,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/shadowtls_test.go b/test/shadowtls_test.go index 6f9ee1e5..71e8d9fa 100644 --- a/test/shadowtls_test.go +++ b/test/shadowtls_test.go @@ -37,7 +37,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) method := shadowaead_2022.List[0] ssPassword := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -80,7 +80,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ @@ -142,7 +142,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool) func TestShadowTLSFallback(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeShadowTLS, ShadowTLSOptions: option.ShadowTLSInboundOptions{ @@ -189,7 +189,7 @@ func TestShadowTLSInbound(t *testing.T) { Cmd: []string{"--v3", "--threads", "1", "client", "--listen", "0.0.0.0:" + F.ToString(otherPort), "--server", "127.0.0.1:" + F.ToString(serverPort), "--sni", "google.com", "--password", password}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", @@ -232,7 +232,7 @@ func TestShadowTLSInbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -283,7 +283,7 @@ func TestShadowTLSOutbound(t *testing.T) { Env: []string{"RUST_LOG=trace"}, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -306,7 +306,7 @@ func TestShadowTLSOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/ss_plugin_test.go b/test/ss_plugin_test.go index 94606b70..3f837b4e 100644 --- a/test/ss_plugin_test.go +++ b/test/ss_plugin_test.go @@ -33,7 +33,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -44,7 +44,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string) }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeShadowsocks, ShadowsocksOptions: option.ShadowsocksOutboundOptions{ diff --git a/test/tfo_test.go b/test/tfo_test.go index 7bd34e2d..458a936d 100644 --- a/test/tfo_test.go +++ b/test/tfo_test.go @@ -13,7 +13,7 @@ func TestTCPSlowOpen(t *testing.T) { method := shadowaead.List[0] password := mkBase64(t, 16) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -37,7 +37,7 @@ func TestTCPSlowOpen(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/tls_test.go b/test/tls_test.go index cfc6c1a5..b42d924f 100644 --- a/test/tls_test.go +++ b/test/tls_test.go @@ -11,7 +11,7 @@ import ( func TestUTLS(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -46,7 +46,7 @@ func TestUTLS(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/trojan_test.go b/test/trojan_test.go index f88ec885..1a206c66 100644 --- a/test/trojan_test.go +++ b/test/trojan_test.go @@ -20,7 +20,7 @@ func TestTrojanOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -31,7 +31,7 @@ func TestTrojanOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeTrojan, TrojanOptions: option.TrojanOutboundOptions{ @@ -57,7 +57,7 @@ func TestTrojanOutbound(t *testing.T) { func TestTrojanSelf(t *testing.T) { _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -92,7 +92,7 @@ func TestTrojanSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -140,7 +140,7 @@ func TestTrojanSelf(t *testing.T) { func TestTrojanPlainSelf(t *testing.T) { startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -167,7 +167,7 @@ func TestTrojanPlainSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/tuic_test.go b/test/tuic_test.go index 5b838f22..41fb7599 100644 --- a/test/tuic_test.go +++ b/test/tuic_test.go @@ -29,7 +29,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { udpRelayMode = "quic" } startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -62,7 +62,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -113,7 +113,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) { func TestTUICInbound(t *testing.T) { caPem, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICInboundOptions{ @@ -160,7 +160,7 @@ func TestTUICOutbound(t *testing.T) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -171,7 +171,7 @@ func TestTUICOutbound(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeTUIC, TUICOptions: option.TUICOutboundOptions{ diff --git a/test/v2ray_api_test.go b/test/v2ray_api_test.go index 1bea41a6..cd7ae2c4 100644 --- a/test/v2ray_api_test.go +++ b/test/v2ray_api_test.go @@ -14,7 +14,7 @@ import ( func TestV2RayAPI(t *testing.T) { i := startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "in", @@ -26,7 +26,7 @@ func TestV2RayAPI(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, Tag: "out", diff --git a/test/v2ray_grpc_test.go b/test/v2ray_grpc_test.go index fa43f753..5cf87543 100644 --- a/test/v2ray_grpc_test.go +++ b/test/v2ray_grpc_test.go @@ -27,7 +27,7 @@ func testV2RayGRPCInbound(t *testing.T, forceLite bool) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -126,7 +126,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -138,7 +138,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, Tag: "vmess-out", diff --git a/test/v2ray_transport_test.go b/test/v2ray_transport_test.go index c7362f34..27074e78 100644 --- a/test/v2ray_transport_test.go +++ b/test/v2ray_transport_test.go @@ -44,7 +44,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -80,7 +80,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -133,7 +133,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -169,7 +169,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions, }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -224,7 +224,7 @@ func TestVMessQUICSelf(t *testing.T) { require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -260,7 +260,7 @@ func TestVMessQUICSelf(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, @@ -312,7 +312,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO user, err := uuid.DefaultGenerator.NewV4() require.NoError(t, err) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -340,7 +340,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/v2ray_ws_test.go b/test/v2ray_ws_test.go index 0e238c28..de8d4bdc 100644 --- a/test/v2ray_ws_test.go +++ b/test/v2ray_ws_test.go @@ -61,7 +61,7 @@ func testV2RayWebsocketInbound(t *testing.T, maxEarlyData uint32, earlyDataHeade require.NoError(t, err) _, certPem, keyPem := createSelfSignedCertificate(t, "example.org") startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -158,7 +158,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -170,7 +170,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, Tag: "vmess-out", diff --git a/test/vmess_test.go b/test/vmess_test.go index fcf7bf8f..9f81d9a0 100644 --- a/test/vmess_test.go +++ b/test/vmess_test.go @@ -181,7 +181,7 @@ func testVMessInboundWithV2Ray(t *testing.T, security string, alterId int, authe }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeVMess, VMessOptions: option.VMessInboundOptions{ @@ -229,7 +229,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -240,7 +240,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeVMess, VMessOptions: option.VMessOutboundOptions{ @@ -263,7 +263,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo func testVMessSelf(t *testing.T, security string, alterId int, globalPadding bool, authenticatedLength bool, packetAddr bool) { user := newUUID() startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, Tag: "mixed-in", @@ -291,7 +291,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeDirect, }, diff --git a/test/wireguard_test.go b/test/wireguard_test.go index 50e87ee0..70c0e5a5 100644 --- a/test/wireguard_test.go +++ b/test/wireguard_test.go @@ -21,7 +21,7 @@ func _TestWireGuard(t *testing.T) { }) time.Sleep(5 * time.Second) startInstance(t, option.Options{ - Inbounds: []option.Inbound{ + Inbounds: []option.LegacyInbound{ { Type: C.TypeMixed, MixedOptions: option.HTTPMixedInboundOptions{ @@ -32,7 +32,7 @@ func _TestWireGuard(t *testing.T) { }, }, }, - Outbounds: []option.Outbound{ + LegacyOutbounds: []option.LegacyOutbound{ { Type: C.TypeWireGuard, WireGuardOptions: option.WireGuardOutboundOptions{ diff --git a/test/wrapper_test.go b/test/wrapper_test.go index d2b6b9ff..a7c23f33 100644 --- a/test/wrapper_test.go +++ b/test/wrapper_test.go @@ -10,7 +10,7 @@ import ( ) func TestOptionsWrapper(t *testing.T) { - inbound := option.Inbound{ + inbound := option.LegacyInbound{ Type: C.TypeHTTP, HTTPOptions: option.HTTPMixedInboundOptions{ InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ diff --git a/transport/trojan/mux.go b/transport/trojan/mux.go index b1cc9985..0329bd40 100644 --- a/transport/trojan/mux.go +++ b/transport/trojan/mux.go @@ -8,12 +8,13 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/task" "github.com/sagernet/smux" ) -func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) error { +func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) error { session, err := smux.Server(conn, smuxConfig()) if err != nil { return err @@ -26,7 +27,7 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata if err != nil { return err } - go newMuxConnection(ctx, stream, metadata, handler) + go newMuxConnection(ctx, stream, metadata, handler, logger) } }) group.Cleanup(func() { @@ -35,10 +36,10 @@ func HandleMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata return group.Run(ctx) } -func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler) { +func newMuxConnection(ctx context.Context, conn net.Conn, metadata M.Metadata, handler Handler, logger logger.ContextLogger) { err := newMuxConnection0(ctx, conn, metadata, handler) if err != nil { - handler.NewError(ctx, E.Cause(err, "process trojan-go multiplex connection")) + logger.ErrorContext(ctx, E.Cause(err, "process trojan-go multiplex connection")) } } diff --git a/transport/trojan/service.go b/transport/trojan/service.go index 97f674ab..978d737f 100644 --- a/transport/trojan/service.go +++ b/transport/trojan/service.go @@ -9,6 +9,7 @@ import ( "github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/common/rw" @@ -17,7 +18,6 @@ import ( type Handler interface { N.TCPConnectionHandler N.UDPConnectionHandler - E.Handler } type Service[K comparable] struct { @@ -25,14 +25,16 @@ type Service[K comparable] struct { keys map[[56]byte]K handler Handler fallbackHandler N.TCPConnectionHandler + logger logger.ContextLogger } -func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler) *Service[K] { +func NewService[K comparable](handler Handler, fallbackHandler N.TCPConnectionHandler, logger logger.ContextLogger) *Service[K] { return &Service[K]{ users: make(map[K][56]byte), keys: make(map[[56]byte]K), handler: handler, fallbackHandler: fallbackHandler, + logger: logger, } } @@ -110,7 +112,7 @@ func (s *Service[K]) NewConnection(ctx context.Context, conn net.Conn, metadata return s.handler.NewPacketConnection(ctx, &PacketConn{Conn: conn}, metadata) // case CommandMux: default: - return HandleMuxConnection(ctx, conn, metadata, s.handler) + return HandleMuxConnection(ctx, conn, metadata, s.handler, s.logger) } } diff --git a/transport/wireguard/client_bind.go b/transport/wireguard/client_bind.go index 6c534532..20e7c079 100644 --- a/transport/wireguard/client_bind.go +++ b/transport/wireguard/client_bind.go @@ -10,6 +10,7 @@ import ( "github.com/sagernet/sing/common" "github.com/sagernet/sing/common/bufio" E "github.com/sagernet/sing/common/exceptions" + "github.com/sagernet/sing/common/logger" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" "github.com/sagernet/sing/service" @@ -21,10 +22,10 @@ var _ conn.Bind = (*ClientBind)(nil) type ClientBind struct { ctx context.Context + logger logger.Logger pauseManager pause.Manager bindCtx context.Context bindDone context.CancelFunc - errorHandler E.Handler dialer N.Dialer reservedForEndpoint map[netip.AddrPort][3]uint8 connAccess sync.Mutex @@ -35,11 +36,11 @@ type ClientBind struct { reserved [3]uint8 } -func NewClientBind(ctx context.Context, errorHandler E.Handler, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { +func NewClientBind(ctx context.Context, logger logger.Logger, dialer N.Dialer, isConnect bool, connectAddr netip.AddrPort, reserved [3]uint8) *ClientBind { return &ClientBind{ ctx: ctx, + logger: logger, pauseManager: service.FromContext[pause.Manager](ctx), - errorHandler: errorHandler, dialer: dialer, reservedForEndpoint: make(map[netip.AddrPort][3]uint8), done: make(chan struct{}), @@ -115,7 +116,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) return default: } - c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server")) + c.logger.Error(E.Cause(err, "connect to server")) err = nil c.pauseManager.WaitActive() time.Sleep(time.Second) @@ -127,7 +128,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint) select { case <-c.done: default: - c.errorHandler.NewError(context.Background(), E.Cause(err, "read packet")) + c.logger.Error(context.Background(), E.Cause(err, "read packet")) err = nil } return diff --git a/transport/wireguard/resolve.go b/transport/wireguard/resolve.go index 5b4124d2..d7a1d19c 100644 --- a/transport/wireguard/resolve.go +++ b/transport/wireguard/resolve.go @@ -8,7 +8,7 @@ import ( "github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/option" - dns "github.com/sagernet/sing-dns" + "github.com/sagernet/sing-dns" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" )