refactor: Modular outbounds

This commit is contained in:
世界 2024-11-02 00:39:02 +08:00
parent e537c56b6b
commit 0b2c7ec35c
No known key found for this signature in database
GPG key ID: CD109927C34A63C4
99 changed files with 1004 additions and 1022 deletions

View file

@ -1,6 +1,10 @@
package adapter package adapter
import ( import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
@ -13,3 +17,8 @@ type Outbound interface {
Dependencies() []string Dependencies() []string
N.Dialer N.Dialer
} }
type OutboundRegistry interface {
option.OutboundOptionsRegistry
CreateOutbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Outbound, error)
}

View file

@ -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
}

View file

@ -9,8 +9,6 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
@ -21,42 +19,6 @@ import (
N "github.com/sagernet/sing/common/network" 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 { func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata) ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn 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) 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)
}

View file

@ -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
optiosnType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optiosnType: 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.optiosnType[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.optiosnType[outboundType] = optionsConstructor
r.constructors[outboundType] = constructor
}

53
box.go
View file

@ -17,7 +17,7 @@ import (
"github.com/sagernet/sing-box/inbound" "github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "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-box/route"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -48,12 +48,26 @@ type Options struct {
PlatformLogWriter log.PlatformWriter PlatformLogWriter log.PlatformWriter
} }
func Context(ctx context.Context, registry adapter.OutboundRegistry) context.Context {
if service.FromContext[option.OutboundOptionsRegistry](ctx) != nil &&
service.FromContext[adapter.OutboundRegistry](ctx) != nil {
return ctx
}
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, registry)
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, registry)
return ctx
}
func New(options Options) (*Box, error) { func New(options Options) (*Box, error) {
createdAt := time.Now() createdAt := time.Now()
ctx := options.Context ctx := options.Context
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
ctx = service.ContextWithDefaultRegistry(ctx) ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.WithDefaultManager(ctx) ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental) experimentalOptions := common.PtrValueOrDefault(options.Experimental)
@ -98,6 +112,16 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "parse route options") return nil, E.Cause(err, "parse route options")
} }
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds)) inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
//nolint:staticcheck
if len(options.LegacyOutbounds) > 0 {
for _, legacyOutbound := range options.LegacyOutbounds {
options.Outbounds = append(options.Outbounds, option.Outbound{
Type: legacyOutbound.Type,
Tag: legacyOutbound.Tag,
Options: common.Must1(legacyOutbound.RawOptions()),
})
}
}
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds)) outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
for i, inboundOptions := range options.Inbounds { for i, inboundOptions := range options.Inbounds {
var in adapter.Inbound var in adapter.Inbound
@ -121,29 +145,38 @@ func New(options Options) (*Box, error) {
inbounds = append(inbounds, in) inbounds = append(inbounds, in)
} }
for i, outboundOptions := range options.Outbounds { for i, outboundOptions := range options.Outbounds {
var out adapter.Outbound var currentOutbound adapter.Outbound
var tag string var tag string
if outboundOptions.Tag != "" { if outboundOptions.Tag != "" {
tag = outboundOptions.Tag tag = outboundOptions.Tag
} else { } else {
tag = F.ToString(i) tag = F.ToString(i)
} }
out, err = outbound.New( outboundCtx := ctx
ctx, if tag != "" {
// TODO: remove this
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
Outbound: tag,
})
}
currentOutbound, err = outboundRegistry.CreateOutbound(
outboundCtx,
router, router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag, tag,
outboundOptions) outboundOptions.Type,
outboundOptions.Options,
)
if err != nil { if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]") return nil, E.Cause(err, "parse outbound[", i, "]")
} }
outbounds = append(outbounds, out) outbounds = append(outbounds, currentOutbound)
} }
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound { 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"}) defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
common.Must(oErr) common.Must(cErr)
outbounds = append(outbounds, out) outbounds = append(outbounds, defaultOutbound)
return out return defaultOutbound
}) })
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -7,8 +7,9 @@ import (
"strconv" "strconv"
"time" "time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/experimental/deprecated" "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-box/log"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
"github.com/sagernet/sing/service/filemanager" "github.com/sagernet/sing/service/filemanager"
@ -68,4 +69,5 @@ func preRun(cmd *cobra.Command, args []string) {
configPaths = append(configPaths, "config.json") configPaths = append(configPaths, "config.json")
} }
globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger())) globalCtx = service.ContextWith(globalCtx, deprecated.NewEnvManager(log.StdLogger()))
globalCtx = box.Context(globalCtx, include.OutboundRegistry())
} }

View file

@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"context"
"os" "os"
"path/filepath" "path/filepath"
@ -38,7 +39,7 @@ func format() error {
return err return err
} }
for _, optionsEntry := range optionsList { for _, optionsEntry := range optionsList {
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options) optionsEntry.options, err = badjson.Omitempty(context.TODO(), optionsEntry.options)
if err != nil { if err != nil {
return err return err
} }

View file

@ -78,19 +78,14 @@ func mergePathResources(options *option.Options) error {
} }
options.Inbounds[index] = inbound options.Inbounds[index] = inbound
} }
for index, outbound := range options.Outbounds { for _, outbound := range options.Outbounds {
rawOptions, err := outbound.RawOptions()
if err != nil {
return err
}
switch outbound.Type { switch outbound.Type {
case C.TypeSSH: 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())) tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
} }
options.Outbounds[index] = outbound
} }
return nil return nil
} }
@ -138,13 +133,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
return options return options
} }
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions { func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
if options.PrivateKeyPath != "" { if options.PrivateKeyPath != "" {
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil { if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n")) options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
} }
} }
return options
} }
func trimStringArray(array []string) []string { func trimStringArray(array []string) []string {

View file

@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
if err != nil { if err != nil {
return nil, E.Cause(err, "read config at ", path) 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 { if err != nil {
return nil, E.Cause(err, "decode config at ", path) return nil, E.Cause(err, "decode config at ", path)
} }
@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
} }
var mergedMessage json.RawMessage var mergedMessage json.RawMessage
for _, options := range optionsList { 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 { if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path) return option.Options{}, E.Cause(err, "merge config at ", options.path)
} }
} }
var mergedOptions option.Options var mergedOptions option.Options
err = mergedOptions.UnmarshalJSON(mergedMessage) err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
if err != nil { if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged config") return option.Options{}, E.Cause(err, "unmarshal merged config")
} }

View file

@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
setMultiPathTCP(&dialer4) setMultiPathTCP(&dialer4)
} }
if options.IsWireGuardListener { if options.IsWireGuardListener {
for _, controlFn := range wgControlFns { for _, controlFn := range WgControlFns {
listener.Control = control.Append(listener.Control, controlFn) listener.Control = control.Append(listener.Control, controlFn)
} }
} }

View file

@ -2,8 +2,12 @@ package dialer
import ( import (
"net" "net"
"github.com/sagernet/sing/common/control"
) )
type WireGuardListener interface { type WireGuardListener interface {
ListenPacketCompat(network, address string) (net.PacketConn, error) ListenPacketCompat(network, address string) (net.PacketConn, error)
} }
var WgControlFns []control.Func

View file

@ -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

View file

@ -1,9 +0,0 @@
//go:build !with_wireguard
package dialer
import (
"github.com/sagernet/sing/common/control"
)
var wgControlFns []control.Func

View file

@ -10,7 +10,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest" "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"
"github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/batch"
"github.com/sagernet/sing/common/json/badjson" "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) { func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound) proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
group, ok := proxy.(adapter.OutboundGroup) outboundGroup, ok := proxy.(adapter.OutboundGroup)
if !ok { if !ok {
render.Status(r, http.StatusNotFound) render.Status(r, http.StatusNotFound)
render.JSON(w, r, ErrNotFound) render.JSON(w, r, ErrNotFound)
@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
defer cancel() defer cancel()
var result map[string]uint16 var result map[string]uint16
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup { if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup {
result, err = urlTestGroup.URLTest(ctx) result, err = urlTestGroup.URLTest(ctx)
} else { } 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) itOutbound, _ := server.router.Outbound(it)
return itOutbound return itOutbound
})) }))
@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
var resultAccess sync.Mutex var resultAccess sync.Mutex
for _, detour := range outbounds { for _, detour := range outbounds {
tag := detour.Tag() tag := detour.Tag()
realTag := outbound.RealTag(detour) realTag := group.RealTag(detour)
if checked[realTag] { if checked[realTag] {
continue continue
} }

View file

@ -11,7 +11,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant" 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" "github.com/sagernet/sing/common"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json/badjson" "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) proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
selector, ok := proxy.(*outbound.Selector) selector, ok := proxy.(*group.Selector)
if !ok { if !ok {
render.Status(r, http.StatusBadRequest) render.Status(r, http.StatusBadRequest)
render.JSON(w, r, newError("Must be a Selector")) 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) delay, err := urltest.URLTest(ctx, url, proxy)
defer func() { defer func() {
realTag := outbound.RealTag(proxy) realTag := group.RealTag(proxy)
if err != nil { if err != nil {
server.urlTestHistory.DeleteURLTestHistory(realTag) server.urlTestHistory.DeleteURLTestHistory(realTag)
} else { } else {

View file

@ -9,7 +9,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest" "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" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/common/varbin"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
} }
var groups []OutboundGroup var groups []OutboundGroup
for _, iGroup := range iGroups { for _, iGroup := range iGroups {
var group OutboundGroup var outboundGroup OutboundGroup
group.Tag = iGroup.Tag() outboundGroup.Tag = iGroup.Tag()
group.Type = iGroup.Type() outboundGroup.Type = iGroup.Type()
_, group.Selectable = iGroup.(*outbound.Selector) _, outboundGroup.Selectable = iGroup.(*group.Selector)
group.Selected = iGroup.Now() outboundGroup.Selected = iGroup.Now()
if cacheFile != nil { if cacheFile != nil {
if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded { if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded {
group.IsExpand = isExpand outboundGroup.IsExpand = isExpand
} }
} }
@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
item.URLTestTime = history.Time.Unix() item.URLTestTime = history.Time.Unix()
item.URLTestDelay = int32(history.Delay) 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 continue
} }
groups = append(groups, group) groups = append(groups, outboundGroup)
} }
return varbin.Write(writer, binary.BigEndian, groups) return varbin.Write(writer, binary.BigEndian, groups)
} }

View file

@ -4,7 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"net" "net"
"github.com/sagernet/sing-box/outbound" "github.com/sagernet/sing-box/protocol/group"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin" "github.com/sagernet/sing/common/varbin"
) )
@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
if !isLoaded { if !isLoaded {
return writeError(conn, E.New("selector not found: ", groupTag)) return writeError(conn, E.New("selector not found: ", groupTag))
} }
selector, isSelector := outboundGroup.(*outbound.Selector) selector, isSelector := outboundGroup.(*group.Selector)
if !isSelector { if !isSelector {
return writeError(conn, E.New("outbound is not a selector: ", groupTag)) return writeError(conn, E.New("outbound is not a selector: ", groupTag))
} }

View file

@ -7,7 +7,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/urltest" "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"
"github.com/sagernet/sing/common/batch" "github.com/sagernet/sing/common/batch"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
if !isOutboundGroup { if !isOutboundGroup {
return writeError(conn, E.New("outbound is not a group: ", groupTag)) return writeError(conn, E.New("outbound is not a group: ", groupTag))
} }
urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest) urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
if isURLTest { if isURLTest {
go urlTest.CheckOutbounds() go urlTest.CheckOutbounds()
} else { } else {

View file

@ -17,6 +17,7 @@ import (
"github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs" "github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
"github.com/sagernet/sing-box/experimental/libbox/platform" "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/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
@ -47,6 +48,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
} }
runtimeDebug.FreeOSMemory() runtimeDebug.FreeOSMemory()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
ctx = box.Context(ctx, include.OutboundRegistry())
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID) ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
urlTestHistoryStorage := urltest.NewHistoryStorage() urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage) ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)

View file

@ -9,7 +9,6 @@ import (
"github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/humanize"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
_ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
) )

4
go.mod
View file

@ -3,7 +3,6 @@ module github.com/sagernet/sing-box
go 1.20 go 1.20
require ( require (
berty.tech/go-libtor v1.0.385
github.com/caddyserver/certmagic v0.20.0 github.com/caddyserver/certmagic v0.20.0
github.com/cloudflare/circl v1.3.7 github.com/cloudflare/circl v1.3.7
github.com/cretz/bine v0.2.0 github.com/cretz/bine v0.2.0
@ -17,7 +16,6 @@ require (
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/mholt/acmez v1.2.0 github.com/mholt/acmez v1.2.0
github.com/miekg/dns v1.1.62 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/oschwald/maxminddb-golang v1.12.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
@ -27,7 +25,7 @@ require (
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
github.com/sagernet/quic-go v0.48.0-beta.1 github.com/sagernet/quic-go v0.48.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.5.0-rc.4.0.20241023053048-94f058276959 github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369
github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a
github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec
github.com/sagernet/sing-quic v0.3.0-rc.1 github.com/sagernet/sing-quic v0.3.0-rc.1

12
go.sum
View file

@ -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 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 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 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 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/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 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= 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= 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 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= 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/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 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
@ -115,8 +110,8 @@ github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-rc.4.0.20241023053048-94f058276959 h1:8BzTt5cU8h6HK4CcRq1UQHKsgUi942GjO0by/ntFZIs= github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369 h1:gfiUYWslwKM7OtvG37PV0iIDCWcacJSEUS2h29rpYac=
github.com/sagernet/sing v0.5.0-rc.4.0.20241023053048-94f058276959/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a h1:jpAlbmZxc1LymZrmJacsvHI57Wito5xy8qASZJMWoOQ= github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a h1:jpAlbmZxc1LymZrmJacsvHI57Wito5xy8qASZJMWoOQ=
github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M= github.com/sagernet/sing-dns v0.3.0-rc.2.0.20241023053951-feb6d5403f2a/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M=
github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48= github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec h1:6Fd/VsEsw9qIjaGi1IBTZSb4b4v5JYtNcoiBtGsQC48=
@ -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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/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.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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 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= 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 h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= 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.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
@ -182,7 +175,6 @@ golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.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-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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

59
include/outbound.go Normal file
View file

@ -0,0 +1,59 @@
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"
"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/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/vless"
"github.com/sagernet/sing-box/protocol/vmess"
E "github.com/sagernet/sing/common/exceptions"
)
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 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")
})
}

View file

@ -3,6 +3,16 @@
package include package include
import ( import (
"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/tuic"
_ "github.com/sagernet/sing-box/transport/v2rayquic" _ "github.com/sagernet/sing-box/transport/v2rayquic"
_ "github.com/sagernet/sing-dns/quic" _ "github.com/sagernet/sing-dns/quic"
) )
func registerQUICOutbounds(registry *outbound.Registry) {
hysteria.RegisterOutbound(registry)
tuic.RegisterOutbound(registry)
hysteria2.RegisterOutbound(registry)
}

View file

@ -6,8 +6,10 @@ import (
"context" "context"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/transport/v2ray" "github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
@ -29,3 +31,15 @@ func init() {
}, },
) )
} }
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
})
}

12
include/wireguard.go Normal file
View file

@ -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)
}

20
include/wireguard_stub.go Normal file
View file

@ -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`)
})
}

View file

@ -6,6 +6,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
) )
type _Inbound struct { type _Inbound struct {
@ -79,7 +80,7 @@ func (h Inbound) MarshalJSON() ([]byte, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return MarshallObjects((_Inbound)(h), rawOptions) return badjson.MarshallObjects((_Inbound)(h), rawOptions)
} }
func (h *Inbound) UnmarshalJSON(bytes []byte) error { func (h *Inbound) UnmarshalJSON(bytes []byte) error {
@ -91,7 +92,7 @@ func (h *Inbound) UnmarshalJSON(bytes []byte) error {
if err != nil { if err != nil {
return err return err
} }
err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions) err = badjson.UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions)
if err != nil { if err != nil {
return err return err
} }

View file

@ -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)
}

View file

@ -2,6 +2,7 @@ package option
import ( import (
"bytes" "bytes"
"context"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
) )
@ -16,12 +17,15 @@ type _Options struct {
Outbounds []Outbound `json:"outbounds,omitempty"` Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"` Route *RouteOptions `json:"route,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"` Experimental *ExperimentalOptions `json:"experimental,omitempty"`
// Deprecated: use Outbounds instead
LegacyOutbounds []LegacyOutbound `json:"_"`
} }
type Options _Options type Options _Options
func (o *Options) UnmarshalJSON(content []byte) error { func (o *Options) UnmarshalJSONContext(ctx context.Context, content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content)) decoder := json.NewDecoderContext(ctx, bytes.NewReader(content))
decoder.DisallowUnknownFields() decoder.DisallowUnknownFields()
err := decoder.Decode((*_Options)(o)) err := decoder.Decode((*_Options)(o))
if err != nil { if err != nil {
@ -38,3 +42,5 @@ type LogOptions struct {
Timestamp bool `json:"timestamp,omitempty"` Timestamp bool `json:"timestamp,omitempty"`
DisableColor bool `json:"-"` DisableColor bool `json:"-"`
} }
type StubOptions struct{}

View file

@ -1,104 +1,49 @@
package option package option
import ( import (
C "github.com/sagernet/sing-box/constant" "context"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/service"
) )
type OutboundOptionsRegistry interface {
CreateOptions(outboundType string) (any, bool)
}
type _Outbound struct { type _Outbound struct {
Type string `json:"type"` Type string `json:"type"`
Tag string `json:"tag,omitempty"` Tag string `json:"tag,omitempty"`
DirectOptions DirectOutboundOptions `json:"-"` Options any `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 Outbound _Outbound type Outbound _Outbound
func (h *Outbound) RawOptions() (any, error) { func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) {
var rawOptionsPtr any return badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options)
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) MarshalJSON() ([]byte, error) { func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error {
rawOptions, err := h.RawOptions() err := json.Unmarshal(content, (*_Outbound)(h))
if err != nil {
return nil, err
}
return MarshallObjects((*_Outbound)(h), rawOptions)
}
func (h *Outbound) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_Outbound)(h))
if err != nil { if err != nil {
return err return err
} }
rawOptions, err := h.RawOptions() registry := service.FromContext[OutboundOptionsRegistry](ctx)
if err != nil { if registry == nil {
return err return E.New("missing outbound options registry in context")
} }
err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions) 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 { if err != nil {
return err return err
} }
h.Options = options
return nil return nil
} }

103
option/outbound_legacy.go Normal file
View file

@ -0,0 +1,103 @@
package option
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 _LegacyOutbound 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 LegacyOutbound _LegacyOutbound
func (h *LegacyOutbound) RawOptions() (any, error) {
var rawOptionsPtr any
switch h.Type {
case C.TypeDirect:
rawOptionsPtr = &h.DirectOptions
case C.TypeBlock, C.TypeDNS:
rawOptionsPtr = new(StubOptions)
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 *LegacyOutbound) MarshalJSON() ([]byte, error) {
rawOptions, err := h.RawOptions()
if err != nil {
return nil, err
}
return badjson.MarshallObjects((*_LegacyOutbound)(h), rawOptions)
}
func (h *LegacyOutbound) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_LegacyOutbound)(h))
if err != nil {
return err
}
rawOptions, err := h.RawOptions()
if err != nil {
return err
}
err = badjson.UnmarshallExcluded(bytes, (*_LegacyOutbound)(h), rawOptions)
if err != nil {
return err
}
return nil
}

View file

@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
) )
type _Rule struct { type _Rule struct {
@ -28,7 +29,7 @@ func (r Rule) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown rule type: " + r.Type) 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 { func (r *Rule) UnmarshalJSON(bytes []byte) error {
@ -46,7 +47,7 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown rule type: " + r.Type) return E.New("unknown rule type: " + r.Type)
} }
err = UnmarshallExcluded(bytes, (*_Rule)(r), v) err = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v)
if err != nil { if err != nil {
return err return err
} }
@ -109,7 +110,7 @@ type DefaultRule struct {
} }
func (r *DefaultRule) MarshalJSON() ([]byte, error) { 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 { func (r *DefaultRule) UnmarshalJSON(data []byte) error {
@ -117,7 +118,7 @@ func (r *DefaultRule) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
return UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction) return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction)
} }
func (r *DefaultRule) IsValid() bool { func (r *DefaultRule) IsValid() bool {
@ -139,7 +140,7 @@ type LogicalRule struct {
} }
func (r *LogicalRule) MarshalJSON() ([]byte, error) { 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 { func (r *LogicalRule) UnmarshalJSON(data []byte) error {
@ -147,7 +148,7 @@ func (r *LogicalRule) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
return UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction) return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction)
} }
func (r *LogicalRule) IsValid() bool { func (r *LogicalRule) IsValid() bool {

View file

@ -4,6 +4,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
) )
type _RuleAction struct { type _RuleAction struct {
@ -36,9 +37,9 @@ func (r RuleAction) MarshalJSON() ([]byte, error) {
return nil, E.New("unknown rule action: " + r.Action) return nil, E.New("unknown rule action: " + r.Action)
} }
if v == nil { 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 { func (r *RuleAction) UnmarshalJSON(data []byte) error {
@ -68,7 +69,7 @@ func (r *RuleAction) UnmarshalJSON(data []byte) error {
// check unknown fields // check unknown fields
return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{}) return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{})
} }
return UnmarshallExcluded(data, (*_RuleAction)(r), v) return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v)
} }
type _DNSRuleAction struct { type _DNSRuleAction struct {
@ -95,9 +96,9 @@ func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
return nil, E.New("unknown DNS rule action: " + r.Action) return nil, E.New("unknown DNS rule action: " + r.Action)
} }
if v == nil { 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 { func (r *DNSRuleAction) UnmarshalJSON(data []byte) error {
@ -121,7 +122,7 @@ func (r *DNSRuleAction) UnmarshalJSON(data []byte) error {
// check unknown fields // check unknown fields
return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{}) return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{})
} }
return UnmarshallExcluded(data, (*_DNSRuleAction)(r), v) return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v)
} }
type RouteActionOptions struct { type RouteActionOptions struct {

View file

@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
) )
type _DNSRule struct { type _DNSRule struct {
@ -28,7 +29,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown rule type: " + r.Type) 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 { func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
@ -46,7 +47,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown rule type: " + r.Type) return E.New("unknown rule type: " + r.Type)
} }
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v) err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
if err != nil { if err != nil {
return err return err
} }
@ -111,7 +112,7 @@ type DefaultDNSRule struct {
} }
func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) { 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 { func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error {
@ -119,7 +120,7 @@ func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
return UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction) return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction)
} }
func (r *DefaultDNSRule) IsValid() bool { func (r *DefaultDNSRule) IsValid() bool {
@ -141,7 +142,7 @@ type LogicalDNSRule struct {
} }
func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) { 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 { func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error {
@ -149,7 +150,7 @@ func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error {
if err != nil { if err != nil {
return err return err
} }
return UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction) return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction)
} }
func (r *LogicalDNSRule) IsValid() bool { func (r *LogicalDNSRule) IsValid() bool {

View file

@ -9,6 +9,7 @@ import (
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"go4.org/netipx" "go4.org/netipx"
) )
@ -37,7 +38,7 @@ func (r RuleSet) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown rule-set type: " + r.Type) 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 { func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
@ -71,7 +72,7 @@ func (r *RuleSet) UnmarshalJSON(bytes []byte) error {
} else { } else {
r.Format = "" r.Format = ""
} }
err = UnmarshallExcluded(bytes, (*_RuleSet)(r), v) err = badjson.UnmarshallExcluded(bytes, (*_RuleSet)(r), v)
if err != nil { if err != nil {
return err return err
} }
@ -107,7 +108,7 @@ func (r HeadlessRule) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown rule type: " + r.Type) 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 { func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error {
@ -125,7 +126,7 @@ func (r *HeadlessRule) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown rule type: " + r.Type) return E.New("unknown rule type: " + r.Type)
} }
err = UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v) err = badjson.UnmarshallExcluded(bytes, (*_HeadlessRule)(r), v)
if err != nil { if err != nil {
return err return err
} }
@ -203,7 +204,7 @@ func (r PlainRuleSetCompat) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown rule-set version: ", r.Version) 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 { func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
@ -220,7 +221,7 @@ func (r *PlainRuleSetCompat) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown rule-set version: ", r.Version) 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 { if err != nil {
return err return err
} }

View file

@ -14,7 +14,7 @@ type HTTPMixedInboundOptions struct {
InboundTLSOptionsContainer InboundTLSOptionsContainer
} }
type SocksOutboundOptions struct { type SOCKSOutboundOptions struct {
DialerOptions DialerOptions
ServerOptions ServerOptions
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`

View file

@ -4,6 +4,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
) )
type InboundACMEOptions struct { type InboundACMEOptions struct {
@ -45,7 +46,7 @@ func (o ACMEDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown provider type: " + o.Provider) 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 { func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
@ -62,7 +63,7 @@ func (o *ACMEDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown provider type: " + o.Provider) return E.New("unknown provider type: " + o.Provider)
} }
err = UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v) err = badjson.UnmarshallExcluded(bytes, (*_ACMEDNS01ChallengeOptions)(o), v)
if err != nil { if err != nil {
return err return err
} }

View file

@ -4,6 +4,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
) )
type _V2RayTransportOptions struct { type _V2RayTransportOptions struct {
@ -35,7 +36,7 @@ func (o V2RayTransportOptions) MarshalJSON() ([]byte, error) {
default: default:
return nil, E.New("unknown transport type: " + o.Type) 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 { func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
@ -58,7 +59,7 @@ func (o *V2RayTransportOptions) UnmarshalJSON(bytes []byte) error {
default: default:
return E.New("unknown transport type: " + o.Type) return E.New("unknown transport type: " + o.Type)
} }
err = UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v) err = badjson.UnmarshallExcluded(bytes, (*_V2RayTransportOptions)(o), v)
if err != nil { if err != nil {
return err return err
} }

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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")
}

View file

@ -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,
}
}

View file

@ -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,
}
}

View file

@ -1,9 +0,0 @@
//go:build !with_embedded_tor
package outbound
import "github.com/cretz/bine/tor"
func newConfig() tor.StartConf {
return tor.StartConf{}
}

View file

@ -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
}

View file

@ -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`)
}

View file

@ -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
}

View file

@ -1,4 +1,4 @@
package outbound package direct
import ( import (
"net" "net"

View file

@ -1,4 +1,4 @@
package outbound package direct
import ( import (
"context" "context"
@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "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/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@ -14,17 +15,20 @@ import (
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
var ( func RegisterOutbound(registry *outbound.Registry) {
_ adapter.Outbound = (*Direct)(nil) outbound.Register[option.DirectOutboundOptions](registry, C.TypeDirect, NewOutbound)
_ N.ParallelDialer = (*Direct)(nil) }
)
type Direct struct { var _ N.ParallelDialer = (*Outbound)(nil)
myOutboundAdapter
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
domainStrategy dns.DomainStrategy domainStrategy dns.DomainStrategy
fallbackDelay time.Duration fallbackDelay time.Duration
@ -33,21 +37,15 @@ type Direct struct {
// loopBack *loopBackDetector // 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 options.UDPFragmentDefault = true
outboundDialer, err := dialer.New(router, options.DialerOptions) outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &Direct{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeDirect, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.DialerOptions),
protocol: C.TypeDirect,
network: []string{N.NetworkTCP, N.NetworkUDP},
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
domainStrategy: dns.DomainStrategy(options.DomainStrategy), domainStrategy: dns.DomainStrategy(options.DomainStrategy),
fallbackDelay: time.Duration(options.FallbackDelay), fallbackDelay: time.Duration(options.FallbackDelay),
dialer: outboundDialer, dialer: outboundDialer,
@ -69,9 +67,9 @@ func NewDirect(router adapter.Router, logger log.ContextLogger, tag string, opti
return outbound, nil 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
switch h.overrideOption { switch h.overrideOption {
case 1: case 1:
@ -98,9 +96,9 @@ func (h *Direct) DialContext(ctx context.Context, network string, destination M.
return h.dialer.DialContext(ctx, network, destination) 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
switch h.overrideOption { switch h.overrideOption {
case 1, 2: 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) 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
originDestination := destination originDestination := destination
switch h.overrideOption { switch h.overrideOption {
@ -156,14 +154,14 @@ func (h *Direct) ListenPacket(ctx context.Context, destination M.Socksaddr) (net
return conn, nil 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())) { if h.loopBack.CheckConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {
return E.New("reject loopback connection to ", metadata.Destination) return E.New("reject loopback connection to ", metadata.Destination)
} }
return NewConnection(ctx, h, conn, metadata) 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())) { if h.loopBack.CheckPacketConn(metadata.Source.AddrPort(), M.AddrPortFromNet(conn.LocalAddr())) {
return E.New("reject loopback packet connection to ", metadata.Destination) return E.New("reject loopback packet connection to ", metadata.Destination)
} }

View file

@ -1,15 +1,13 @@
package outbound package dns
import ( import (
"context" "context"
"encoding/binary" "encoding/binary"
"net" "net"
"os"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-dns" dns "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
@ -21,44 +19,6 @@ import (
mDNS "github.com/miekg/dns" 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 { func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net.Conn, metadata adapter.InboundContext) error {
var queryLength uint16 var queryLength uint16
err := binary.Read(conn, binary.BigEndian, &queryLength) err := binary.Read(conn, binary.BigEndian, &queryLength)
@ -100,11 +60,6 @@ func HandleStreamDNSRequest(ctx context.Context, router adapter.Router, conn net
return nil 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 { func NewDNSPacketConnection(ctx context.Context, router adapter.Router, conn N.PacketConn, cachedPackets []*N.PacketBuffer, metadata adapter.InboundContext) error {
metadata.Destination = M.Socksaddr{} metadata.Destination = M.Socksaddr{}
var reader N.PacketReader = conn var reader N.PacketReader = conn

61
protocol/dns/outbound.go Normal file
View file

@ -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)
}

View file

@ -1,28 +1,33 @@
package outbound package group
import ( import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/adapter" "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/interrupt"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
) )
var ( func RegisterSelector(registry *outbound.Registry) {
_ adapter.Outbound = (*Selector)(nil) outbound.Register[option.SelectorOutboundOptions](registry, C.TypeSelector, NewSelector)
_ adapter.OutboundGroup = (*Selector)(nil) }
)
var _ adapter.OutboundGroup = (*Selector)(nil)
type Selector struct { type Selector struct {
myOutboundAdapter outbound.Adapter
ctx context.Context ctx context.Context
router adapter.Router
logger logger.ContextLogger
tags []string tags []string
defaultTag string defaultTag string
outbounds map[string]adapter.Outbound outbounds map[string]adapter.Outbound
@ -31,16 +36,12 @@ type Selector struct {
interruptExternalConnections bool 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{ outbound := &Selector{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapter(C.TypeSelector, nil, tag, options.Outbounds),
protocol: C.TypeSelector, ctx: ctx,
router: router, router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: options.Outbounds,
},
ctx: ctx,
tags: options.Outbounds, tags: options.Outbounds,
defaultTag: options.Default, defaultTag: options.Default,
outbounds: make(map[string]adapter.Outbound), outbounds: make(map[string]adapter.Outbound),
@ -69,10 +70,10 @@ func (s *Selector) Start() error {
s.outbounds[tag] = detour s.outbounds[tag] = detour
} }
if s.tag != "" { if s.Tag() != "" {
cacheFile := service.FromContext[adapter.CacheFile](s.ctx) cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
if cacheFile != nil { if cacheFile != nil {
selected := cacheFile.LoadSelected(s.tag) selected := cacheFile.LoadSelected(s.Tag())
if selected != "" { if selected != "" {
detour, loaded := s.outbounds[selected] detour, loaded := s.outbounds[selected]
if loaded { if loaded {
@ -113,10 +114,10 @@ func (s *Selector) SelectOutbound(tag string) bool {
return true return true
} }
s.selected = detour s.selected = detour
if s.tag != "" { if s.Tag() != "" {
cacheFile := service.FromContext[adapter.CacheFile](s.ctx) cacheFile := service.FromContext[adapter.CacheFile](s.ctx)
if cacheFile != nil { if cacheFile != nil {
err := cacheFile.StoreSelected(s.tag, tag) err := cacheFile.StoreSelected(s.Tag(), tag)
if err != nil { if err != nil {
s.logger.Error("store selected: ", err) s.logger.Error("store selected: ", err)
} }
@ -149,7 +150,7 @@ func (s *Selector) NewConnection(ctx context.Context, conn net.Conn, metadata ad
if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok { if legacyHandler, ok := s.selected.(adapter.ConnectionHandler); ok {
return legacyHandler.NewConnection(ctx, conn, metadata) return legacyHandler.NewConnection(ctx, conn, metadata)
} else { } else {
return NewConnection(ctx, s.selected, conn, metadata) return outbound.NewConnection(ctx, s.selected, conn, metadata)
} }
} }
@ -160,7 +161,7 @@ func (s *Selector) NewPacketConnection(ctx context.Context, conn N.PacketConn, m
if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok { if legacyHandler, ok := s.selected.(adapter.PacketConnectionHandler); ok {
return legacyHandler.NewPacketConnection(ctx, conn, metadata) return legacyHandler.NewPacketConnection(ctx, conn, metadata)
} else { } else {
return NewPacketConnection(ctx, s.selected, conn, metadata) return outbound.NewPacketConnection(ctx, s.selected, conn, metadata)
} }
} }

View file

@ -1,4 +1,4 @@
package outbound package group
import ( import (
"context" "context"
@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "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/interrupt"
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@ -22,15 +23,20 @@ import (
"github.com/sagernet/sing/service/pause" "github.com/sagernet/sing/service/pause"
) )
func RegisterURLTest(registry *outbound.Registry) {
outbound.Register[option.URLTestOutboundOptions](registry, C.TypeURLTest, NewURLTest)
}
var ( var (
_ adapter.Outbound = (*URLTest)(nil)
_ adapter.OutboundGroup = (*URLTest)(nil) _ adapter.OutboundGroup = (*URLTest)(nil)
_ adapter.InterfaceUpdateListener = (*URLTest)(nil) _ adapter.InterfaceUpdateListener = (*URLTest)(nil)
) )
type URLTest struct { type URLTest struct {
myOutboundAdapter outbound.Adapter
ctx context.Context ctx context.Context
router adapter.Router
logger log.ContextLogger
tags []string tags []string
link string link string
interval time.Duration interval time.Duration
@ -40,17 +46,12 @@ type URLTest struct {
interruptExternalConnections bool 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{ outbound := &URLTest{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapter(C.TypeURLTest, []string{N.NetworkTCP, N.NetworkUDP}, tag, options.Outbounds),
protocol: C.TypeURLTest, ctx: ctx,
network: []string{N.NetworkTCP, N.NetworkUDP},
router: router, router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: options.Outbounds,
},
ctx: ctx,
tags: options.Outbounds, tags: options.Outbounds,
link: options.URL, link: options.URL,
interval: time.Duration(options.Interval), interval: time.Duration(options.Interval),
@ -171,14 +172,14 @@ func (s *URLTest) ListenPacket(ctx context.Context, destination M.Socksaddr) (ne
// Deprecated // Deprecated
func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (s *URLTest) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
return NewConnection(ctx, s, conn, metadata) return outbound.NewConnection(ctx, s, conn, metadata)
} }
// TODO // TODO
// Deprecated // Deprecated
func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (s *URLTest) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = interrupt.ContextWithIsExternalConnection(ctx) ctx = interrupt.ContextWithIsExternalConnection(ctx)
return NewPacketConnection(ctx, s, conn, metadata) return outbound.NewPacketConnection(ctx, s, conn, metadata)
} }
func (s *URLTest) InterfaceUpdated() { func (s *URLTest) InterfaceUpdated() {

View file

@ -1,4 +1,4 @@
package outbound package http
import ( import (
"context" "context"
@ -6,25 +6,30 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
sHTTP "github.com/sagernet/sing/protocol/http" 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 { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
client *sHTTP.Client 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) outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
@ -33,16 +38,10 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &HTTP{ return &Outbound{
myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHTTP, []string{N.NetworkTCP}, tag, options.DialerOptions),
protocol: C.TypeHTTP,
network: []string{N.NetworkTCP},
router: router,
logger: logger, logger: logger,
tag: tag, client: sHTTP.NewClient(sHTTP.Options{
dependencies: withDialerDependency(options.DialerOptions),
},
sHTTP.NewClient(sHTTP.Options{
Dialer: detour, Dialer: detour,
Server: options.ServerOptions.Build(), Server: options.ServerOptions.Build(),
Username: options.Username, Username: options.Username,
@ -53,14 +52,14 @@ func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogge
}, nil }, 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
h.logger.InfoContext(ctx, "outbound connection to ", destination) h.logger.InfoContext(ctx, "outbound connection to ", destination)
return h.client.DialContext(ctx, network, 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 return nil, os.ErrInvalid
} }

View file

@ -1,6 +1,4 @@
//go:build with_quic package hysteria
package outbound
import ( import (
"context" "context"
@ -8,31 +6,39 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/tuic"
"github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, NewOutbound)
}
var ( var (
_ adapter.Outbound = (*TUIC)(nil) _ adapter.Outbound = (*tuic.Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*TUIC)(nil) _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)
) )
type Hysteria struct { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
client *hysteria.Client 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 options.UDPFragmentDefault = true
if options.TLS == nil || !options.TLS.Enabled { if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired return nil, C.ErrTLSRequired
@ -88,20 +94,14 @@ func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextL
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Hysteria{ return &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria, networkList, tag, options.DialerOptions),
protocol: C.TypeHysteria,
network: networkList,
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
client: client, client: client,
}, nil }, 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) { switch N.NetworkName(network) {
case N.NetworkTCP: case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination) 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) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return h.client.ListenPacket(ctx, destination) return h.client.ListenPacket(ctx, destination)
} }
func (h *Hysteria) InterfaceUpdated() { func (h *Outbound) InterfaceUpdated() {
h.client.CloseWithError(E.New("network changed")) h.client.CloseWithError(E.New("network changed"))
} }
func (h *Hysteria) Close() error { func (h *Outbound) Close() error {
return h.client.CloseWithError(os.ErrClosed) return h.client.CloseWithError(os.ErrClosed)
} }

View file

@ -1,6 +1,4 @@
//go:build with_quic package hysteria2
package outbound
import ( import (
"context" "context"
@ -8,31 +6,39 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/tuic"
"github.com/sagernet/sing-quic/hysteria" "github.com/sagernet/sing-quic/hysteria"
"github.com/sagernet/sing-quic/hysteria2" "github.com/sagernet/sing-quic/hysteria2"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, NewOutbound)
}
var ( var (
_ adapter.Outbound = (*TUIC)(nil) _ adapter.Outbound = (*tuic.Outbound)(nil)
_ adapter.InterfaceUpdateListener = (*TUIC)(nil) _ adapter.InterfaceUpdateListener = (*tuic.Outbound)(nil)
) )
type Hysteria2 struct { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
client *hysteria2.Client 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 options.UDPFragmentDefault = true
if options.TLS == nil || !options.TLS.Enabled { if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired return nil, C.ErrTLSRequired
@ -74,20 +80,14 @@ func NewHysteria2(ctx context.Context, router adapter.Router, logger log.Context
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Hysteria2{ return &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeHysteria2, networkList, tag, options.DialerOptions),
protocol: C.TypeHysteria2,
network: networkList,
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
client: client, client: client,
}, nil }, 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) { switch N.NetworkName(network) {
case N.NetworkTCP: case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination) 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) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return h.client.ListenPacket(ctx) return h.client.ListenPacket(ctx)
} }
func (h *Hysteria2) InterfaceUpdated() { func (h *Outbound) InterfaceUpdated() {
h.client.CloseWithError(E.New("network changed")) h.client.CloseWithError(E.New("network changed"))
} }
func (h *Hysteria2) Close() error { func (h *Outbound) Close() error {
return h.client.CloseWithError(os.ErrClosed) return h.client.CloseWithError(os.ErrClosed)
} }

View file

@ -1,10 +1,11 @@
package outbound package shadowsocks
import ( import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/mux"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@ -15,15 +16,19 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot" "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 { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
method shadowsocks.Method method shadowsocks.Method
serverAddr M.Socksaddr serverAddr M.Socksaddr
@ -32,7 +37,7 @@ type Shadowsocks struct {
multiplexDialer *mux.Client 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{ method, err := shadowsocks.CreateMethod(ctx, options.Method, shadowsocks.MethodOptions{
Password: options.Password, Password: options.Password,
}) })
@ -43,15 +48,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &Shadowsocks{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowsocks, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeShadowsocks,
network: options.Network.Build(),
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
dialer: outboundDialer, dialer: outboundDialer,
method: method, method: method,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
@ -78,9 +77,9 @@ func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.Conte
return outbound, nil 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
if h.multiplexDialer == nil { if h.multiplexDialer == nil {
switch N.NetworkName(network) { 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
if h.multiplexDialer == nil { if h.multiplexDialer == nil {
if h.uotClient != 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 { if h.multiplexDialer != nil {
h.multiplexDialer.Reset() h.multiplexDialer.Reset()
} }
return return
} }
func (h *Shadowsocks) Close() error { func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer)) return common.Close(common.PtrOrNil(h.multiplexDialer))
} }
var _ N.Dialer = (*shadowsocksDialer)(nil) 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) { func (h *shadowsocksDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: 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) { func (h *shadowsocksDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr) outConn, err := h.dialer.DialContext(ctx, N.NetworkUDP, h.serverAddr)
if err != nil { if err != nil {

View file

@ -1,4 +1,4 @@
package outbound package shadowtls
import ( import (
"context" "context"
@ -6,6 +6,7 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@ -17,23 +18,18 @@ import (
N "github.com/sagernet/sing/common/network" 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 { type Outbound struct {
myOutboundAdapter outbound.Adapter
client *shadowtls.Client client *shadowtls.Client
} }
func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (*ShadowTLS, error) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowTLSOutboundOptions) (adapter.Outbound, error) {
outbound := &ShadowTLS{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeShadowTLS, []string{N.NetworkTCP}, tag, options.DialerOptions),
protocol: C.TypeShadowTLS,
network: []string{N.NetworkTCP},
router: router,
logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
} }
if options.TLS == nil || !options.TLS.Enabled { if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired return nil, C.ErrTLSRequired
@ -91,9 +87,9 @@ func NewShadowTLS(ctx context.Context, router adapter.Router, logger log.Context
return outbound, nil 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: 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 return nil, os.ErrInvalid
} }

View file

@ -1,10 +1,11 @@
package outbound package socks
import ( import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/adapter" "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/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@ -12,22 +13,29 @@ import (
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/uot"
"github.com/sagernet/sing/protocol/socks" "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 { var _ adapter.Outbound = (*Outbound)(nil)
myOutboundAdapter
type Outbound struct {
outbound.Adapter
router adapter.Router
logger logger.ContextLogger
client *socks.Client client *socks.Client
resolve bool resolve bool
uotClient *uot.Client 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 version socks.Version
var err error var err error
if options.Version != "" { if options.Version != "" {
@ -42,15 +50,10 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &Socks{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSOCKS, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeSOCKS,
network: options.Network.Build(),
router: router, router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password), client: socks.NewClient(outboundDialer, options.ServerOptions.Build(), version, options.Username, options.Password),
resolve: version == socks.Version4, resolve: version == socks.Version4,
} }
@ -64,9 +67,9 @@ func NewSocks(router adapter.Router, logger log.ContextLogger, tag string, optio
return outbound, nil 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: 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) 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) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
if h.uotClient != nil { if h.uotClient != nil {
h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) 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 // TODO
// Deprecated // 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 { 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 { } else {
return NewConnection(ctx, h, conn, metadata) return outbound.NewConnection(ctx, h, conn, metadata)
} }
} }
// TODO // TODO
// Deprecated // 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 { 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 { } else {
return NewPacketConnection(ctx, h, conn, metadata) return outbound.NewPacketConnection(ctx, h, conn, metadata)
} }
} }

View file

@ -1,4 +1,4 @@
package outbound package ssh
import ( import (
"bytes" "bytes"
@ -12,26 +12,30 @@ import (
"sync" "sync"
"github.com/sagernet/sing-box/adapter" "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/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
var ( func RegisterOutbound(registry *outbound.Registry) {
_ adapter.Outbound = (*SSH)(nil) outbound.Register[option.SSHOutboundOptions](registry, C.TypeSSH, NewOutbound)
_ adapter.InterfaceUpdateListener = (*SSH)(nil) }
)
type SSH struct { var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
myOutboundAdapter
type Outbound struct {
outbound.Adapter
ctx context.Context ctx context.Context
logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
serverAddr M.Socksaddr serverAddr M.Socksaddr
user string user string
@ -44,21 +48,15 @@ type SSH struct {
client *ssh.Client 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) outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &SSH{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeSSH, []string{N.NetworkTCP}, tag, options.DialerOptions),
protocol: C.TypeSSH,
network: []string{N.NetworkTCP},
router: router,
logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx, ctx: ctx,
logger: logger,
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
user: options.User, user: options.User,
@ -122,7 +120,7 @@ func randomVersion() string {
return version return version
} }
func (s *SSH) connect() (*ssh.Client, error) { func (s *Outbound) connect() (*ssh.Client, error) {
if s.client != nil { if s.client != nil {
return s.client, nil return s.client, nil
} }
@ -179,16 +177,16 @@ func (s *SSH) connect() (*ssh.Client, error) {
return client, nil return client, nil
} }
func (s *SSH) InterfaceUpdated() { func (s *Outbound) InterfaceUpdated() {
common.Close(s.clientConn) common.Close(s.clientConn)
return return
} }
func (s *SSH) Close() error { func (s *Outbound) Close() error {
return common.Close(s.clientConn) 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() client, err := s.connect()
if err != nil { if err != nil {
return nil, err 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()) 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 return nil, os.ErrInvalid
} }

View file

@ -1,4 +1,4 @@
package outbound package tor
import ( import (
"context" "context"
@ -8,6 +8,7 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "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/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@ -15,6 +16,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
@ -24,11 +26,14 @@ import (
"github.com/cretz/bine/tor" "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 { type Outbound struct {
myOutboundAdapter outbound.Adapter
ctx context.Context ctx context.Context
logger logger.ContextLogger
proxy *ProxyListener proxy *ProxyListener
startConf *tor.StartConf startConf *tor.StartConf
options map[string]string options map[string]string
@ -37,8 +42,8 @@ type Tor struct {
socksClient *socks.Client socksClient *socks.Client
} }
func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (*Tor, error) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TorOutboundOptions) (adapter.Outbound, error) {
startConf := newConfig() var startConf tor.StartConf
startConf.DataDir = os.ExpandEnv(options.DataDirectory) startConf.DataDir = os.ExpandEnv(options.DataDirectory)
startConf.TempDataDirBase = os.TempDir() startConf.TempDataDirBase = os.TempDir()
startConf.ExtraArgs = options.ExtraArgs startConf.ExtraArgs = options.ExtraArgs
@ -74,23 +79,17 @@ func NewTor(ctx context.Context, router adapter.Router, logger log.ContextLogger
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Tor{ return &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTor, []string{N.NetworkTCP}, tag, options.DialerOptions),
protocol: C.TypeTor,
network: []string{N.NetworkTCP},
router: router,
logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx, ctx: ctx,
logger: logger,
proxy: NewProxyListener(ctx, logger, outboundDialer), proxy: NewProxyListener(ctx, logger, outboundDialer),
startConf: &startConf, startConf: &startConf,
options: options.Options, options: options.Options,
}, nil }, nil
} }
func (t *Tor) Start() error { func (t *Outbound) Start() error {
err := t.start() err := t.start()
if err != nil { if err != nil {
t.Close() t.Close()
@ -106,7 +105,7 @@ var torLogEvents = []control.EventCode{
control.EventCodeLogWarn, control.EventCodeLogWarn,
} }
func (t *Tor) start() error { func (t *Outbound) start() error {
torInstance, err := tor.Start(t.ctx, t.startConf) torInstance, err := tor.Start(t.ctx, t.startConf)
if err != nil { if err != nil {
return E.New(strings.ToLower(err.Error())) return E.New(strings.ToLower(err.Error()))
@ -168,7 +167,7 @@ func (t *Tor) start() error {
return nil return nil
} }
func (t *Tor) recvLoop() { func (t *Outbound) recvLoop() {
for rawEvent := range t.events { for rawEvent := range t.events {
switch event := rawEvent.(type) { switch event := rawEvent.(type) {
case *control.LogEvent: case *control.LogEvent:
@ -191,7 +190,7 @@ func (t *Tor) recvLoop() {
} }
} }
func (t *Tor) Close() error { func (t *Outbound) Close() error {
err := common.Close( err := common.Close(
common.PtrOrNil(t.proxy), common.PtrOrNil(t.proxy),
common.PtrOrNil(t.instance), common.PtrOrNil(t.instance),
@ -203,11 +202,11 @@ func (t *Tor) Close() error {
return err 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) t.logger.InfoContext(ctx, "outbound connection to ", destination)
return t.socksClient.DialContext(ctx, network, 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 return nil, os.ErrInvalid
} }

View file

@ -1,4 +1,4 @@
package outbound package tor
import ( import (
"context" "context"
@ -7,6 +7,7 @@ import (
"net" "net"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth" "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.Network = N.NetworkTCP
metadata.Destination = upstreamMetadata.Destination metadata.Destination = upstreamMetadata.Destination
l.logger.InfoContext(ctx, "proxy connection to ", metadata.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 { 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.Network = N.NetworkUDP
metadata.Destination = upstreamMetadata.Destination metadata.Destination = upstreamMetadata.Destination
l.logger.InfoContext(ctx, "proxy packet connection to ", metadata.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)
} }

View file

@ -1,10 +1,11 @@
package outbound package trojan
import ( import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
@ -16,14 +17,18 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
var _ adapter.Outbound = (*Trojan)(nil) func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.TrojanOutboundOptions](registry, C.TypeTrojan, NewOutbound)
}
type Trojan struct { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
serverAddr M.Socksaddr serverAddr M.Socksaddr
key [56]byte key [56]byte
@ -32,20 +37,14 @@ type Trojan struct {
transport adapter.V2RayClientTransport 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) outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &Trojan{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTrojan, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeTrojan,
network: options.Network.Build(),
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
key: trojan.Key(options.Password), key: trojan.Key(options.Password),
@ -69,7 +68,7 @@ func NewTrojan(ctx context.Context, router adapter.Router, logger log.ContextLog
return outbound, nil 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 { if h.multiplexDialer == nil {
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: 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 { if h.multiplexDialer == nil {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return (*trojanDialer)(h).ListenPacket(ctx, 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 { if h.transport != nil {
h.transport.Close() h.transport.Close()
} }
@ -109,15 +108,15 @@ func (h *Trojan) InterfaceUpdated() {
return return
} }
func (h *Trojan) Close() error { func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) 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) { func (h *trojanDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
var conn net.Conn var conn net.Conn
var err error var err error

View file

@ -1,6 +1,4 @@
//go:build with_quic package tuic
package outbound
import ( import (
"context" "context"
@ -9,6 +7,7 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
@ -18,6 +17,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/uot" "github.com/sagernet/sing/common/uot"
@ -25,18 +25,20 @@ import (
"github.com/gofrs/uuid/v5" "github.com/gofrs/uuid/v5"
) )
var ( func RegisterOutbound(registry *outbound.Registry) {
_ adapter.Outbound = (*TUIC)(nil) outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, NewOutbound)
_ adapter.InterfaceUpdateListener = (*TUIC)(nil) }
)
type TUIC struct { var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
myOutboundAdapter
type Outbound struct {
outbound.Adapter
logger logger.ContextLogger
client *tuic.Client client *tuic.Client
udpStream bool 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 options.UDPFragmentDefault = true
if options.TLS == nil || !options.TLS.Enabled { if options.TLS == nil || !options.TLS.Enabled {
return nil, C.ErrTLSRequired return nil, C.ErrTLSRequired
@ -77,21 +79,15 @@ func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogge
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &TUIC{ return &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeTUIC, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeTUIC,
network: options.Network.Build(),
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
client: client, client: client,
udpStream: options.UDPOverStream, udpStream: options.UDPOverStream,
}, nil }, 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) { switch N.NetworkName(network) {
case N.NetworkTCP: case N.NetworkTCP:
h.logger.InfoContext(ctx, "outbound connection to ", destination) 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 { if h.udpStream {
h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination) h.logger.InfoContext(ctx, "outbound stream packet connection to ", destination)
streamConn, err := h.client.DialConn(ctx, uot.RequestDestination(uot.Version)) 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")) _ = h.client.CloseWithError(E.New("network changed"))
} }
func (h *TUIC) Close() error { func (h *Outbound) Close() error {
return h.client.CloseWithError(os.ErrClosed) return h.client.CloseWithError(os.ErrClosed)
} }

View file

@ -1,10 +1,11 @@
package outbound package vless
import ( import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
@ -17,14 +18,18 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
var _ adapter.Outbound = (*VLESS)(nil) func RegisterOutbound(registry *outbound.Registry) {
outbound.Register[option.VLESSOutboundOptions](registry, C.TypeVLESS, NewOutbound)
}
type VLESS struct { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
client *vless.Client client *vless.Client
serverAddr M.Socksaddr serverAddr M.Socksaddr
@ -35,20 +40,14 @@ type VLESS struct {
xudp bool 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) outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &VLESS{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVLESS, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeVLESS,
network: options.Network.Build(),
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
} }
@ -88,7 +87,7 @@ func NewVLESS(ctx context.Context, router adapter.Router, logger log.ContextLogg
return outbound, nil 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 { if h.multiplexDialer == nil {
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: 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 { if h.multiplexDialer == nil {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return (*vlessDialer)(h).ListenPacket(ctx, 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 { if h.transport != nil {
h.transport.Close() h.transport.Close()
} }
@ -128,15 +127,15 @@ func (h *VLESS) InterfaceUpdated() {
return return
} }
func (h *VLESS) Close() error { func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) 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) { func (h *vlessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
var conn net.Conn var conn net.Conn
var err error 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) { func (h *vlessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
var conn net.Conn var conn net.Conn
var err error var err error

View file

@ -1,10 +1,11 @@
package outbound package vmess
import ( import (
"context" "context"
"net" "net"
"github.com/sagernet/sing-box/adapter" "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/dialer"
"github.com/sagernet/sing-box/common/mux" "github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/tls"
@ -16,15 +17,19 @@ import (
"github.com/sagernet/sing-vmess/packetaddr" "github.com/sagernet/sing-vmess/packetaddr"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp" "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 { type Outbound struct {
myOutboundAdapter outbound.Adapter
logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
client *vmess.Client client *vmess.Client
serverAddr M.Socksaddr serverAddr M.Socksaddr
@ -35,20 +40,14 @@ type VMess struct {
xudp bool 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) outboundDialer, err := dialer.New(router, options.DialerOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
outbound := &VMess{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeVMess, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeVMess,
network: options.Network.Build(),
router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
dialer: outboundDialer, dialer: outboundDialer,
serverAddr: options.ServerOptions.Build(), serverAddr: options.ServerOptions.Build(),
} }
@ -102,7 +101,7 @@ func NewVMess(ctx context.Context, router adapter.Router, logger log.ContextLogg
return outbound, nil return outbound, nil
} }
func (h *VMess) InterfaceUpdated() { func (h *Outbound) InterfaceUpdated() {
if h.transport != nil { if h.transport != nil {
h.transport.Close() h.transport.Close()
} }
@ -112,11 +111,11 @@ func (h *VMess) InterfaceUpdated() {
return return
} }
func (h *VMess) Close() error { func (h *Outbound) Close() error {
return common.Close(common.PtrOrNil(h.multiplexDialer), h.transport) 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 { if h.multiplexDialer == nil {
switch N.NetworkName(network) { switch N.NetworkName(network) {
case N.NetworkTCP: 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 { if h.multiplexDialer == nil {
h.logger.InfoContext(ctx, "outbound packet connection to ", destination) h.logger.InfoContext(ctx, "outbound packet connection to ", destination)
return (*vmessDialer)(h).ListenPacket(ctx, 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) { func (h *vmessDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
var conn net.Conn var conn net.Conn
var err error 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) { func (h *vmessDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
ctx, metadata := adapter.ExtendContext(ctx) ctx, metadata := adapter.ExtendContext(ctx)
metadata.Outbound = h.tag metadata.Outbound = h.Tag()
metadata.Destination = destination metadata.Destination = destination
var conn net.Conn var conn net.Conn
var err error var err error

View file

@ -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
}

View file

@ -1,6 +1,4 @@
//go:build with_wireguard package wireguard
package outbound
import ( import (
"context" "context"
@ -12,6 +10,7 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "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/dialer"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@ -21,6 +20,7 @@ import (
"github.com/sagernet/sing-tun" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/x/list" "github.com/sagernet/sing/common/x/list"
@ -30,14 +30,17 @@ import (
"github.com/sagernet/wireguard-go/device" "github.com/sagernet/wireguard-go/device"
) )
var ( func RegisterOutbound(registry *outbound.Registry) {
_ adapter.Outbound = (*WireGuard)(nil) outbound.Register[option.WireGuardOutboundOptions](registry, C.TypeWireGuard, NewOutbound)
_ adapter.InterfaceUpdateListener = (*WireGuard)(nil) }
)
type WireGuard struct { var _ adapter.InterfaceUpdateListener = (*Outbound)(nil)
myOutboundAdapter
type Outbound struct {
outbound.Adapter
ctx context.Context ctx context.Context
router adapter.Router
logger logger.ContextLogger
workers int workers int
peers []wireguard.PeerConfig peers []wireguard.PeerConfig
useStdNetBind bool useStdNetBind bool
@ -51,17 +54,12 @@ type WireGuard struct {
tunDevice wireguard.Device tunDevice wireguard.Device
} }
func NewWireGuard(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (*WireGuard, error) { func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.WireGuardOutboundOptions) (adapter.Outbound, error) {
outbound := &WireGuard{ outbound := &Outbound{
myOutboundAdapter: myOutboundAdapter{ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeWireGuard, options.Network.Build(), tag, options.DialerOptions),
protocol: C.TypeWireGuard, ctx: ctx,
network: options.Network.Build(),
router: router, router: router,
logger: logger, logger: logger,
tag: tag,
dependencies: withDialerDependency(options.DialerOptions),
},
ctx: ctx,
workers: options.Workers, workers: options.Workers,
pauseManager: service.FromContext[pause.Manager](ctx), pauseManager: service.FromContext[pause.Manager](ctx),
} }
@ -111,7 +109,7 @@ func NewWireGuard(ctx context.Context, router adapter.Router, logger log.Context
return outbound, nil return outbound, nil
} }
func (w *WireGuard) Start() error { func (w *Outbound) Start() error {
if common.Any(w.peers, func(peer wireguard.PeerConfig) bool { if common.Any(w.peers, func(peer wireguard.PeerConfig) bool {
return !peer.Endpoint.IsValid() return !peer.Endpoint.IsValid()
}) { }) {
@ -121,7 +119,7 @@ func (w *WireGuard) Start() error {
return w.start() return w.start()
} }
func (w *WireGuard) PostStart() error { func (w *Outbound) PostStart() error {
if common.All(w.peers, func(peer wireguard.PeerConfig) bool { if common.All(w.peers, func(peer wireguard.PeerConfig) bool {
return peer.Endpoint.IsValid() return peer.Endpoint.IsValid()
}) { }) {
@ -130,7 +128,7 @@ func (w *WireGuard) PostStart() error {
return w.start() return w.start()
} }
func (w *WireGuard) start() error { func (w *Outbound) start() error {
err := wireguard.ResolvePeers(w.ctx, w.router, w.peers) err := wireguard.ResolvePeers(w.ctx, w.router, w.peers)
if err != nil { if err != nil {
return err return err
@ -150,7 +148,7 @@ func (w *WireGuard) start() error {
connectAddr = w.peers[0].Endpoint connectAddr = w.peers[0].Endpoint
reserved = w.peers[0].Reserved 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() err = w.tunDevice.Start()
if err != nil { if err != nil {
@ -177,7 +175,7 @@ func (w *WireGuard) start() error {
return nil return nil
} }
func (w *WireGuard) Close() error { func (w *Outbound) Close() error {
if w.device != nil { if w.device != nil {
w.device.Close() w.device.Close()
} }
@ -187,12 +185,12 @@ func (w *WireGuard) Close() error {
return nil return nil
} }
func (w *WireGuard) InterfaceUpdated() { func (w *Outbound) InterfaceUpdated() {
w.device.BindUpdate() w.device.BindUpdate()
return return
} }
func (w *WireGuard) onPauseUpdated(event int) { func (w *Outbound) onPauseUpdated(event int) {
switch event { switch event {
case pause.EventDevicePaused: case pause.EventDevicePaused:
w.device.Down() 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 { switch network {
case N.NetworkTCP: case N.NetworkTCP:
w.logger.InfoContext(ctx, "outbound connection to ", destination) 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) 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) w.logger.InfoContext(ctx, "outbound packet connection to ", destination)
if destination.IsFqdn() { if destination.IsFqdn() {
destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn) destinationAddresses, err := w.router.LookupDefault(ctx, destination.Fqdn)
@ -236,12 +234,12 @@ func (w *WireGuard) ListenPacket(ctx context.Context, destination M.Socksaddr) (
// TODO // TODO
// Deprecated // Deprecated
func (w *WireGuard) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error { func (w *Outbound) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) return outbound.NewDirectConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
} }
// TODO // TODO
// Deprecated // Deprecated
func (w *WireGuard) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error { func (w *Outbound) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS) return outbound.NewDirectPacketConnection(ctx, w.router, w, conn, metadata, dns.DomainStrategyAsIS)
} }

View file

@ -7,7 +7,7 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" 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-dns"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
@ -22,7 +22,7 @@ func (r *Router) hijackDNSStream(ctx context.Context, conn net.Conn, metadata ad
metadata.Destination = M.Socksaddr{} metadata.Destination = M.Socksaddr{}
for { for {
conn.SetReadDeadline(time.Now().Add(C.DNSTimeout)) conn.SetReadDeadline(time.Now().Add(C.DNSTimeout))
err := outbound.HandleStreamDNSRequest(ctx, r, conn, metadata) err := dnsOutbound.HandleStreamDNSRequest(ctx, r, conn, metadata)
if err != nil { if err != nil {
return err return err
} }
@ -46,7 +46,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
}) })
return return
} }
err := outbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata) err := dnsOutbound.NewDNSPacketConnection(ctx, r, conn, packetBuffers, metadata)
if err != nil && !E.IsClosedOrCanceled(err) { if err != nil && !E.IsClosedOrCanceled(err) {
r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection")) r.dnsLogger.ErrorContext(ctx, E.Cause(err, "process packet connection"))
} }

View file

@ -12,12 +12,12 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "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/conntrack"
"github.com/sagernet/sing-box/common/process" "github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing-mux" "github.com/sagernet/sing-mux"

View file

@ -46,7 +46,7 @@ func TestBrutalShadowsocks(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -138,7 +138,7 @@ func TestBrutalTrojan(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -227,7 +227,7 @@ func TestBrutalVMess(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -326,7 +326,7 @@ func TestBrutalVLESS(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -33,7 +33,7 @@ func _TestProxyProtocol(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -49,7 +49,7 @@ func TestTUICDomainUDP(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -55,7 +55,7 @@ func TestECH(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -145,7 +145,7 @@ func TestECHQUIC(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -235,7 +235,7 @@ func TestECHHysteria2(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -31,7 +31,7 @@ func TestHTTPSelf(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -63,7 +63,7 @@ func testHysteria2Self(t *testing.T, salamanderPassword string) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -178,7 +178,7 @@ func TestHysteria2Outbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeHysteria2, Type: C.TypeHysteria2,
Hysteria2Options: option.Hysteria2OutboundOptions{ Hysteria2Options: option.Hysteria2OutboundOptions{

View file

@ -46,7 +46,7 @@ func TestHysteriaSelf(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -160,7 +160,7 @@ func TestHysteriaOutbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeHysteria, Type: C.TypeHysteria,
HysteriaOptions: option.HysteriaOutboundOptions{ HysteriaOptions: option.HysteriaOutboundOptions{

View file

@ -49,7 +49,7 @@ func TestChainedInbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -92,7 +92,7 @@ func TestMuxCoolClient(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeVMess, Type: C.TypeVMess,
VMessOptions: option.VMessOutboundOptions{ VMessOptions: option.VMessOutboundOptions{
@ -139,7 +139,7 @@ func TestMuxCoolSelf(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -81,7 +81,7 @@ func testShadowsocksMux(t *testing.T, options option.OutboundMultiplexOptions) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -154,7 +154,7 @@ func testVMessMux(t *testing.T, options option.OutboundMultiplexOptions) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -35,7 +35,7 @@ func testShadowsocksLegacy(t *testing.T, method string) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeShadowsocks, Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksOutboundOptions{ ShadowsocksOptions: option.ShadowsocksOutboundOptions{

View file

@ -135,7 +135,7 @@ func testShadowsocksOutboundWithShadowsocksRust(t *testing.T, method string, pas
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeShadowsocks, Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksOutboundOptions{ ShadowsocksOptions: option.ShadowsocksOutboundOptions{
@ -177,7 +177,7 @@ func testShadowsocksSelf(t *testing.T, method string, password string) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -244,7 +244,7 @@ func TestShadowsocksUoT(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -317,7 +317,7 @@ func testShadowsocks2022EIH(t *testing.T, method string, password string) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -80,7 +80,7 @@ func testShadowTLS(t *testing.T, version int, password string, utlsEanbled bool)
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeShadowsocks, Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksOutboundOptions{ ShadowsocksOptions: option.ShadowsocksOutboundOptions{
@ -232,7 +232,7 @@ func TestShadowTLSInbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -306,7 +306,7 @@ func TestShadowTLSOutbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeShadowsocks, Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksOutboundOptions{ ShadowsocksOptions: option.ShadowsocksOutboundOptions{

View file

@ -44,7 +44,7 @@ func testShadowsocksPlugin(t *testing.T, name string, opts string, args string)
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeShadowsocks, Type: C.TypeShadowsocks,
ShadowsocksOptions: option.ShadowsocksOutboundOptions{ ShadowsocksOptions: option.ShadowsocksOutboundOptions{

View file

@ -37,7 +37,7 @@ func TestTCPSlowOpen(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -46,7 +46,7 @@ func TestUTLS(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -31,7 +31,7 @@ func TestTrojanOutbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeTrojan, Type: C.TypeTrojan,
TrojanOptions: option.TrojanOutboundOptions{ TrojanOptions: option.TrojanOutboundOptions{
@ -92,7 +92,7 @@ func TestTrojanSelf(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -167,7 +167,7 @@ func TestTrojanPlainSelf(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -62,7 +62,7 @@ func testTUICSelf(t *testing.T, udpStream bool, zeroRTTHandshake bool) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -171,7 +171,7 @@ func TestTUICOutbound(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeTUIC, Type: C.TypeTUIC,
TUICOptions: option.TUICOutboundOptions{ TUICOptions: option.TUICOutboundOptions{

View file

@ -26,7 +26,7 @@ func TestV2RayAPI(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
Tag: "out", Tag: "out",

View file

@ -138,7 +138,7 @@ func testV2RayGRPCOutbound(t *testing.T, forceLite bool) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeVMess, Type: C.TypeVMess,
Tag: "vmess-out", Tag: "vmess-out",

View file

@ -80,7 +80,7 @@ func testVMessTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -169,7 +169,7 @@ func testTrojanTransportSelf(t *testing.T, server *option.V2RayTransportOptions,
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -260,7 +260,7 @@ func TestVMessQUICSelf(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },
@ -340,7 +340,7 @@ func testV2RayTransportNOTLSSelf(t *testing.T, transport *option.V2RayTransportO
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -170,7 +170,7 @@ func testV2RayWebsocketOutbound(t *testing.T, maxEarlyData uint32, earlyDataHead
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeVMess, Type: C.TypeVMess,
Tag: "vmess-out", Tag: "vmess-out",

View file

@ -240,7 +240,7 @@ func testVMessOutboundWithV2Ray(t *testing.T, security string, globalPadding boo
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeVMess, Type: C.TypeVMess,
VMessOptions: option.VMessOutboundOptions{ VMessOptions: option.VMessOutboundOptions{
@ -291,7 +291,7 @@ func testVMessSelf(t *testing.T, security string, alterId int, globalPadding boo
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeDirect, Type: C.TypeDirect,
}, },

View file

@ -32,7 +32,7 @@ func _TestWireGuard(t *testing.T) {
}, },
}, },
}, },
Outbounds: []option.Outbound{ LegacyOutbounds: []option.LegacyOutbound{
{ {
Type: C.TypeWireGuard, Type: C.TypeWireGuard,
WireGuardOptions: option.WireGuardOutboundOptions{ WireGuardOptions: option.WireGuardOutboundOptions{

View file

@ -10,6 +10,7 @@ import (
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
@ -21,10 +22,10 @@ var _ conn.Bind = (*ClientBind)(nil)
type ClientBind struct { type ClientBind struct {
ctx context.Context ctx context.Context
logger logger.Logger
pauseManager pause.Manager pauseManager pause.Manager
bindCtx context.Context bindCtx context.Context
bindDone context.CancelFunc bindDone context.CancelFunc
errorHandler E.Handler
dialer N.Dialer dialer N.Dialer
reservedForEndpoint map[netip.AddrPort][3]uint8 reservedForEndpoint map[netip.AddrPort][3]uint8
connAccess sync.Mutex connAccess sync.Mutex
@ -35,11 +36,11 @@ type ClientBind struct {
reserved [3]uint8 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{ return &ClientBind{
ctx: ctx, ctx: ctx,
logger: logger,
pauseManager: service.FromContext[pause.Manager](ctx), pauseManager: service.FromContext[pause.Manager](ctx),
errorHandler: errorHandler,
dialer: dialer, dialer: dialer,
reservedForEndpoint: make(map[netip.AddrPort][3]uint8), reservedForEndpoint: make(map[netip.AddrPort][3]uint8),
done: make(chan struct{}), done: make(chan struct{}),
@ -115,7 +116,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
return return
default: default:
} }
c.errorHandler.NewError(context.Background(), E.Cause(err, "connect to server")) c.logger.Error(E.Cause(err, "connect to server"))
err = nil err = nil
c.pauseManager.WaitActive() c.pauseManager.WaitActive()
time.Sleep(time.Second) time.Sleep(time.Second)
@ -127,7 +128,7 @@ func (c *ClientBind) receive(packets [][]byte, sizes []int, eps []conn.Endpoint)
select { select {
case <-c.done: case <-c.done:
default: default:
c.errorHandler.NewError(context.Background(), E.Cause(err, "read packet")) c.logger.Error(context.Background(), E.Cause(err, "read packet"))
err = nil err = nil
} }
return return