Compare commits

...

11 Commits

Author SHA1 Message Date
世界
47736b27ba refactor: Modular inbounds 2024-11-03 20:24:41 +08:00
世界
0b2c7ec35c refactor: Modular outbounds 2024-11-03 19:56:44 +08:00
世界
e537c56b6b Implement dns-hijack 2024-11-03 19:47:17 +08:00
世界
b456aff4ac Implement resolve(server) 2024-11-03 19:47:15 +08:00
世界
4da652ff02 Implement TCP and ICMP rejects 2024-11-03 19:47:11 +08:00
世界
262727ec6c Crazy sekai overturns the small pond 2024-11-03 19:47:09 +08:00
世界
81dc9e7698 documentation: Bump version 2024-11-02 21:40:38 +08:00
世界
d876077281 Update dependencies 2024-11-02 21:40:28 +08:00
世界
0df81c297b Update quic-go to v0.48.0 2024-11-02 21:40:28 +08:00
世界
b460484e43 Fix "Fix metadata context" 2024-11-02 21:37:04 +08:00
世界
0e511791e8 platform: Add openURL event 2024-11-02 17:13:13 +08:00
235 changed files with 6527 additions and 4447 deletions

View File

@@ -1,104 +0,0 @@
package adapter
import (
"context"
"net"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@@ -6,27 +6,53 @@ import (
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
// Deprecated
type ConnectionHandler interface {
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
}
type ConnectionHandlerEx interface {
NewConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
// Deprecated: use PacketHandlerEx instead
type PacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
}
type PacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, source M.Socksaddr)
}
// Deprecated: use OOBPacketHandlerEx instead
type OOBPacketHandler interface {
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
}
type OOBPacketHandlerEx interface {
NewPacketEx(buffer *buf.Buffer, oob []byte, source M.Socksaddr)
}
// Deprecated
type PacketConnectionHandler interface {
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type PacketConnectionHandlerEx interface {
NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
type UpstreamHandlerAdapter interface {
N.TCPConnectionHandler
N.UDPConnectionHandler
E.Handler
}
type UpstreamHandlerAdapterEx interface {
N.TCPConnectionHandlerEx
N.UDPConnectionHandlerEx
}

View File

@@ -2,13 +2,12 @@ package adapter
import (
"context"
"net"
"net/netip"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Inbound interface {
@@ -17,11 +16,19 @@ type Inbound interface {
Tag() string
}
type InjectableInbound interface {
type TCPInjectableInbound interface {
Inbound
Network() []string
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
ConnectionHandlerEx
}
type UDPInjectableInbound interface {
Inbound
PacketConnectionHandlerEx
}
type InboundRegistry interface {
option.InboundOptionsRegistry
CreateInbound(ctx context.Context, router Router, logger log.ContextLogger, tag string, outboundType string, options any) (Inbound, error)
}
type InboundContext struct {
@@ -43,10 +50,15 @@ type InboundContext struct {
// cache
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
InboundOptions option.InboundOptions
// Deprecated: implement in rule action
InboundDetour string
LastInbound string
OriginDestination M.Socksaddr
// Deprecated
InboundOptions option.InboundOptions
UDPDisableDomainUnmapping bool
DNSServer string
DestinationAddresses []netip.Addr
SourceGeoIPCode string
GeoIPCode string

View File

@@ -0,0 +1,21 @@
package inbound
type Adapter struct {
inboundType string
inboundTag string
}
func NewAdapter(inboundType string, inboundTag string) Adapter {
return Adapter{
inboundType: inboundType,
inboundTag: inboundTag,
}
}
func (a *Adapter) Type() string {
return a.inboundType
}
func (a *Adapter) Tag() string {
return a.inboundTag
}

View File

@@ -0,0 +1,68 @@
package inbound
import (
"context"
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
)
type ConstructorFunc[T any] func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options T) (adapter.Inbound, error)
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
registry.register(outboundType, func() any {
return new(Options)
}, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error) {
return constructor(ctx, router, logger, tag, common.PtrValueOrDefault(options.(*Options)))
})
}
var _ adapter.InboundRegistry = (*Registry)(nil)
type (
optionsConstructorFunc func() any
constructorFunc func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options any) (adapter.Inbound, error)
)
type Registry struct {
access sync.Mutex
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Inbound, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[outboundType] = optionsConstructor
r.constructors[outboundType] = constructor
}

View File

@@ -2,8 +2,9 @@ package adapter
import (
"context"
"net"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
N "github.com/sagernet/sing/common/network"
)
@@ -15,6 +16,9 @@ type Outbound interface {
Network() []string
Dependencies() []string
N.Dialer
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
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"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
@@ -21,42 +19,6 @@ import (
N "github.com/sagernet/sing/common/network"
)
type myOutboundAdapter struct {
protocol string
network []string
router adapter.Router
logger log.ContextLogger
tag string
dependencies []string
}
func (a *myOutboundAdapter) Type() string {
return a.protocol
}
func (a *myOutboundAdapter) Tag() string {
return a.tag
}
func (a *myOutboundAdapter) Network() []string {
return a.network
}
func (a *myOutboundAdapter) Dependencies() []string {
return a.dependencies
}
func (a *myOutboundAdapter) NewError(ctx context.Context, err error) {
NewError(a.logger, ctx, err)
}
func withDialerDependency(options option.DialerOptions) []string {
if options.Detour != "" {
return []string{options.Detour}
}
return nil
}
func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata adapter.InboundContext) error {
ctx = adapter.WithContext(ctx, &metadata)
var outConn net.Conn
@@ -69,7 +31,7 @@ func NewConnection(ctx context.Context, this N.Dialer, conn net.Conn, metadata a
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
err = N.ReportConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
return err
@@ -96,7 +58,7 @@ func NewDirectConnection(ctx context.Context, router adapter.Router, this N.Dial
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
err = N.ReportConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
return err
@@ -117,14 +79,14 @@ func NewPacketConnection(ctx context.Context, this N.Dialer, conn N.PacketConn,
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
return err
}
if destinationAddress.IsValid() {
if metadata.Destination.IsFqdn() {
if metadata.InboundOptions.UDPDisableDomainUnmapping {
if metadata.UDPDisableDomainUnmapping {
outConn = bufio.NewUnidirectionalNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
} else {
outConn = bufio.NewNATPacketConn(bufio.NewPacketConn(outConn), M.SocksaddrFrom(destinationAddress, metadata.Destination.Port), metadata.Destination)
@@ -165,7 +127,7 @@ func NewDirectPacketConnection(ctx context.Context, router adapter.Router, this
if err != nil {
return N.ReportHandshakeFailure(conn, err)
}
err = N.ReportHandshakeSuccess(conn)
err = N.ReportPacketConnHandshakeSuccess(conn, outConn)
if err != nil {
outConn.Close()
return err
@@ -233,12 +195,3 @@ func CopyEarlyConn(ctx context.Context, conn net.Conn, serverConn net.Conn) erro
}
return bufio.CopyConn(ctx, conn, serverConn)
}
func NewError(logger log.ContextLogger, ctx context.Context, err error) {
common.Close(err)
if E.IsClosedOrCanceled(err) {
logger.DebugContext(ctx, "connection closed: ", err)
return
}
logger.ErrorContext(ctx, err)
}

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
optionsType map[string]optionsConstructorFunc
constructors map[string]constructorFunc
}
func NewRegistry() *Registry {
return &Registry{
optionsType: make(map[string]optionsConstructorFunc),
constructors: make(map[string]constructorFunc),
}
}
func (r *Registry) CreateOptions(outboundType string) (any, bool) {
r.access.Lock()
defer r.access.Unlock()
optionsConstructor, loaded := r.optionsType[outboundType]
if !loaded {
return nil, false
}
return optionsConstructor(), true
}
func (r *Registry) CreateOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Outbound, error) {
r.access.Lock()
defer r.access.Unlock()
constructor, loaded := r.constructors[outboundType]
if !loaded {
return nil, E.New("outbound type not found: " + outboundType)
}
return constructor(ctx, router, logger, tag, options)
}
func (r *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
r.access.Lock()
defer r.access.Unlock()
r.optionsType[outboundType] = optionsConstructor
r.constructors[outboundType] = constructor
}

View File

@@ -34,6 +34,8 @@ type Router interface {
FakeIPStore() FakeIPStore
ConnectionRouter
PreMatch(metadata InboundContext) error
ConnectionRouterEx
GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error)
@@ -70,6 +72,18 @@ type Router interface {
ResetNetwork() error
}
// Deprecated: Use ConnectionRouterEx instead.
type ConnectionRouter interface {
RouteConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
}
type ConnectionRouterEx interface {
ConnectionRouter
RouteConnectionEx(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
}
func ContextWithRouter(ctx context.Context, router Router) context.Context {
return service.ContextWith(ctx, router)
}
@@ -78,28 +92,6 @@ func RouterFromContext(ctx context.Context) Router {
return service.FromContext[Router](ctx)
}
type HeadlessRule interface {
Match(metadata *InboundContext) bool
String() string
}
type Rule interface {
HeadlessRule
Service
Type() string
UpdateGeosite() error
Outbound() string
}
type DNSRule interface {
Rule
DisableCache() bool
RewriteTTL() *uint32
ClientSubnet() *netip.Prefix
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleSet interface {
Name() string
StartContext(ctx context.Context, startContext *HTTPStartContext) error

38
adapter/rule.go Normal file
View File

@@ -0,0 +1,38 @@
package adapter
import (
C "github.com/sagernet/sing-box/constant"
)
type HeadlessRule interface {
Match(metadata *InboundContext) bool
String() string
}
type Rule interface {
HeadlessRule
Service
Type() string
UpdateGeosite() error
Action() RuleAction
}
type DNSRule interface {
Rule
WithAddressLimit() bool
MatchAddressLimit(metadata *InboundContext) bool
}
type RuleAction interface {
Type() string
String() string
}
func IsFinalAction(action RuleAction) bool {
switch action.Type() {
case C.RuleActionTypeSniff, C.RuleActionTypeResolve:
return false
default:
return true
}
}

View File

@@ -4,112 +4,165 @@ import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type (
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
)
func NewUpstreamHandler(
func NewUpstreamHandlerEx(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
connectionHandler ConnectionHandlerFuncEx,
packetHandler PacketConnectionHandlerFuncEx,
) UpstreamHandlerAdapterEx {
return &myUpstreamHandlerWrapperEx{
metadata: metadata,
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
type myUpstreamHandlerWrapper struct {
type myUpstreamHandlerWrapperEx struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFuncEx
}
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.connectionHandler(ctx, conn, myMetadata)
w.connectionHandler(ctx, conn, myMetadata, onClose)
}
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.packetHandler(ctx, conn, myMetadata)
w.packetHandler(ctx, conn, myMetadata, onClose)
}
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
type myUpstreamContextHandlerWrapperEx struct {
connectionHandler ConnectionHandlerFuncEx
packetHandler PacketConnectionHandlerFuncEx
}
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source,
Destination: metadata.Destination,
}
}
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
func NewUpstreamContextHandlerEx(
connectionHandler ConnectionHandlerFuncEx,
packetHandler PacketConnectionHandlerFuncEx,
) UpstreamHandlerAdapterEx {
return &myUpstreamContextHandlerWrapperEx{
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.connectionHandler(ctx, conn, *myMetadata)
w.connectionHandler(ctx, conn, *myMetadata, onClose)
}
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
if source.IsValid() {
myMetadata.Source = source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
if destination.IsValid() {
myMetadata.Destination = destination
}
return w.packetHandler(ctx, conn, *myMetadata)
w.packetHandler(ctx, conn, *myMetadata, onClose)
}
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
func NewRouteHandlerEx(
metadata InboundContext,
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeHandlerWrapperEx{
metadata: metadata,
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeHandlerWrapperEx)(nil)
type routeHandlerWrapperEx struct {
metadata InboundContext
router ConnectionRouterEx
}
func (r *routeHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, r.metadata, onClose)
}
func (r *routeHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
if source.IsValid() {
r.metadata.Source = source
}
if destination.IsValid() {
r.metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, r.metadata, onClose)
}
func NewRouteContextHandlerEx(
router ConnectionRouterEx,
) UpstreamHandlerAdapterEx {
return &routeContextHandlerWrapperEx{
router: router,
}
}
var _ UpstreamHandlerAdapterEx = (*routeContextHandlerWrapperEx)(nil)
type routeContextHandlerWrapperEx struct {
router ConnectionRouterEx
}
func (r *routeContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RouteConnectionEx(ctx, conn, *metadata, onClose)
}
func (r *routeContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
metadata := ContextFrom(ctx)
if source.IsValid() {
metadata.Source = source
}
if destination.IsValid() {
metadata.Destination = destination
}
r.router.RoutePacketConnectionEx(ctx, conn, *metadata, onClose)
}

216
adapter/upstream_legacy.go Normal file
View File

@@ -0,0 +1,216 @@
package adapter
import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type (
// Deprecated
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
// Deprecated
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
)
// Deprecated
func NewUpstreamHandler(
metadata InboundContext,
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamHandlerWrapper{
metadata: metadata,
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
// Deprecated
type myUpstreamHandlerWrapper struct {
metadata InboundContext
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, myMetadata)
}
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, myMetadata)
}
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated
func UpstreamMetadata(metadata InboundContext) M.Metadata {
return M.Metadata{
Source: metadata.Source,
Destination: metadata.Destination,
}
}
// Deprecated
type myUpstreamContextHandlerWrapper struct {
connectionHandler ConnectionHandlerFunc
packetHandler PacketConnectionHandlerFunc
errorHandler E.Handler
}
// Deprecated
func NewUpstreamContextHandler(
connectionHandler ConnectionHandlerFunc,
packetHandler PacketConnectionHandlerFunc,
errorHandler E.Handler,
) UpstreamHandlerAdapter {
return &myUpstreamContextHandlerWrapper{
connectionHandler: connectionHandler,
packetHandler: packetHandler,
errorHandler: errorHandler,
}
}
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.connectionHandler(ctx, conn, *myMetadata)
}
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.packetHandler(ctx, conn, *myMetadata)
}
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.errorHandler.NewError(ctx, err)
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteHandler(
metadata InboundContext,
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeHandlerWrapper{
metadata: metadata,
router: router,
logger: logger,
}
}
// Deprecated: Use ConnectionRouterEx instead.
func NewRouteContextHandler(
router ConnectionRouter,
logger logger.ContextLogger,
) UpstreamHandlerAdapter {
return &routeContextHandlerWrapper{
router: router,
logger: logger,
}
}
var _ UpstreamHandlerAdapter = (*routeHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
type routeHandlerWrapper struct {
metadata InboundContext
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := w.metadata
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, myMetadata)
}
func (w *routeHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}
var _ UpstreamHandlerAdapter = (*routeContextHandlerWrapper)(nil)
// Deprecated: Use ConnectionRouterEx instead.
type routeContextHandlerWrapper struct {
router ConnectionRouter
logger logger.ContextLogger
}
func (w *routeContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RouteConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
myMetadata := ContextFrom(ctx)
if metadata.Source.IsValid() {
myMetadata.Source = metadata.Source
}
if metadata.Destination.IsValid() {
myMetadata.Destination = metadata.Destination
}
return w.router.RoutePacketConnection(ctx, conn, *myMetadata)
}
func (w *routeContextHandlerWrapper) NewError(ctx context.Context, err error) {
w.logger.ErrorContext(ctx, err)
}

View File

@@ -4,7 +4,6 @@ import (
"context"
"net"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
)
@@ -16,8 +15,7 @@ type V2RayServerTransport interface {
}
type V2RayServerTransportHandler interface {
N.TCPConnectionHandler
E.Handler
N.TCPConnectionHandlerEx
}
type V2RayClientTransport interface {

91
box.go
View File

@@ -14,10 +14,9 @@ import (
"github.com/sagernet/sing-box/experimental"
"github.com/sagernet/sing-box/experimental/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/outbound"
"github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
@@ -44,16 +43,37 @@ type Box struct {
type Options struct {
option.Options
Context context.Context
PlatformInterface platform.Interface
PlatformLogWriter log.PlatformWriter
}
func Context(ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry)
ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry)
}
if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.OutboundRegistry](ctx) == nil {
ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry)
ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry)
}
return ctx
}
func New(options Options) (*Box, error) {
createdAt := time.Now()
ctx := options.Context
if ctx == nil {
ctx = context.Background()
}
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
if inboundRegistry == nil {
return nil, E.New("missing inbound registry in context")
}
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
if outboundRegistry == nil {
return nil, E.New("missing outbound registry in context")
}
ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.WithDefaultManager(ctx)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
@@ -70,8 +90,9 @@ func New(options Options) (*Box, error) {
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
needV2RayAPI = true
}
platformInterface := service.FromContext[platform.Interface](ctx)
var defaultLogWriter io.Writer
if options.PlatformInterface != nil {
if platformInterface != nil {
defaultLogWriter = io.Discard
}
logFactory, err := log.New(log.Options{
@@ -92,64 +113,92 @@ func New(options Options) (*Box, error) {
common.PtrValueOrDefault(options.DNS),
common.PtrValueOrDefault(options.NTP),
options.Inbounds,
options.PlatformInterface,
)
if err != nil {
return nil, E.Cause(err, "parse route options")
}
//nolint:staticcheck
if len(options.LegacyInbounds) > 0 {
for _, legacyInbound := range options.LegacyInbounds {
options.Inbounds = append(options.Inbounds, option.Inbound{
Type: legacyInbound.Type,
Tag: legacyInbound.Tag,
Options: common.Must1(legacyInbound.RawOptions()),
})
}
}
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))
for i, inboundOptions := range options.Inbounds {
var in adapter.Inbound
var currentInbound adapter.Inbound
var tag string
if inboundOptions.Tag != "" {
tag = inboundOptions.Tag
} else {
tag = F.ToString(i)
}
in, err = inbound.New(
currentInbound, err = inboundRegistry.CreateInbound(
ctx,
router,
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
tag,
inboundOptions,
options.PlatformInterface,
inboundOptions.Type,
inboundOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "parse inbound[", i, "]")
}
inbounds = append(inbounds, in)
inbounds = append(inbounds, currentInbound)
}
for i, outboundOptions := range options.Outbounds {
var out adapter.Outbound
var currentOutbound adapter.Outbound
var tag string
if outboundOptions.Tag != "" {
tag = outboundOptions.Tag
} else {
tag = F.ToString(i)
}
out, err = outbound.New(
ctx,
outboundCtx := ctx
if tag != "" {
// TODO: remove this
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
Outbound: tag,
})
}
currentOutbound, err = outboundRegistry.CreateOutbound(
outboundCtx,
router,
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
tag,
outboundOptions)
outboundOptions.Type,
outboundOptions.Options,
)
if err != nil {
return nil, E.Cause(err, "parse outbound[", i, "]")
}
outbounds = append(outbounds, out)
outbounds = append(outbounds, currentOutbound)
}
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
common.Must(oErr)
outbounds = append(outbounds, out)
return out
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
common.Must(cErr)
outbounds = append(outbounds, defaultOutbound)
return defaultOutbound
})
if err != nil {
return nil, err
}
if options.PlatformInterface != nil {
err = options.PlatformInterface.Initialize(ctx, router)
if platformInterface != nil {
err = platformInterface.Initialize(ctx, router)
if err != nil {
return nil, E.Cause(err, "initialize platform interface")
}

View File

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

View File

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

View File

@@ -68,29 +68,19 @@ func merge(outputPath string) error {
}
func mergePathResources(options *option.Options) error {
for index, inbound := range options.Inbounds {
rawOptions, err := inbound.RawOptions()
if err != nil {
return err
}
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
for _, inbound := range options.Inbounds {
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
}
options.Inbounds[index] = inbound
}
for index, outbound := range options.Outbounds {
rawOptions, err := outbound.RawOptions()
if err != nil {
return err
}
for _, outbound := range options.Outbounds {
switch outbound.Type {
case C.TypeSSH:
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
}
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
}
options.Outbounds[index] = outbound
}
return nil
}
@@ -138,13 +128,12 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
return options
}
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
if options.PrivateKeyPath != "" {
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
}
}
return options
}
func trimStringArray(array []string) []string {

View File

@@ -10,7 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/route/rule"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/json"
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
}
for i, ruleOptions := range plainRuleSet.Rules {
var currentRule adapter.HeadlessRule
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
if err != nil {
return E.Cause(err, "parse rule_set.rules.[", i, "]")
}

View File

@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtended[option.Options](configContent)
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
}
var mergedMessage json.RawMessage
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
if err != nil {
return option.Options{}, E.Cause(err, "merge config at ", options.path)
}
}
var mergedOptions option.Options
err = mergedOptions.UnmarshalJSON(mergedMessage)
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged config")
}

View File

@@ -1,6 +1,9 @@
package main
import (
"errors"
"os"
"github.com/sagernet/sing-box"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
@@ -23,7 +26,9 @@ func init() {
func createPreStartedClient() (*box.Box, error) {
options, err := readConfigAndMerge()
if err != nil {
return nil, err
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
return nil, err
}
}
instance, err := box.New(box.Options{Options: options})
if err != nil {

View File

@@ -5,7 +5,7 @@ import (
"testing"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/route"
"github.com/sagernet/sing-box/route/rule"
"github.com/stretchr/testify/require"
)
@@ -26,7 +26,7 @@ example.arpa
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := route.NewHeadlessRule(nil, rules[0])
rule, err := rule.NewHeadlessRule(nil, rules[0])
require.NoError(t, err)
matchDomain := []string{
"example.org",
@@ -85,7 +85,7 @@ func TestHosts(t *testing.T) {
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := route.NewHeadlessRule(nil, rules[0])
rule, err := rule.NewHeadlessRule(nil, rules[0])
require.NoError(t, err)
matchDomain := []string{
"google.com",
@@ -115,7 +115,7 @@ www.example.org
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := route.NewHeadlessRule(nil, rules[0])
rule, err := rule.NewHeadlessRule(nil, rules[0])
require.NoError(t, err)
matchDomain := []string{
"example.com",

View File

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

View File

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

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

136
common/listener/listener.go Normal file
View File

@@ -0,0 +1,136 @@
package listener
import (
"context"
"net"
"sync/atomic"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/settings"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Listener struct {
ctx context.Context
logger logger.ContextLogger
network []string
listenOptions option.ListenOptions
connHandler adapter.ConnectionHandlerEx
packetHandler adapter.PacketHandlerEx
oobPacketHandler adapter.OOBPacketHandlerEx
threadUnsafePacketWriter bool
disablePacketOutput bool
setSystemProxy bool
systemProxySOCKS bool
tcpListener net.Listener
systemProxy settings.SystemProxy
udpConn *net.UDPConn
udpAddr M.Socksaddr
packetOutbound chan *N.PacketBuffer
packetOutboundClosed chan struct{}
shutdown atomic.Bool
}
type Options struct {
Context context.Context
Logger logger.ContextLogger
Network []string
Listen option.ListenOptions
ConnectionHandler adapter.ConnectionHandlerEx
PacketHandler adapter.PacketHandlerEx
OOBPacketHandler adapter.OOBPacketHandlerEx
ThreadUnsafePacketWriter bool
DisablePacketOutput bool
SetSystemProxy bool
SystemProxySOCKS bool
}
func New(
options Options,
) *Listener {
return &Listener{
ctx: options.Context,
logger: options.Logger,
network: options.Network,
listenOptions: options.Listen,
connHandler: options.ConnectionHandler,
packetHandler: options.PacketHandler,
oobPacketHandler: options.OOBPacketHandler,
threadUnsafePacketWriter: options.ThreadUnsafePacketWriter,
disablePacketOutput: options.DisablePacketOutput,
setSystemProxy: options.SetSystemProxy,
systemProxySOCKS: options.SystemProxySOCKS,
}
}
func (l *Listener) Start() error {
if common.Contains(l.network, N.NetworkTCP) {
_, err := l.ListenTCP()
if err != nil {
return err
}
go l.loopTCPIn()
}
if common.Contains(l.network, N.NetworkUDP) {
_, err := l.ListenUDP()
if err != nil {
return err
}
l.packetOutboundClosed = make(chan struct{})
l.packetOutbound = make(chan *N.PacketBuffer, 64)
go l.loopUDPIn()
if !l.disablePacketOutput {
go l.loopUDPOut()
}
}
if l.setSystemProxy {
listenPort := M.SocksaddrFromNet(l.tcpListener.Addr()).Port
var listenAddrString string
listenAddr := l.listenOptions.Listen.Build()
if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1"
} else {
listenAddrString = listenAddr.String()
}
systemProxy, err := settings.NewSystemProxy(l.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), l.systemProxySOCKS)
if err != nil {
return E.Cause(err, "initialize system proxy")
}
err = systemProxy.Enable()
if err != nil {
return E.Cause(err, "set system proxy")
}
l.systemProxy = systemProxy
}
return nil
}
func (l *Listener) Close() error {
l.shutdown.Store(true)
var err error
if l.systemProxy != nil && l.systemProxy.IsEnabled() {
err = l.systemProxy.Disable()
}
return E.Errors(err, common.Close(
l.tcpListener,
common.PtrOrNil(l.udpConn),
))
}
func (l *Listener) TCPListener() net.Listener {
return l.tcpListener
}
func (l *Listener) UDPConn() *net.UDPConn {
return l.udpConn
}
func (l *Listener) ListenOptions() option.ListenOptions {
return l.listenOptions
}

View File

@@ -1,6 +1,6 @@
//go:build go1.21
package inbound
package listener
import "net"

View File

@@ -0,0 +1,16 @@
//go:build go1.23
package listener
import (
"net"
"time"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAliveConfig = net.KeepAliveConfig{
Enable: true,
Idle: idle,
Interval: interval,
}
}

View File

@@ -1,6 +1,6 @@
//go:build !go1.21
package inbound
package listener
import "net"

View File

@@ -0,0 +1,15 @@
//go:build !go1.23
package listener
import (
"net"
"time"
"github.com/sagernet/sing/common/control"
)
func setKeepAliveConfig(listener *net.ListenConfig, idle time.Duration, interval time.Duration) {
listener.KeepAlive = idle
listener.Control = control.Append(listener.Control, control.SetKeepAlivePeriod(idle, interval))
}

View File

@@ -0,0 +1,85 @@
package listener
import (
"net"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/metacubex/tfo-go"
)
func (l *Listener) ListenTCP() (net.Listener, error) {
var err error
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
var tcpListener net.Listener
var listenConfig net.ListenConfig
if l.listenOptions.TCPKeepAlive >= 0 {
keepIdle := time.Duration(l.listenOptions.TCPKeepAlive)
if keepIdle == 0 {
keepIdle = C.TCPKeepAliveInitial
}
keepInterval := time.Duration(l.listenOptions.TCPKeepAliveInterval)
if keepInterval == 0 {
keepInterval = C.TCPKeepAliveInterval
}
setKeepAliveConfig(&listenConfig, keepIdle, keepInterval)
}
if l.listenOptions.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&listenConfig)
}
if l.listenOptions.TCPFastOpen {
var tfoConfig tfo.ListenConfig
tfoConfig.ListenConfig = listenConfig
tcpListener, err = tfoConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
} else {
tcpListener, err = listenConfig.Listen(l.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
}
if err == nil {
l.logger.Info("tcp server started at ", tcpListener.Addr())
}
//nolint:staticcheck
if l.listenOptions.ProxyProtocol || l.listenOptions.ProxyProtocolAcceptNoHeader {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
}
l.tcpListener = tcpListener
return tcpListener, err
}
func (l *Listener) loopTCPIn() {
tcpListener := l.tcpListener
var metadata adapter.InboundContext
for {
conn, err := tcpListener.Accept()
if err != nil {
//nolint:staticcheck
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
l.logger.Error(err)
continue
}
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.tcpListener.Close()
l.logger.Error("tcp listener closed: ", err)
continue
}
//nolint:staticcheck
metadata.InboundDetour = l.listenOptions.Detour
//nolint:staticcheck
metadata.InboundOptions = l.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
metadata.OriginDestination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
ctx := log.ContextWithNewID(l.ctx)
l.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
go l.connHandler.NewConnectionEx(ctx, conn, metadata, nil)
}
}

View File

@@ -0,0 +1,154 @@
package listener
import (
"net"
"os"
"time"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (l *Listener) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(l.listenOptions.Listen.Build(), l.listenOptions.ListenPort)
var lc net.ListenConfig
var udpFragment bool
if l.listenOptions.UDPFragment != nil {
udpFragment = *l.listenOptions.UDPFragment
} else {
udpFragment = l.listenOptions.UDPFragmentDefault
}
if !udpFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
}
udpConn, err := lc.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
if err != nil {
return nil, err
}
l.udpConn = udpConn.(*net.UDPConn)
l.udpAddr = bindAddr
l.logger.Info("udp server started at ", udpConn.LocalAddr())
return udpConn, err
}
func (l *Listener) UDPAddr() M.Socksaddr {
return l.udpAddr
}
func (l *Listener) PacketWriter() N.PacketWriter {
return (*packetWriter)(l)
}
func (l *Listener) loopUDPIn() {
defer close(l.packetOutboundClosed)
var buffer *buf.Buffer
if !l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
defer buffer.Release()
}
buffer.IncRef()
defer buffer.DecRef()
if l.oobPacketHandler != nil {
oob := make([]byte, 1024)
for {
if l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
} else {
buffer.Reset()
}
n, oobN, _, addr, err := l.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
if err != nil {
if l.threadUnsafePacketWriter {
buffer.Release()
}
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener closed: ", err)
return
}
buffer.Truncate(n)
l.oobPacketHandler.NewPacketEx(buffer, oob[:oobN], M.SocksaddrFromNetIP(addr).Unwrap())
}
} else {
for {
if l.threadUnsafePacketWriter {
buffer = buf.NewPacket()
} else {
buffer.Reset()
}
n, addr, err := l.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
if l.threadUnsafePacketWriter {
buffer.Release()
}
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener closed: ", err)
return
}
buffer.Truncate(n)
l.packetHandler.NewPacketEx(buffer, M.SocksaddrFromNetIP(addr).Unwrap())
}
}
}
func (l *Listener) loopUDPOut() {
for {
select {
case packet := <-l.packetOutbound:
destination := packet.Destination.AddrPort()
_, err := l.udpConn.WriteToUDPAddrPort(packet.Buffer.Bytes(), destination)
packet.Buffer.Release()
N.PutPacketBuffer(packet)
if err != nil {
if l.shutdown.Load() && E.IsClosed(err) {
return
}
l.udpConn.Close()
l.logger.Error("udp listener write back: ", destination, ": ", err)
return
}
continue
case <-l.packetOutboundClosed:
}
for {
select {
case packet := <-l.packetOutbound:
packet.Buffer.Release()
N.PutPacketBuffer(packet)
case <-time.After(time.Second):
return
}
}
}
}
type packetWriter Listener
func (w *packetWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
packet := N.NewPacketBuffer()
packet.Buffer = buffer
packet.Destination = destination
select {
case w.packetOutbound <- packet:
return nil
default:
buffer.Release()
N.PutPacketBuffer(packet)
if w.shutdown.Load() {
return os.ErrClosed
}
w.logger.Trace("dropped packet to ", destination)
return nil
}
}
func (w *packetWriter) WriteIsThreadUnsafe() {
}

View File

@@ -15,11 +15,11 @@ import (
)
type Router struct {
router adapter.ConnectionRouter
router adapter.ConnectionRouterEx
service *mux.Service
}
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
if !options.Enabled {
return router, nil
}
@@ -54,6 +54,7 @@ func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.Context
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination == mux.Destination {
// TODO: check if WithContext is necessary
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
} else {
return r.router.RouteConnection(ctx, conn, metadata)
@@ -63,3 +64,15 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
if metadata.Destination == mux.Destination {
r.service.NewConnectionEx(adapter.WithContext(ctx, &metadata), conn, metadata.Source, metadata.Destination, onClose)
return
}
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -1,32 +0,0 @@
package mux
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
vmess "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common/logger"
N "github.com/sagernet/sing/common/network"
)
type V2RayLegacyRouter struct {
router adapter.ConnectionRouter
logger logger.ContextLogger
}
func NewV2RayLegacyRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) adapter.ConnectionRouter {
return &V2RayLegacyRouter{router, logger}
}
func (r *V2RayLegacyRouter) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
if metadata.Destination.Fqdn == vmess.MuxDestination.Fqdn {
r.logger.InfoContext(ctx, "inbound legacy multiplex connection")
return vmess.HandleMuxConnection(ctx, conn, adapter.NewRouteHandler(metadata, r.router, r.logger))
}
return r.router.RouteConnection(ctx, conn, metadata)
}
func (r *V2RayLegacyRouter) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -18,7 +18,7 @@ type (
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
)
func Skip(metadata adapter.InboundContext) bool {
func Skip(metadata *adapter.InboundContext) bool {
// skip server first protocols
switch metadata.Destination.Port {
case 25, 465, 587:

View File

@@ -13,14 +13,14 @@ import (
"github.com/sagernet/sing/common/uot"
)
var _ adapter.ConnectionRouter = (*Router)(nil)
var _ adapter.ConnectionRouterEx = (*Router)(nil)
type Router struct {
router adapter.ConnectionRouter
router adapter.ConnectionRouterEx
logger logger.ContextLogger
}
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {
return &Router{router, logger}
}
@@ -51,3 +51,36 @@ func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata ad
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return r.router.RoutePacketConnection(ctx, conn, metadata)
}
func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
switch metadata.Destination.Fqdn {
case uot.MagicAddress:
request, err := uot.ReadRequest(conn)
if err != nil {
err = E.Cause(err, "UoT read request")
r.logger.ErrorContext(ctx, "process connection from ", metadata.Source, ": ", err)
N.CloseOnHandshakeFailure(conn, onClose, err)
return
}
if request.IsConnect {
r.logger.InfoContext(ctx, "inbound UoT connect connection to ", request.Destination)
} else {
r.logger.InfoContext(ctx, "inbound UoT connection to ", request.Destination)
}
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = request.Destination
r.router.RoutePacketConnectionEx(ctx, uot.NewConn(conn, *request), metadata, onClose)
return
case uot.LegacyMagicAddress:
r.logger.InfoContext(ctx, "inbound legacy UoT connection")
metadata.Domain = metadata.Destination.Fqdn
metadata.Destination = M.Socksaddr{Addr: netip.IPv4Unspecified()}
r.RoutePacketConnectionEx(ctx, uot.NewConn(conn, uot.Request{}), metadata, onClose)
return
}
r.router.RouteConnectionEx(ctx, conn, metadata, onClose)
}
func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) {
r.router.RoutePacketConnectionEx(ctx, conn, metadata, onClose)
}

View File

@@ -22,3 +22,21 @@ const (
RuleSetVersion1 = 1 + iota
RuleSetVersion2
)
const (
RuleActionTypeRoute = "route"
RuleActionTypeReturn = "return"
RuleActionTypeReject = "reject"
RuleActionTypeHijackDNS = "hijack-dns"
RuleActionTypeSniff = "sniff"
RuleActionTypeResolve = "resolve"
)
const (
RuleActionRejectMethodDefault = "default"
RuleActionRejectMethodReset = "reset"
RuleActionRejectMethodNetworkUnreachable = "network-unreachable"
RuleActionRejectMethodHostUnreachable = "host-unreachable"
RuleActionRejectMethodPortUnreachable = "port-unreachable"
RuleActionRejectMethodDrop = "drop"
)

View File

@@ -2,6 +2,20 @@
icon: material/alert-decagram
---
#### 1.11.0-alpha.5
* Fixes and improvements
#### 1.11.0-alpha.2
* Add warnings for usage of deprecated features
* Fixes and improvements
#### 1.11.0-alpha.1
* Update quic-go to v0.48.0
* Fixes and improvements
### 1.10.1
* Fixes and improvements

View File

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

View File

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

View File

@@ -30,10 +30,9 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request
rules = append(rules, Rule{
Type: rule.Type(),
Payload: rule.String(),
Proxy: rule.Outbound(),
Proxy: rule.Action().String(),
})
}
render.JSON(w, r, render.M{
"rules": rules,
})

View File

@@ -5,6 +5,7 @@ import (
"time"
"github.com/sagernet/sing-box/adapter"
R "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
"github.com/sagernet/sing/common/bufio"
@@ -60,7 +61,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
}
var rule string
if t.Rule != nil {
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
rule = F.ToString(t.Rule, " => ", t.Rule.Action())
} else {
rule = "final"
}
@@ -131,19 +132,21 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
outbound string
outboundType string
)
if rule == nil {
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
next = defaultOutbound.Tag()
}
} else {
next = rule.Outbound()
var action adapter.RuleAction
if rule != nil {
action = rule.Action()
}
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
next = routeAction.Outbound
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
next = defaultOutbound.Tag()
}
for {
chain = append(chain, next)
detour, loaded := router.Outbound(next)
if !loaded {
break
}
chain = append(chain, next)
outbound = detour.Tag()
outboundType = detour.Type()
group, isGroup := detour.(adapter.OutboundGroup)
@@ -218,19 +221,21 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
outbound string
outboundType string
)
if rule == nil {
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
next = defaultOutbound.Tag()
}
} else {
next = rule.Outbound()
var action adapter.RuleAction
if rule != nil {
action = rule.Action()
}
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
next = routeAction.Outbound
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
next = defaultOutbound.Tag()
}
for {
chain = append(chain, next)
detour, loaded := router.Outbound(next)
if !loaded {
break
}
chain = append(chain, next)
outbound = detour.Tag()
outboundType = detour.Type()
group, isGroup := detour.(adapter.OutboundGroup)

View File

@@ -20,6 +20,7 @@ type CommandClient struct {
type CommandClientOptions struct {
Command int32
StatusInterval int64
IsMainClient bool
}
type CommandClientHandler interface {
@@ -28,6 +29,7 @@ type CommandClientHandler interface {
ClearLogs()
WriteLogs(messageList StringIterator)
WriteStatus(message *StatusMessage)
OpenURL(url string)
WriteGroups(message OutboundGroupIterator)
InitializeClashMode(modeList StringIterator, currentMode string)
UpdateClashMode(newMode string)
@@ -91,9 +93,13 @@ func (c *CommandClient) Connect() error {
c.handler.Connected()
go c.handleLogConn(conn)
case CommandStatus:
err = binary.Write(conn, binary.BigEndian, c.options.IsMainClient)
if err != nil {
return E.Cause(err, "write is main client")
}
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
if err != nil {
return E.Cause(err, "write interval")
return E.Cause(err, "write header")
}
c.handler.Connected()
go c.handleStatusConn(conn)

View File

@@ -0,0 +1,40 @@
package libbox
import (
"encoding/binary"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/varbin"
)
type myEvent interface {
writeTo(writer varbin.Writer)
}
func readEvent(reader varbin.Reader) (myEvent, error) {
eventType, err := reader.ReadByte()
if err != nil {
return nil, err
}
switch eventType {
case eventTypeEmpty:
return nil, nil
case eventTypeOpenURL:
url, err := varbin.ReadValue[string](reader, binary.BigEndian)
if err != nil {
return nil, err
}
return &eventOpenURL{URL: url}, nil
default:
return nil, E.New("unknown event type: ", eventType)
}
}
type eventOpenURL struct {
URL string
}
func (e *eventOpenURL) writeTo(writer varbin.Writer) {
writer.WriteByte(eventTypeOpenURL)
varbin.Write(writer, binary.BigEndian, e.URL)
}

View File

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

View File

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

View File

@@ -33,6 +33,7 @@ type CommandServer struct {
urlTestUpdate chan struct{}
modeUpdate chan struct{}
logReset chan struct{}
events chan myEvent
closedConnections []Connection
}
@@ -52,6 +53,7 @@ func NewCommandServer(handler CommandServerHandler, maxLines int32) *CommandServ
urlTestUpdate: make(chan struct{}, 1),
modeUpdate: make(chan struct{}, 1),
logReset: make(chan struct{}, 1),
events: make(chan myEvent, 8),
}
server.observer = observable.NewObserver[string](server.subscriber, 64)
return server
@@ -61,6 +63,12 @@ func (s *CommandServer) SetService(newService *BoxService) {
if newService != nil {
service.PtrFromContext[urltest.HistoryStorage](newService.ctx).SetHook(s.urlTestUpdate)
newService.instance.Router().ClashServer().(*clashapi.Server).SetModeUpdateHook(s.modeUpdate)
newService.platformInterface.openURLFunc = func(url string) {
select {
case s.events <- &eventOpenURL{URL: url}:
default:
}
}
}
s.service = newService
s.notifyURLTestUpdate()

View File

@@ -1,6 +1,7 @@
package libbox
import (
std_bufio "bufio"
"encoding/binary"
"net"
"runtime"
@@ -9,9 +10,15 @@ import (
"github.com/sagernet/sing-box/common/conntrack"
"github.com/sagernet/sing-box/experimental/clashapi"
E "github.com/sagernet/sing/common/exceptions"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/memory"
)
const (
eventTypeEmpty byte = iota
eventTypeOpenURL
)
type StatusMessage struct {
Memory int64
Goroutines int32
@@ -44,31 +51,73 @@ func (s *CommandServer) readStatus() StatusMessage {
}
func (s *CommandServer) handleStatusConn(conn net.Conn) error {
var isMainClient bool
err := binary.Read(conn, binary.BigEndian, &isMainClient)
if err != nil {
return E.Cause(err, "read is main client")
}
var interval int64
err := binary.Read(conn, binary.BigEndian, &interval)
err = binary.Read(conn, binary.BigEndian, &interval)
if err != nil {
return E.Cause(err, "read interval")
}
ticker := time.NewTicker(time.Duration(interval))
defer ticker.Stop()
ctx := connKeepAlive(conn)
for {
err = binary.Write(conn, binary.BigEndian, s.readStatus())
if err != nil {
return err
writer := std_bufio.NewWriter(conn)
if isMainClient {
for {
writer.WriteByte(eventTypeEmpty)
err = binary.Write(conn, binary.BigEndian, s.readStatus())
if err != nil {
return err
}
writer.Flush()
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
case event := <-s.events:
event.writeTo(writer)
writer.Flush()
}
}
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
} else {
for {
err = binary.Write(conn, binary.BigEndian, s.readStatus())
if err != nil {
return err
}
writer.Flush()
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
}
}
}
}
func (c *CommandClient) handleStatusConn(conn net.Conn) {
reader := std_bufio.NewReader(conn)
for {
if c.options.IsMainClient {
rawEvent, err := readEvent(reader)
if err != nil {
c.handler.Disconnected(err.Error())
return
}
switch event := rawEvent.(type) {
case *eventOpenURL:
c.handler.OpenURL(event.URL)
continue
case nil:
default:
panic(F.ToString("unexpected event type: ", event))
}
}
var message StatusMessage
err := binary.Read(conn, binary.BigEndian, &message)
err := binary.Read(reader, binary.BigEndian, &message)
if err != nil {
c.handler.Disconnected(err.Error())
return

View File

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

View File

@@ -9,6 +9,8 @@ import (
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/process"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/control"
@@ -16,10 +18,11 @@ import (
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/common/x/list"
"github.com/sagernet/sing/service"
)
func parseConfig(configContent string) (option.Options, error) {
options, err := json.UnmarshalExtended[option.Options]([]byte(configContent))
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
if err != nil {
return option.Options{}, E.Cause(err, "decode config")
}
@@ -27,16 +30,16 @@ func parseConfig(configContent string) (option.Options, error) {
}
func CheckConfig(configContent string) error {
options, err := parseConfig(configContent)
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
if err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil))
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
PlatformInterface: (*platformInterfaceStub)(nil),
Context: ctx,
Options: options,
})
if err == nil {
instance.Close()
@@ -134,8 +137,11 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
}
func (s *platformInterfaceStub) OpenURL(url string) {
}
func FormatConfig(configContent string) (string, error) {
options, err := parseConfig(configContent)
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
if err != nil {
return "", err
}

View File

@@ -25,4 +25,5 @@ type Interface interface {
ClearDNSCache()
ReadWIFIState() adapter.WIFIState
process.Searcher
OpenURL(url string)
}

View File

@@ -17,6 +17,7 @@ import (
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-tun"
@@ -34,28 +35,29 @@ type BoxService struct {
ctx context.Context
cancel context.CancelFunc
instance *box.Box
platformInterface *platformInterfaceWrapper
pauseManager pause.Manager
urlTestHistoryStorage *urltest.HistoryStorage
servicePauseFields
}
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
options, err := parseConfig(configContent)
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
options, err := parseConfig(ctx, configContent)
if err != nil {
return nil, err
}
runtimeDebug.FreeOSMemory()
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(ctx)
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
urlTestHistoryStorage := urltest.NewHistoryStorage()
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
ctx = service.ContextWith[platform.Interface](ctx, platformWrapper)
instance, err := box.New(box.Options{
Context: ctx,
Options: options,
PlatformInterface: platformWrapper,
PlatformLogWriter: platformWrapper,
})
if err != nil {
@@ -67,6 +69,7 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box
ctx: ctx,
cancel: cancel,
instance: instance,
platformInterface: platformWrapper,
urlTestHistoryStorage: urlTestHistoryStorage,
pauseManager: service.FromContext[pause.Manager](ctx),
}, nil
@@ -102,9 +105,10 @@ var (
)
type platformInterfaceWrapper struct {
iif PlatformInterface
useProcFS bool
router adapter.Router
iif PlatformInterface
useProcFS bool
router adapter.Router
openURLFunc func(url string)
}
func (w *platformInterfaceWrapper) Initialize(ctx context.Context, router adapter.Router) error {
@@ -238,3 +242,9 @@ func (w *platformInterfaceWrapper) DisableColors() bool {
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
w.iif.WriteLog(message)
}
func (w *platformInterfaceWrapper) OpenURL(url string) {
if w.openURLFunc != nil {
w.openURLFunc(url)
}
}

View File

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

40
go.mod
View File

@@ -3,60 +3,56 @@ module github.com/sagernet/sing-box
go 1.20
require (
berty.tech/go-libtor v1.0.385
github.com/caddyserver/certmagic v0.20.0
github.com/cloudflare/circl v1.3.7
github.com/cretz/bine v0.2.0
github.com/go-chi/chi/v5 v5.0.12
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/render v1.0.3
github.com/gofrs/uuid/v5 v5.3.0
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
github.com/libdns/alidns v1.0.3
github.com/libdns/cloudflare v0.1.1
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/mholt/acmez v1.2.0
github.com/miekg/dns v1.1.62
github.com/ooni/go-libtor v1.1.8
github.com/oschwald/maxminddb-golang v1.12.0
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.4
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
github.com/sagernet/quic-go v0.47.0-beta.2
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
github.com/sagernet/quic-go v0.48.0-beta.1
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
github.com/sagernet/sing v0.5.0-rc.2
github.com/sagernet/sing-dns v0.3.0-rc.2
github.com/sagernet/sing-mux v0.2.0
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-mux v0.2.1-0.20241020175909-fe6153f7a9ec
github.com/sagernet/sing-quic v0.3.0-rc.1
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
github.com/sagernet/sing-shadowtls v0.1.4
github.com/sagernet/sing-tun v0.4.0-rc.4
github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241023054150-3b5b396d06f7
github.com/sagernet/sing-vmess v0.1.12
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.8.0
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.25.0
golang.org/x/crypto v0.28.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/mod v0.19.0
golang.org/x/net v0.27.0
golang.org/x/sys v0.25.0
golang.org/x/mod v0.20.0
golang.org/x/net v0.30.0
golang.org/x/sys v0.26.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.33.0
howett.net/plist v1.0.1
)
//replace github.com/sagernet/sing => ../sing
require (
github.com/ajg/form v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.6 // indirect
@@ -66,10 +62,10 @@ require (
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
@@ -91,9 +87,9 @@ require (
github.com/zeebo/blake3 v0.2.3 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.23.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

84
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/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
@@ -8,8 +6,7 @@ github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4
github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo=
github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -17,8 +14,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -33,14 +30,14 @@ github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk=
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA=
@@ -70,8 +67,8 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U=
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30=
github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
@@ -81,8 +78,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU=
github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w=
github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
@@ -104,23 +99,23 @@ github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQ
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.4 h1:WzX9ka+iHdupMgy2Vdich+OAt7TM8C2cZbIbzNjBrJY=
github.com/sagernet/gomobile v0.1.4/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I=
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0=
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3 h1:RxEz7LhPNiF/gX/Hg+OXr5lqsM9iVAgmaK1L1vzlDRM=
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/quic-go v0.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU=
github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q=
github.com/sagernet/quic-go v0.48.0-beta.1 h1:86hQZrmuoARI3BpDRkQaP0iAVpywA4YsRrzJPYuPKWg=
github.com/sagernet/quic-go v0.48.0-beta.1/go.mod h1:1WgdDIVD1Gybp40JTWketeSfKA/+or9YMLaG5VeTk4k=
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/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo=
github.com/sagernet/sing v0.5.0-rc.2 h1:tIrs6pRbjJWvI0ITRSg47P1wosY+iSuHpw9t5/hBx+Q=
github.com/sagernet/sing v0.5.0-rc.2/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-dns v0.3.0-rc.2 h1:z1yROBxd/6wik5h53Sz5df1DSmbPTaOu/Z0wAmyXGoQ=
github.com/sagernet/sing-dns v0.3.0-rc.2/go.mod h1:TqLIelI+FAbVEdiTRolhGLOwvhVjY7oT+wezlOJUQ7M=
github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo=
github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing v0.5.0-rc.4.0.20241101160402-8452992a6369 h1:gfiUYWslwKM7OtvG37PV0iIDCWcacJSEUS2h29rpYac=
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/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/go.mod h1:RSwqqHwbtTOX3vs6ms8vMtBGH/0ZNyLm/uwt6TlmR84=
github.com/sagernet/sing-quic v0.3.0-rc.1 h1:SlzL1yfEAKJyRduub8vzOVtbyTLAX7RZEEBZxO5utts=
github.com/sagernet/sing-quic v0.3.0-rc.1/go.mod h1:uX+aUHA0fgIN6U3WViseDpSdTQriViZ7qz0Wbsf1mNQ=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
@@ -129,8 +124,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-tun v0.4.0-rc.4 h1:EFz+UjLZTm+YS3ob1vpqjOga67wyvnxWcbQKfe5wE3Y=
github.com/sagernet/sing-tun v0.4.0-rc.4/go.mod h1:+lQdWhqD4atzrCgRhoyrxBCg1OBru/hAv2BT3kdgmGM=
github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241023054150-3b5b396d06f7 h1:wWfRBSP8v0Gc9yUeMgoKCiG+LIs/+bYLWWwVVYSbGFI=
github.com/sagernet/sing-tun v0.4.0-rc.4.0.20241023054150-3b5b396d06f7/go.mod h1:2v1L3BQKzoOpGuKMwC6pcs/5/Xb5PBqzqL6Lq88IoS8=
github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg=
github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
@@ -141,12 +136,11 @@ github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYASco
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
@@ -168,21 +162,19 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
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/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
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-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -190,19 +182,19 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE=
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=

View File

@@ -1,54 +0,0 @@
package inbound
import (
"context"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func New(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Inbound, platformInterface platform.Interface) (adapter.Inbound, error) {
if options.Type == "" {
return nil, E.New("missing inbound type")
}
switch options.Type {
case C.TypeTun:
return NewTun(ctx, router, logger, tag, options.TunOptions, platformInterface)
case C.TypeRedirect:
return NewRedirect(ctx, router, logger, tag, options.RedirectOptions), nil
case C.TypeTProxy:
return NewTProxy(ctx, router, logger, tag, options.TProxyOptions), nil
case C.TypeDirect:
return NewDirect(ctx, router, logger, tag, options.DirectOptions), nil
case C.TypeSOCKS:
return NewSocks(ctx, router, logger, tag, options.SocksOptions), nil
case C.TypeHTTP:
return NewHTTP(ctx, router, logger, tag, options.HTTPOptions)
case C.TypeMixed:
return NewMixed(ctx, router, logger, tag, options.MixedOptions), nil
case C.TypeShadowsocks:
return NewShadowsocks(ctx, router, logger, tag, options.ShadowsocksOptions)
case C.TypeVMess:
return NewVMess(ctx, router, logger, tag, options.VMessOptions)
case C.TypeTrojan:
return NewTrojan(ctx, router, logger, tag, options.TrojanOptions)
case C.TypeNaive:
return NewNaive(ctx, router, logger, tag, options.NaiveOptions)
case C.TypeHysteria:
return NewHysteria(ctx, router, logger, tag, options.HysteriaOptions)
case C.TypeShadowTLS:
return NewShadowTLS(ctx, router, logger, tag, options.ShadowTLSOptions)
case C.TypeVLESS:
return NewVLESS(ctx, router, logger, tag, options.VLESSOptions)
case C.TypeTUIC:
return NewTUIC(ctx, router, logger, tag, options.TUICOptions)
case C.TypeHysteria2:
return NewHysteria2(ctx, router, logger, tag, options.Hysteria2Options)
default:
return nil, E.New("unknown inbound type: ", options.Type)
}
}

View File

@@ -1,196 +0,0 @@
package inbound
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/settings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/atomic"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
var _ adapter.Inbound = (*myInboundAdapter)(nil)
type myInboundAdapter struct {
protocol string
network []string
ctx context.Context
router adapter.ConnectionRouter
logger log.ContextLogger
tag string
listenOptions option.ListenOptions
connHandler adapter.ConnectionHandler
packetHandler adapter.PacketHandler
oobPacketHandler adapter.OOBPacketHandler
packetUpstream any
// http mixed
setSystemProxy bool
systemProxy settings.SystemProxy
// internal
tcpListener net.Listener
udpConn *net.UDPConn
udpAddr M.Socksaddr
packetOutboundClosed chan struct{}
packetOutbound chan *myInboundPacket
inShutdown atomic.Bool
}
func (a *myInboundAdapter) Type() string {
return a.protocol
}
func (a *myInboundAdapter) Tag() string {
return a.tag
}
func (a *myInboundAdapter) Network() []string {
return a.network
}
func (a *myInboundAdapter) Start() error {
var err error
if common.Contains(a.network, N.NetworkTCP) {
_, err = a.ListenTCP()
if err != nil {
return err
}
go a.loopTCPIn()
}
if common.Contains(a.network, N.NetworkUDP) {
_, err = a.ListenUDP()
if err != nil {
return err
}
a.packetOutboundClosed = make(chan struct{})
a.packetOutbound = make(chan *myInboundPacket)
if a.oobPacketHandler != nil {
if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler {
go a.loopUDPOOBIn()
} else {
go a.loopUDPOOBInThreadSafe()
}
} else {
if _, threadUnsafeHandler := common.Cast[N.ThreadUnsafeWriter](a.packetUpstream); !threadUnsafeHandler {
go a.loopUDPIn()
} else {
go a.loopUDPInThreadSafe()
}
go a.loopUDPOut()
}
}
if a.setSystemProxy {
listenPort := M.SocksaddrFromNet(a.tcpListener.Addr()).Port
var listenAddrString string
listenAddr := a.listenOptions.Listen.Build()
if listenAddr.IsUnspecified() {
listenAddrString = "127.0.0.1"
} else {
listenAddrString = listenAddr.String()
}
var systemProxy settings.SystemProxy
systemProxy, err = settings.NewSystemProxy(a.ctx, M.ParseSocksaddrHostPort(listenAddrString, listenPort), a.protocol == C.TypeMixed)
if err != nil {
return E.Cause(err, "initialize system proxy")
}
err = systemProxy.Enable()
if err != nil {
return E.Cause(err, "set system proxy")
}
a.systemProxy = systemProxy
}
return nil
}
func (a *myInboundAdapter) Close() error {
a.inShutdown.Store(true)
var err error
if a.systemProxy != nil && a.systemProxy.IsEnabled() {
err = a.systemProxy.Disable()
}
return E.Errors(err, common.Close(
a.tcpListener,
common.PtrOrNil(a.udpConn),
))
}
func (a *myInboundAdapter) upstreamHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter {
return adapter.NewUpstreamHandler(metadata, a.newConnection, a.streamPacketConnection, a)
}
func (a *myInboundAdapter) upstreamContextHandler() adapter.UpstreamHandlerAdapter {
return adapter.NewUpstreamContextHandler(a.newConnection, a.newPacketConnection, a)
}
func (a *myInboundAdapter) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
return a.router.RouteConnection(ctx, conn, metadata)
}
func (a *myInboundAdapter) streamPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
return a.router.RoutePacketConnection(ctx, conn, metadata)
}
func (a *myInboundAdapter) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
a.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
return a.router.RoutePacketConnection(ctx, conn, metadata)
}
func (a *myInboundAdapter) createMetadata(conn net.Conn, metadata adapter.InboundContext) adapter.InboundContext {
metadata.Inbound = a.tag
metadata.InboundType = a.protocol
metadata.InboundDetour = a.listenOptions.Detour
metadata.InboundOptions = a.listenOptions.InboundOptions
if !metadata.Source.IsValid() {
metadata.Source = M.SocksaddrFromNet(conn.RemoteAddr()).Unwrap()
}
if !metadata.Destination.IsValid() {
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
}
if tcpConn, isTCP := common.Cast[*net.TCPConn](conn); isTCP {
metadata.OriginDestination = M.SocksaddrFromNet(tcpConn.LocalAddr()).Unwrap()
}
return metadata
}
func (a *myInboundAdapter) createPacketMetadata(conn N.PacketConn, metadata adapter.InboundContext) adapter.InboundContext {
metadata.Inbound = a.tag
metadata.InboundType = a.protocol
metadata.InboundDetour = a.listenOptions.Detour
metadata.InboundOptions = a.listenOptions.InboundOptions
if !metadata.Destination.IsValid() {
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
}
return metadata
}
func (a *myInboundAdapter) newError(err error) {
a.logger.Error(err)
}
func (a *myInboundAdapter) NewError(ctx context.Context, err error) {
NewError(a.logger, ctx, err)
}
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

@@ -1,88 +0,0 @@
package inbound
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (a *myInboundAdapter) ListenTCP() (net.Listener, error) {
var err error
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
var tcpListener net.Listener
var listenConfig net.ListenConfig
// TODO: Add an option to customize the keep alive period
listenConfig.KeepAlive = C.TCPKeepAliveInitial
listenConfig.Control = control.Append(listenConfig.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
if a.listenOptions.TCPMultiPath {
if !go121Available {
return nil, E.New("MultiPath TCP requires go1.21, please recompile your binary.")
}
setMultiPathTCP(&listenConfig)
}
if a.listenOptions.TCPFastOpen {
if !go120Available {
return nil, E.New("TCP Fast Open requires go1.20, please recompile your binary.")
}
tcpListener, err = listenTFO(listenConfig, a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
} else {
tcpListener, err = listenConfig.Listen(a.ctx, M.NetworkFromNetAddr(N.NetworkTCP, bindAddr.Addr), bindAddr.String())
}
if err == nil {
a.logger.Info("tcp server started at ", tcpListener.Addr())
}
if a.listenOptions.ProxyProtocol || a.listenOptions.ProxyProtocolAcceptNoHeader {
return nil, E.New("Proxy Protocol is deprecated and removed in sing-box 1.6.0")
}
a.tcpListener = tcpListener
return tcpListener, err
}
func (a *myInboundAdapter) loopTCPIn() {
tcpListener := a.tcpListener
for {
conn, err := tcpListener.Accept()
if err != nil {
//goland:noinspection GoDeprecation
//nolint:staticcheck
if netError, isNetError := err.(net.Error); isNetError && netError.Temporary() {
a.logger.Error(err)
continue
}
if a.inShutdown.Load() && E.IsClosed(err) {
return
}
a.tcpListener.Close()
a.logger.Error("serve error: ", err)
continue
}
go a.injectTCP(conn, adapter.InboundContext{})
}
}
func (a *myInboundAdapter) injectTCP(conn net.Conn, metadata adapter.InboundContext) {
ctx := log.ContextWithNewID(a.ctx)
metadata = a.createMetadata(conn, metadata)
a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
hErr := a.connHandler.NewConnection(ctx, conn, metadata)
if hErr != nil {
conn.Close()
a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source))
}
}
func (a *myInboundAdapter) routeTCP(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) {
a.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
hErr := a.newConnection(ctx, conn, metadata)
if hErr != nil {
conn.Close()
a.NewError(ctx, E.Cause(hErr, "process connection from ", metadata.Source))
}
}

View File

@@ -1,18 +0,0 @@
//go:build go1.20
package inbound
import (
"context"
"net"
"github.com/metacubex/tfo-go"
)
const go120Available = true
func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) {
var tfoConfig tfo.ListenConfig
tfoConfig.ListenConfig = listenConfig
return tfoConfig.Listen(ctx, network, address)
}

View File

@@ -1,15 +0,0 @@
//go:build !go1.20
package inbound
import (
"context"
"net"
"os"
)
const go120Available = false
func listenTFO(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.Listener, error) {
return nil, os.ErrInvalid
}

View File

@@ -1,229 +0,0 @@
package inbound
import (
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
func (a *myInboundAdapter) ListenUDP() (net.PacketConn, error) {
bindAddr := M.SocksaddrFrom(a.listenOptions.Listen.Build(), a.listenOptions.ListenPort)
var lc net.ListenConfig
var udpFragment bool
if a.listenOptions.UDPFragment != nil {
udpFragment = *a.listenOptions.UDPFragment
} else {
udpFragment = a.listenOptions.UDPFragmentDefault
}
if !udpFragment {
lc.Control = control.Append(lc.Control, control.DisableUDPFragment())
}
udpConn, err := lc.ListenPacket(a.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
if err != nil {
return nil, err
}
a.udpConn = udpConn.(*net.UDPConn)
a.udpAddr = bindAddr
a.logger.Info("udp server started at ", udpConn.LocalAddr())
return udpConn, err
}
func (a *myInboundAdapter) loopUDPIn() {
defer close(a.packetOutboundClosed)
buffer := buf.NewPacket()
defer buffer.Release()
buffer.IncRef()
defer buffer.DecRef()
packetService := (*myInboundPacketAdapter)(a)
for {
buffer.Reset()
n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
return
}
buffer.Truncate(n)
var metadata adapter.InboundContext
metadata.Inbound = a.tag
metadata.InboundType = a.protocol
metadata.InboundOptions = a.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
metadata.OriginDestination = a.udpAddr
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
if err != nil {
a.newError(E.Cause(err, "process packet from ", metadata.Source))
}
}
}
func (a *myInboundAdapter) loopUDPOOBIn() {
defer close(a.packetOutboundClosed)
buffer := buf.NewPacket()
defer buffer.Release()
buffer.IncRef()
defer buffer.DecRef()
packetService := (*myInboundPacketAdapter)(a)
oob := make([]byte, 1024)
for {
buffer.Reset()
n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
if err != nil {
return
}
buffer.Truncate(n)
var metadata adapter.InboundContext
metadata.Inbound = a.tag
metadata.InboundType = a.protocol
metadata.InboundOptions = a.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
metadata.OriginDestination = a.udpAddr
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
if err != nil {
a.newError(E.Cause(err, "process packet from ", metadata.Source))
}
}
}
func (a *myInboundAdapter) loopUDPInThreadSafe() {
defer close(a.packetOutboundClosed)
packetService := (*myInboundPacketAdapter)(a)
for {
buffer := buf.NewPacket()
n, addr, err := a.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
buffer.Release()
return
}
buffer.Truncate(n)
var metadata adapter.InboundContext
metadata.Inbound = a.tag
metadata.InboundType = a.protocol
metadata.InboundOptions = a.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
metadata.OriginDestination = a.udpAddr
err = a.packetHandler.NewPacket(a.ctx, packetService, buffer, metadata)
if err != nil {
buffer.Release()
a.newError(E.Cause(err, "process packet from ", metadata.Source))
}
}
}
func (a *myInboundAdapter) loopUDPOOBInThreadSafe() {
defer close(a.packetOutboundClosed)
packetService := (*myInboundPacketAdapter)(a)
oob := make([]byte, 1024)
for {
buffer := buf.NewPacket()
n, oobN, _, addr, err := a.udpConn.ReadMsgUDPAddrPort(buffer.FreeBytes(), oob)
if err != nil {
buffer.Release()
return
}
buffer.Truncate(n)
var metadata adapter.InboundContext
metadata.Inbound = a.tag
metadata.InboundType = a.protocol
metadata.InboundOptions = a.listenOptions.InboundOptions
metadata.Source = M.SocksaddrFromNetIP(addr).Unwrap()
metadata.OriginDestination = a.udpAddr
err = a.oobPacketHandler.NewPacket(a.ctx, packetService, buffer, oob[:oobN], metadata)
if err != nil {
buffer.Release()
a.newError(E.Cause(err, "process packet from ", metadata.Source))
}
}
}
func (a *myInboundAdapter) loopUDPOut() {
for {
select {
case packet := <-a.packetOutbound:
err := a.writePacket(packet.buffer, packet.destination)
if err != nil && !E.IsClosed(err) {
a.newError(E.New("write back udp: ", err))
}
continue
case <-a.packetOutboundClosed:
}
for {
select {
case packet := <-a.packetOutbound:
packet.buffer.Release()
default:
return
}
}
}
}
func (a *myInboundAdapter) writePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
if destination.IsFqdn() {
udpAddr, err := net.ResolveUDPAddr(N.NetworkUDP, destination.String())
if err != nil {
return err
}
return common.Error(a.udpConn.WriteTo(buffer.Bytes(), udpAddr))
}
return common.Error(a.udpConn.WriteToUDPAddrPort(buffer.Bytes(), destination.AddrPort()))
}
type myInboundPacketAdapter myInboundAdapter
func (s *myInboundPacketAdapter) ReadPacket(buffer *buf.Buffer) (M.Socksaddr, error) {
n, addr, err := s.udpConn.ReadFromUDPAddrPort(buffer.FreeBytes())
if err != nil {
return M.Socksaddr{}, err
}
buffer.Truncate(n)
return M.SocksaddrFromNetIP(addr), nil
}
func (s *myInboundPacketAdapter) WriteIsThreadUnsafe() {
}
type myInboundPacket struct {
buffer *buf.Buffer
destination M.Socksaddr
}
func (s *myInboundPacketAdapter) Upstream() any {
return s.udpConn
}
func (s *myInboundPacketAdapter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
select {
case s.packetOutbound <- &myInboundPacket{buffer, destination}:
return nil
case <-s.packetOutboundClosed:
return os.ErrClosed
}
}
func (s *myInboundPacketAdapter) Close() error {
return s.udpConn.Close()
}
func (s *myInboundPacketAdapter) LocalAddr() net.Addr {
return s.udpConn.LocalAddr()
}
func (s *myInboundPacketAdapter) SetDeadline(t time.Time) error {
return s.udpConn.SetDeadline(t)
}
func (s *myInboundPacketAdapter) SetReadDeadline(t time.Time) error {
return s.udpConn.SetReadDeadline(t)
}
func (s *myInboundPacketAdapter) SetWriteDeadline(t time.Time) error {
return s.udpConn.SetWriteDeadline(t)
}

View File

@@ -1,104 +0,0 @@
package inbound
import (
"context"
"net"
"net/netip"
"time"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/buf"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/udpnat"
)
var _ adapter.Inbound = (*Direct)(nil)
type Direct struct {
myInboundAdapter
udpNat *udpnat.Service[netip.AddrPort]
overrideOption int
overrideDestination M.Socksaddr
}
func NewDirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.DirectInboundOptions) *Direct {
options.UDPFragmentDefault = true
inbound := &Direct{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeDirect,
network: options.Network.Build(),
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
if options.OverrideAddress != "" && options.OverridePort != 0 {
inbound.overrideOption = 1
inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort)
} else if options.OverrideAddress != "" {
inbound.overrideOption = 2
inbound.overrideDestination = M.ParseSocksaddrHostPort(options.OverrideAddress, options.OverridePort)
} else if options.OverridePort != 0 {
inbound.overrideOption = 3
inbound.overrideDestination = M.Socksaddr{Port: options.OverridePort}
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
inbound.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound))
inbound.connHandler = inbound
inbound.packetHandler = inbound
inbound.packetUpstream = inbound.udpNat
return inbound
}
func (d *Direct) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
switch d.overrideOption {
case 1:
metadata.Destination = d.overrideDestination
case 2:
destination := d.overrideDestination
destination.Port = metadata.Destination.Port
metadata.Destination = destination
case 3:
metadata.Destination.Port = d.overrideDestination.Port
}
d.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
return d.router.RouteConnection(ctx, conn, metadata)
}
func (d *Direct) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
switch d.overrideOption {
case 1:
metadata.Destination = d.overrideDestination
case 2:
destination := d.overrideDestination
destination.Port = metadata.Destination.Port
metadata.Destination = destination
case 3:
metadata.Destination.Port = d.overrideDestination.Port
}
d.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &udpnat.DirectBackWriter{Source: conn, Nat: natConn}
})
return nil
}
func (d *Direct) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return d.router.RouteConnection(ctx, conn, metadata)
}
func (d *Direct) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
ctx = log.ContextWithNewID(ctx)
d.logger.InfoContext(ctx, "inbound packet connection from ", metadata.Source)
return d.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -1,114 +0,0 @@
package inbound
import (
std_bufio "bufio"
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tls"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/http"
)
var (
_ adapter.Inbound = (*HTTP)(nil)
_ adapter.InjectableInbound = (*HTTP)(nil)
)
type HTTP struct {
myInboundAdapter
authenticator *auth.Authenticator
tlsConfig tls.ServerConfig
}
func NewHTTP(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) (*HTTP, error) {
inbound := &HTTP{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeHTTP,
network: []string{N.NetworkTCP},
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
setSystemProxy: options.SetSystemProxy,
},
authenticator: auth.NewAuthenticator(options.Users),
}
if options.TLS != nil {
tlsConfig, err := tls.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS))
if err != nil {
return nil, err
}
inbound.tlsConfig = tlsConfig
}
inbound.connHandler = inbound
return inbound, nil
}
func (h *HTTP) Start() error {
if h.tlsConfig != nil {
err := h.tlsConfig.Start()
if err != nil {
return E.Cause(err, "create TLS config")
}
}
return h.myInboundAdapter.Start()
}
func (h *HTTP) Close() error {
return common.Close(
&h.myInboundAdapter,
h.tlsConfig,
)
}
func (h *HTTP) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
var err error
if h.tlsConfig != nil {
conn, err = tls.ServerHandshake(ctx, conn, h.tlsConfig)
if err != nil {
return err
}
}
return http.HandleConnection(ctx, conn, std_bufio.NewReader(conn), h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
}
func (h *HTTP) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}
func (a *myInboundAdapter) upstreamUserHandler(metadata adapter.InboundContext) adapter.UpstreamHandlerAdapter {
return adapter.NewUpstreamHandler(metadata, a.newUserConnection, a.streamUserPacketConnection, a)
}
func (a *myInboundAdapter) newUserConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
user, loaded := auth.UserFromContext[string](ctx)
if !loaded {
a.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination)
return a.router.RouteConnection(ctx, conn, metadata)
}
metadata.User = user
a.logger.InfoContext(ctx, "[", user, "] inbound connection to ", metadata.Destination)
return a.router.RouteConnection(ctx, conn, metadata)
}
func (a *myInboundAdapter) streamUserPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
user, loaded := auth.UserFromContext[string](ctx)
if !loaded {
a.logger.InfoContext(ctx, "inbound packet connection to ", metadata.Destination)
return a.router.RoutePacketConnection(ctx, conn, metadata)
}
metadata.User = user
a.logger.InfoContext(ctx, "[", user, "] inbound packet connection to ", metadata.Destination)
return a.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -1,20 +0,0 @@
//go:build !with_quic
package inbound
import (
"context"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
func NewHysteria(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
return nil, C.ErrQUICNotIncluded
}
func NewHysteria2(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) {
return nil, C.ErrQUICNotIncluded
}

View File

@@ -1,66 +0,0 @@
package inbound
import (
std_bufio "bufio"
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/auth"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/http"
"github.com/sagernet/sing/protocol/socks"
"github.com/sagernet/sing/protocol/socks/socks4"
"github.com/sagernet/sing/protocol/socks/socks5"
)
var (
_ adapter.Inbound = (*Mixed)(nil)
_ adapter.InjectableInbound = (*Mixed)(nil)
)
type Mixed struct {
myInboundAdapter
authenticator *auth.Authenticator
}
func NewMixed(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HTTPMixedInboundOptions) *Mixed {
inbound := &Mixed{
myInboundAdapter{
protocol: C.TypeMixed,
network: []string{N.NetworkTCP},
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
setSystemProxy: options.SetSystemProxy,
},
auth.NewAuthenticator(options.Users),
}
inbound.connHandler = inbound
return inbound
}
func (h *Mixed) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
reader := std_bufio.NewReader(conn)
headerBytes, err := reader.Peek(1)
if err != nil {
return err
}
switch headerBytes[0] {
case socks4.Version, socks5.Version:
return socks.HandleConnection0(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
default:
return http.HandleConnection(ctx, conn, reader, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
}
}
func (h *Mixed) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}

View File

@@ -1,47 +0,0 @@
//go:build with_quic
package inbound
import (
"github.com/sagernet/quic-go"
"github.com/sagernet/quic-go/http3"
"github.com/sagernet/sing-quic"
E "github.com/sagernet/sing/common/exceptions"
)
func (n *Naive) configureHTTP3Listener() error {
err := qtls.ConfigureHTTP3(n.tlsConfig)
if err != nil {
return err
}
udpConn, err := n.ListenUDP()
if err != nil {
return err
}
quicListener, err := qtls.ListenEarly(udpConn, n.tlsConfig, &quic.Config{
MaxIncomingStreams: 1 << 60,
Allow0RTT: true,
})
if err != nil {
udpConn.Close()
return err
}
h3Server := &http3.Server{
Port: int(n.listenOptions.ListenPort),
Handler: n,
}
go func() {
sErr := h3Server.ServeListener(quicListener)
udpConn.Close()
if sErr != nil && !E.IsClosedOrCanceled(sErr) {
n.logger.Error("http3 server serve error: ", sErr)
}
}()
n.h3Server = h3Server
return nil
}

View File

@@ -1,11 +0,0 @@
//go:build !with_quic
package inbound
import (
C "github.com/sagernet/sing-box/constant"
)
func (n *Naive) configureHTTP3Listener() error {
return C.ErrQUICNotIncluded
}

View File

@@ -1,44 +0,0 @@
package inbound
import (
"context"
"net"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
type Redirect struct {
myInboundAdapter
}
func NewRedirect(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.RedirectInboundOptions) *Redirect {
redirect := &Redirect{
myInboundAdapter{
protocol: C.TypeRedirect,
network: []string{N.NetworkTCP},
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
redirect.connHandler = redirect
return redirect
}
func (r *Redirect) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
destination, err := redir.GetOriginalDestination(conn)
if err != nil {
return E.Cause(err, "get redirect destination")
}
metadata.Destination = M.SocksaddrFromNetIP(destination)
return r.newConnection(ctx, conn, metadata)
}

View File

@@ -1,99 +0,0 @@
package inbound
import (
"context"
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks"
"github.com/sagernet/sing-shadowsocks/shadowaead"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/ntp"
)
func NewShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
if len(options.Users) > 0 && len(options.Destinations) > 0 {
return nil, E.New("users and destinations options must not be combined")
}
if len(options.Users) > 0 {
return newShadowsocksMulti(ctx, router, logger, tag, options)
} else if len(options.Destinations) > 0 {
return newShadowsocksRelay(ctx, router, logger, tag, options)
} else {
return newShadowsocks(ctx, router, logger, tag, options)
}
}
var (
_ adapter.Inbound = (*Shadowsocks)(nil)
_ adapter.InjectableInbound = (*Shadowsocks)(nil)
)
type Shadowsocks struct {
myInboundAdapter
service shadowsocks.Service
}
func newShadowsocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*Shadowsocks, error) {
inbound := &Shadowsocks{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeShadowsocks,
network: options.Network.Build(),
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
inbound.connHandler = inbound
inbound.packetHandler = inbound
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
switch {
case options.Method == shadowsocks.MethodNone:
inbound.service = shadowsocks.NewNoneService(int64(udpTimeout.Seconds()), inbound.upstreamContextHandler())
case common.Contains(shadowaead.List, options.Method):
inbound.service, err = shadowaead.NewService(options.Method, nil, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler())
case common.Contains(shadowaead_2022.List, options.Method):
inbound.service, err = shadowaead_2022.NewServiceWithPassword(options.Method, options.Password, int64(udpTimeout.Seconds()), inbound.upstreamContextHandler(), ntp.TimeFuncFromContext(ctx))
default:
err = E.New("unsupported method: ", options.Method)
}
inbound.packetUpstream = inbound.service
return inbound, err
}
func (h *Shadowsocks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
}
func (h *Shadowsocks) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata))
}
func (h *Shadowsocks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}

View File

@@ -1,124 +0,0 @@
package inbound
import (
"context"
"net"
"os"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/mux"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-shadowsocks/shadowaead_2022"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/auth"
"github.com/sagernet/sing/common/buf"
F "github.com/sagernet/sing/common/format"
N "github.com/sagernet/sing/common/network"
)
var (
_ adapter.Inbound = (*ShadowsocksRelay)(nil)
_ adapter.InjectableInbound = (*ShadowsocksRelay)(nil)
)
type ShadowsocksRelay struct {
myInboundAdapter
service *shadowaead_2022.RelayService[int]
destinations []option.ShadowsocksDestination
}
func newShadowsocksRelay(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (*ShadowsocksRelay, error) {
inbound := &ShadowsocksRelay{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeShadowsocks,
network: options.Network.Build(),
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
destinations: options.Destinations,
}
inbound.connHandler = inbound
inbound.packetHandler = inbound
var err error
inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex))
if err != nil {
return nil, err
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
service, err := shadowaead_2022.NewRelayServiceWithPassword[int](
options.Method,
options.Password,
int64(udpTimeout.Seconds()),
adapter.NewUpstreamContextHandler(inbound.newConnection, inbound.newPacketConnection, inbound),
)
if err != nil {
return nil, err
}
err = service.UpdateUsersWithPasswords(common.MapIndexed(options.Destinations, func(index int, user option.ShadowsocksDestination) int {
return index
}), common.Map(options.Destinations, func(user option.ShadowsocksDestination) string {
return user.Password
}), common.Map(options.Destinations, option.ShadowsocksDestination.Build))
if err != nil {
return nil, err
}
inbound.service = service
inbound.packetUpstream = service
return inbound, err
}
func (h *ShadowsocksRelay) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return h.service.NewConnection(adapter.WithContext(log.ContextWithNewID(ctx), &metadata), conn, adapter.UpstreamMetadata(metadata))
}
func (h *ShadowsocksRelay) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext) error {
return h.service.NewPacket(adapter.WithContext(ctx, &metadata), conn, buffer, adapter.UpstreamMetadata(metadata))
}
func (h *ShadowsocksRelay) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}
func (h *ShadowsocksRelay) newConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
destinationIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
destination := h.destinations[destinationIndex].Name
if destination == "" {
destination = F.ToString(destinationIndex)
} else {
metadata.User = destination
}
h.logger.InfoContext(ctx, "[", destination, "] inbound connection to ", metadata.Destination)
return h.router.RouteConnection(ctx, conn, metadata)
}
func (h *ShadowsocksRelay) newPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
destinationIndex, loaded := auth.UserFromContext[int](ctx)
if !loaded {
return os.ErrInvalid
}
destination := h.destinations[destinationIndex].Name
if destination == "" {
destination = F.ToString(destinationIndex)
} else {
metadata.User = destination
}
ctx = log.ContextWithNewID(ctx)
h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection from ", metadata.Source)
h.logger.InfoContext(ctx, "[", destination, "] inbound packet connection to ", metadata.Destination)
return h.router.RoutePacketConnection(ctx, conn, metadata)
}

View File

@@ -1,51 +0,0 @@
package inbound
import (
"context"
"net"
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/uot"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/auth"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/protocol/socks"
)
var (
_ adapter.Inbound = (*Socks)(nil)
_ adapter.InjectableInbound = (*Socks)(nil)
)
type Socks struct {
myInboundAdapter
authenticator *auth.Authenticator
}
func NewSocks(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.SocksInboundOptions) *Socks {
inbound := &Socks{
myInboundAdapter{
protocol: C.TypeSOCKS,
network: []string{N.NetworkTCP},
ctx: ctx,
router: uot.NewRouter(router, logger),
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
auth.NewAuthenticator(options.Users),
}
inbound.connHandler = inbound
return inbound
}
func (h *Socks) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
return socks.HandleConnection(ctx, conn, h.authenticator, h.upstreamUserHandler(metadata), adapter.UpstreamMetadata(metadata))
}
func (h *Socks) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
return os.ErrInvalid
}

View File

@@ -1,130 +0,0 @@
package inbound
import (
"context"
"net"
"net/netip"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/udpnat"
)
type TProxy struct {
myInboundAdapter
udpNat *udpnat.Service[netip.AddrPort]
}
func NewTProxy(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TProxyInboundOptions) *TProxy {
tproxy := &TProxy{
myInboundAdapter: myInboundAdapter{
protocol: C.TypeTProxy,
network: options.Network.Build(),
ctx: ctx,
router: router,
logger: logger,
tag: tag,
listenOptions: options.ListenOptions,
},
}
var udpTimeout time.Duration
if options.UDPTimeout != 0 {
udpTimeout = time.Duration(options.UDPTimeout)
} else {
udpTimeout = C.UDPTimeout
}
tproxy.connHandler = tproxy
tproxy.oobPacketHandler = tproxy
tproxy.udpNat = udpnat.New[netip.AddrPort](int64(udpTimeout.Seconds()), tproxy.upstreamContextHandler())
tproxy.packetUpstream = tproxy.udpNat
return tproxy
}
func (t *TProxy) Start() error {
err := t.myInboundAdapter.Start()
if err != nil {
return err
}
if t.tcpListener != nil {
err = control.Conn(common.MustCast[syscall.Conn](t.tcpListener), func(fd uintptr) error {
return redir.TProxy(fd, M.SocksaddrFromNet(t.tcpListener.Addr()).Addr.Is6())
})
if err != nil {
return E.Cause(err, "configure tproxy TCP listener")
}
}
if t.udpConn != nil {
err = control.Conn(t.udpConn, func(fd uintptr) error {
return redir.TProxy(fd, M.SocksaddrFromNet(t.udpConn.LocalAddr()).Addr.Is6())
})
if err != nil {
return E.Cause(err, "configure tproxy UDP listener")
}
}
return nil
}
func (t *TProxy) NewConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
metadata.Destination = M.SocksaddrFromNet(conn.LocalAddr()).Unwrap()
return t.newConnection(ctx, conn, metadata)
}
func (t *TProxy) NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata adapter.InboundContext) error {
destination, err := redir.GetOriginalDestinationFromOOB(oob)
if err != nil {
return E.Cause(err, "get tproxy destination")
}
metadata.Destination = M.SocksaddrFromNetIP(destination).Unwrap()
t.udpNat.NewContextPacket(ctx, metadata.Source.AddrPort(), buffer, adapter.UpstreamMetadata(metadata), func(natConn N.PacketConn) (context.Context, N.PacketWriter) {
return adapter.WithContext(log.ContextWithNewID(ctx), &metadata), &tproxyPacketWriter{ctx: ctx, source: natConn, destination: metadata.Destination}
})
return nil
}
type tproxyPacketWriter struct {
ctx context.Context
source N.PacketConn
destination M.Socksaddr
conn *net.UDPConn
}
func (w *tproxyPacketWriter) WritePacket(buffer *buf.Buffer, destination M.Socksaddr) error {
defer buffer.Release()
conn := w.conn
if w.destination == destination && conn != nil {
_, err := conn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr()))
if err != nil {
w.conn = nil
}
return err
}
var listener net.ListenConfig
listener.Control = control.Append(listener.Control, control.ReuseAddr())
listener.Control = control.Append(listener.Control, redir.TProxyWriteBack())
packetConn, err := listener.ListenPacket(w.ctx, "udp", destination.String())
if err != nil {
return err
}
udpConn := packetConn.(*net.UDPConn)
if w.destination == destination {
w.conn = udpConn
} else {
defer udpConn.Close()
}
return common.Error(udpConn.WriteToUDPAddrPort(buffer.Bytes(), M.AddrPortFromNet(w.source.LocalAddr())))
}
func (w *tproxyPacketWriter) Close() error {
return common.Close(common.PtrOrNil(w.conn))
}

View File

@@ -1,16 +0,0 @@
//go:build !with_quic
package inbound
import (
"context"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
func NewTUIC(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) {
return nil, C.ErrQUICNotIncluded
}

View File

@@ -3,6 +3,24 @@
package include
import (
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/protocol/hysteria"
"github.com/sagernet/sing-box/protocol/hysteria2"
_ "github.com/sagernet/sing-box/protocol/naive/quic"
"github.com/sagernet/sing-box/protocol/tuic"
_ "github.com/sagernet/sing-box/transport/v2rayquic"
_ "github.com/sagernet/sing-dns/quic"
)
func registerQUICInbounds(registry *inbound.Registry) {
hysteria.RegisterInbound(registry)
tuic.RegisterInbound(registry)
hysteria2.RegisterInbound(registry)
}
func registerQUICOutbounds(registry *outbound.Registry) {
hysteria.RegisterOutbound(registry)
tuic.RegisterOutbound(registry)
hysteria2.RegisterOutbound(registry)
}

View File

@@ -4,13 +4,21 @@ package include
import (
"context"
"io"
"net/http"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/common/listener"
"github.com/sagernet/sing-box/common/tls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/naive"
"github.com/sagernet/sing-box/transport/v2ray"
"github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
)
@@ -20,7 +28,7 @@ func init() {
return nil, C.ErrQUICNotIncluded
})
v2ray.RegisterQUICConstructor(
func(ctx context.Context, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
func(ctx context.Context, logger logger.ContextLogger, options option.V2RayQUICOptions, tlsConfig tls.ServerConfig, handler adapter.V2RayServerTransportHandler) (adapter.V2RayServerTransport, error) {
return nil, C.ErrQUICNotIncluded
},
func(ctx context.Context, dialer N.Dialer, serverAddr M.Socksaddr, options option.V2RayQUICOptions, tlsConfig tls.Config) (adapter.V2RayClientTransport, error) {
@@ -28,3 +36,30 @@ func init() {
},
)
}
func registerQUICInbounds(registry *inbound.Registry) {
inbound.Register[option.HysteriaInboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaInboundOptions) (adapter.Inbound, error) {
return nil, C.ErrQUICNotIncluded
})
inbound.Register[option.TUICInboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICInboundOptions) (adapter.Inbound, error) {
return nil, C.ErrQUICNotIncluded
})
inbound.Register[option.Hysteria2InboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2InboundOptions) (adapter.Inbound, error) {
return nil, C.ErrQUICNotIncluded
})
naive.ConfigureHTTP3ListenerFunc = func(listener *listener.Listener, handler http.Handler, tlsConfig tls.ServerConfig, logger logger.Logger) (io.Closer, error) {
return nil, C.ErrQUICNotIncluded
}
}
func registerQUICOutbounds(registry *outbound.Registry) {
outbound.Register[option.HysteriaOutboundOptions](registry, C.TypeHysteria, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.HysteriaOutboundOptions) (adapter.Outbound, error) {
return nil, C.ErrQUICNotIncluded
})
outbound.Register[option.TUICOutboundOptions](registry, C.TypeTUIC, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.TUICOutboundOptions) (adapter.Outbound, error) {
return nil, C.ErrQUICNotIncluded
})
outbound.Register[option.Hysteria2OutboundOptions](registry, C.TypeHysteria2, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.Hysteria2OutboundOptions) (adapter.Outbound, error) {
return nil, C.ErrQUICNotIncluded
})
}

95
include/registry.go Normal file
View File

@@ -0,0 +1,95 @@
package include
import (
"context"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-box/protocol/block"
"github.com/sagernet/sing-box/protocol/direct"
"github.com/sagernet/sing-box/protocol/dns"
"github.com/sagernet/sing-box/protocol/group"
"github.com/sagernet/sing-box/protocol/http"
"github.com/sagernet/sing-box/protocol/mixed"
"github.com/sagernet/sing-box/protocol/naive"
"github.com/sagernet/sing-box/protocol/redirect"
"github.com/sagernet/sing-box/protocol/shadowsocks"
"github.com/sagernet/sing-box/protocol/shadowtls"
"github.com/sagernet/sing-box/protocol/socks"
"github.com/sagernet/sing-box/protocol/ssh"
"github.com/sagernet/sing-box/protocol/tor"
"github.com/sagernet/sing-box/protocol/trojan"
"github.com/sagernet/sing-box/protocol/tun"
"github.com/sagernet/sing-box/protocol/vless"
"github.com/sagernet/sing-box/protocol/vmess"
E "github.com/sagernet/sing/common/exceptions"
)
func InboundRegistry() *inbound.Registry {
registry := inbound.NewRegistry()
tun.RegisterInbound(registry)
redirect.RegisterRedirect(registry)
redirect.RegisterTProxy(registry)
direct.RegisterInbound(registry)
socks.RegisterInbound(registry)
http.RegisterInbound(registry)
mixed.RegisterInbound(registry)
shadowsocks.RegisterInbound(registry)
vmess.RegisterInbound(registry)
trojan.RegisterInbound(registry)
naive.RegisterInbound(registry)
shadowtls.RegisterInbound(registry)
vless.RegisterInbound(registry)
registerQUICInbounds(registry)
registerStubForRemovedInbounds(registry)
return registry
}
func OutboundRegistry() *outbound.Registry {
registry := outbound.NewRegistry()
direct.RegisterOutbound(registry)
block.RegisterOutbound(registry)
dns.RegisterOutbound(registry)
group.RegisterSelector(registry)
group.RegisterURLTest(registry)
socks.RegisterOutbound(registry)
http.RegisterOutbound(registry)
shadowsocks.RegisterOutbound(registry)
vmess.RegisterOutbound(registry)
trojan.RegisterOutbound(registry)
tor.RegisterOutbound(registry)
ssh.RegisterOutbound(registry)
shadowtls.RegisterOutbound(registry)
vless.RegisterOutbound(registry)
registerQUICOutbounds(registry)
registerWireGuardOutbound(registry)
registerStubForRemovedOutbounds(registry)
return registry
}
func registerStubForRemovedInbounds(registry *inbound.Registry) {
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
})
}
func registerStubForRemovedOutbounds(registry *outbound.Registry) {
outbound.Register[option.ShadowsocksROutboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksROutboundOptions) (adapter.Outbound, error) {
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
})
}

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

@@ -1,122 +1,77 @@
package option
import (
"context"
"time"
C "github.com/sagernet/sing-box/constant"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/service"
)
type InboundOptionsRegistry interface {
CreateOptions(outboundType string) (any, bool)
}
type _Inbound struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
TunOptions TunInboundOptions `json:"-"`
RedirectOptions RedirectInboundOptions `json:"-"`
TProxyOptions TProxyInboundOptions `json:"-"`
DirectOptions DirectInboundOptions `json:"-"`
SocksOptions SocksInboundOptions `json:"-"`
HTTPOptions HTTPMixedInboundOptions `json:"-"`
MixedOptions HTTPMixedInboundOptions `json:"-"`
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
VMessOptions VMessInboundOptions `json:"-"`
TrojanOptions TrojanInboundOptions `json:"-"`
NaiveOptions NaiveInboundOptions `json:"-"`
HysteriaOptions HysteriaInboundOptions `json:"-"`
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
VLESSOptions VLESSInboundOptions `json:"-"`
TUICOptions TUICInboundOptions `json:"-"`
Hysteria2Options Hysteria2InboundOptions `json:"-"`
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Options any `json:"-"`
}
type Inbound _Inbound
func (h *Inbound) RawOptions() (any, error) {
var rawOptionsPtr any
switch h.Type {
case C.TypeTun:
rawOptionsPtr = &h.TunOptions
case C.TypeRedirect:
rawOptionsPtr = &h.RedirectOptions
case C.TypeTProxy:
rawOptionsPtr = &h.TProxyOptions
case C.TypeDirect:
rawOptionsPtr = &h.DirectOptions
case C.TypeSOCKS:
rawOptionsPtr = &h.SocksOptions
case C.TypeHTTP:
rawOptionsPtr = &h.HTTPOptions
case C.TypeMixed:
rawOptionsPtr = &h.MixedOptions
case C.TypeShadowsocks:
rawOptionsPtr = &h.ShadowsocksOptions
case C.TypeVMess:
rawOptionsPtr = &h.VMessOptions
case C.TypeTrojan:
rawOptionsPtr = &h.TrojanOptions
case C.TypeNaive:
rawOptionsPtr = &h.NaiveOptions
case C.TypeHysteria:
rawOptionsPtr = &h.HysteriaOptions
case C.TypeShadowTLS:
rawOptionsPtr = &h.ShadowTLSOptions
case C.TypeVLESS:
rawOptionsPtr = &h.VLESSOptions
case C.TypeTUIC:
rawOptionsPtr = &h.TUICOptions
case C.TypeHysteria2:
rawOptionsPtr = &h.Hysteria2Options
case "":
return nil, E.New("missing inbound type")
default:
return nil, E.New("unknown inbound type: ", h.Type)
}
return rawOptionsPtr, nil
func (h *Inbound) MarshalJSONContext(ctx context.Context) ([]byte, error) {
return badjson.MarshallObjectsContext(ctx, (*_Inbound)(h), h.Options)
}
func (h Inbound) MarshalJSON() ([]byte, error) {
rawOptions, err := h.RawOptions()
if err != nil {
return nil, err
}
return MarshallObjects((_Inbound)(h), rawOptions)
}
func (h *Inbound) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_Inbound)(h))
func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) error {
err := json.Unmarshal(content, (*_Inbound)(h))
if err != nil {
return err
}
rawOptions, err := h.RawOptions()
if err != nil {
return err
}
err = UnmarshallExcluded(bytes, (*_Inbound)(h), rawOptions)
registry := service.FromContext[InboundOptionsRegistry](ctx)
if registry == nil {
return E.New("missing inbound options registry in context")
}
options, loaded := registry.CreateOptions(h.Type)
if !loaded {
return E.New("unknown inbound type: ", h.Type)
}
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Inbound)(h), options)
if err != nil {
return err
}
h.Options = options
return nil
}
// Deprecated: Use rule action instead
type InboundOptions struct {
SniffEnabled bool `json:"sniff,omitempty"`
SniffOverrideDestination bool `json:"sniff_override_destination,omitempty"`
SniffTimeout Duration `json:"sniff_timeout,omitempty"`
DomainStrategy DomainStrategy `json:"domain_strategy,omitempty"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
Detour string `json:"detour,omitempty"`
}
type ListenOptions struct {
Listen *ListenAddress `json:"listen,omitempty"`
ListenPort uint16 `json:"listen_port,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"`
Detour string `json:"detour,omitempty"`
Listen *ListenAddress `json:"listen,omitempty"`
ListenPort uint16 `json:"listen_port,omitempty"`
TCPKeepAlive Duration `json:"tcp_keep_alive,omitempty"`
TCPKeepAliveInterval Duration `json:"tcp_keep_alive_interval,omitempty"`
TCPFastOpen bool `json:"tcp_fast_open,omitempty"`
TCPMultiPath bool `json:"tcp_multi_path,omitempty"`
UDPFragment *bool `json:"udp_fragment,omitempty"`
UDPFragmentDefault bool `json:"-"`
UDPTimeout UDPTimeoutCompat `json:"udp_timeout,omitempty"`
// Deprecated: removed
ProxyProtocol bool `json:"proxy_protocol,omitempty"`
// Deprecated: removed
ProxyProtocolAcceptNoHeader bool `json:"proxy_protocol_accept_no_header,omitempty"`
InboundOptions
}

98
option/inbound_legacy.go Normal file
View File

@@ -0,0 +1,98 @@
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 _LegacyInbound struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
TunOptions TunInboundOptions `json:"-"`
RedirectOptions RedirectInboundOptions `json:"-"`
TProxyOptions TProxyInboundOptions `json:"-"`
DirectOptions DirectInboundOptions `json:"-"`
SocksOptions SocksInboundOptions `json:"-"`
HTTPOptions HTTPMixedInboundOptions `json:"-"`
MixedOptions HTTPMixedInboundOptions `json:"-"`
ShadowsocksOptions ShadowsocksInboundOptions `json:"-"`
VMessOptions VMessInboundOptions `json:"-"`
TrojanOptions TrojanInboundOptions `json:"-"`
NaiveOptions NaiveInboundOptions `json:"-"`
HysteriaOptions HysteriaInboundOptions `json:"-"`
ShadowTLSOptions ShadowTLSInboundOptions `json:"-"`
VLESSOptions VLESSInboundOptions `json:"-"`
TUICOptions TUICInboundOptions `json:"-"`
Hysteria2Options Hysteria2InboundOptions `json:"-"`
}
type LegacyInbound _LegacyInbound
func (h *LegacyInbound) RawOptions() (any, error) {
var rawOptionsPtr any
switch h.Type {
case C.TypeTun:
rawOptionsPtr = &h.TunOptions
case C.TypeRedirect:
rawOptionsPtr = &h.RedirectOptions
case C.TypeTProxy:
rawOptionsPtr = &h.TProxyOptions
case C.TypeDirect:
rawOptionsPtr = &h.DirectOptions
case C.TypeSOCKS:
rawOptionsPtr = &h.SocksOptions
case C.TypeHTTP:
rawOptionsPtr = &h.HTTPOptions
case C.TypeMixed:
rawOptionsPtr = &h.MixedOptions
case C.TypeShadowsocks:
rawOptionsPtr = &h.ShadowsocksOptions
case C.TypeVMess:
rawOptionsPtr = &h.VMessOptions
case C.TypeTrojan:
rawOptionsPtr = &h.TrojanOptions
case C.TypeNaive:
rawOptionsPtr = &h.NaiveOptions
case C.TypeHysteria:
rawOptionsPtr = &h.HysteriaOptions
case C.TypeShadowTLS:
rawOptionsPtr = &h.ShadowTLSOptions
case C.TypeVLESS:
rawOptionsPtr = &h.VLESSOptions
case C.TypeTUIC:
rawOptionsPtr = &h.TUICOptions
case C.TypeHysteria2:
rawOptionsPtr = &h.Hysteria2Options
case "":
return nil, E.New("missing inbound type")
default:
return nil, E.New("unknown inbound type: ", h.Type)
}
return rawOptionsPtr, nil
}
func (h LegacyInbound) MarshalJSON() ([]byte, error) {
rawOptions, err := h.RawOptions()
if err != nil {
return nil, err
}
return badjson.MarshallObjects((_LegacyInbound)(h), rawOptions)
}
func (h *LegacyInbound) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_LegacyInbound)(h))
if err != nil {
return err
}
rawOptions, err := h.RawOptions()
if err != nil {
return err
}
err = badjson.UnmarshallExcluded(bytes, (*_LegacyInbound)(h), rawOptions)
if err != nil {
return err
}
return nil
}

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

View File

@@ -1,104 +1,49 @@
package option
import (
C "github.com/sagernet/sing-box/constant"
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
M "github.com/sagernet/sing/common/metadata"
"github.com/sagernet/sing/service"
)
type OutboundOptionsRegistry interface {
CreateOptions(outboundType string) (any, bool)
}
type _Outbound struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
DirectOptions DirectOutboundOptions `json:"-"`
SocksOptions SocksOutboundOptions `json:"-"`
HTTPOptions HTTPOutboundOptions `json:"-"`
ShadowsocksOptions ShadowsocksOutboundOptions `json:"-"`
VMessOptions VMessOutboundOptions `json:"-"`
TrojanOptions TrojanOutboundOptions `json:"-"`
WireGuardOptions WireGuardOutboundOptions `json:"-"`
HysteriaOptions HysteriaOutboundOptions `json:"-"`
TorOptions TorOutboundOptions `json:"-"`
SSHOptions SSHOutboundOptions `json:"-"`
ShadowTLSOptions ShadowTLSOutboundOptions `json:"-"`
ShadowsocksROptions ShadowsocksROutboundOptions `json:"-"`
VLESSOptions VLESSOutboundOptions `json:"-"`
TUICOptions TUICOutboundOptions `json:"-"`
Hysteria2Options Hysteria2OutboundOptions `json:"-"`
SelectorOptions SelectorOutboundOptions `json:"-"`
URLTestOptions URLTestOutboundOptions `json:"-"`
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Options any `json:"-"`
}
type Outbound _Outbound
func (h *Outbound) RawOptions() (any, error) {
var rawOptionsPtr any
switch h.Type {
case C.TypeDirect:
rawOptionsPtr = &h.DirectOptions
case C.TypeBlock, C.TypeDNS:
rawOptionsPtr = nil
case C.TypeSOCKS:
rawOptionsPtr = &h.SocksOptions
case C.TypeHTTP:
rawOptionsPtr = &h.HTTPOptions
case C.TypeShadowsocks:
rawOptionsPtr = &h.ShadowsocksOptions
case C.TypeVMess:
rawOptionsPtr = &h.VMessOptions
case C.TypeTrojan:
rawOptionsPtr = &h.TrojanOptions
case C.TypeWireGuard:
rawOptionsPtr = &h.WireGuardOptions
case C.TypeHysteria:
rawOptionsPtr = &h.HysteriaOptions
case C.TypeTor:
rawOptionsPtr = &h.TorOptions
case C.TypeSSH:
rawOptionsPtr = &h.SSHOptions
case C.TypeShadowTLS:
rawOptionsPtr = &h.ShadowTLSOptions
case C.TypeShadowsocksR:
rawOptionsPtr = &h.ShadowsocksROptions
case C.TypeVLESS:
rawOptionsPtr = &h.VLESSOptions
case C.TypeTUIC:
rawOptionsPtr = &h.TUICOptions
case C.TypeHysteria2:
rawOptionsPtr = &h.Hysteria2Options
case C.TypeSelector:
rawOptionsPtr = &h.SelectorOptions
case C.TypeURLTest:
rawOptionsPtr = &h.URLTestOptions
case "":
return nil, E.New("missing outbound type")
default:
return nil, E.New("unknown outbound type: ", h.Type)
}
return rawOptionsPtr, nil
func (h *Outbound) MarshalJSONContext(ctx context.Context) ([]byte, error) {
return badjson.MarshallObjectsContext(ctx, (*_Outbound)(h), h.Options)
}
func (h *Outbound) MarshalJSON() ([]byte, error) {
rawOptions, err := h.RawOptions()
if err != nil {
return nil, err
}
return MarshallObjects((*_Outbound)(h), rawOptions)
}
func (h *Outbound) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_Outbound)(h))
func (h *Outbound) UnmarshalJSONContext(ctx context.Context, content []byte) error {
err := json.Unmarshal(content, (*_Outbound)(h))
if err != nil {
return err
}
rawOptions, err := h.RawOptions()
if err != nil {
return err
}
err = UnmarshallExcluded(bytes, (*_Outbound)(h), rawOptions)
registry := service.FromContext[OutboundOptionsRegistry](ctx)
if registry == nil {
return E.New("missing outbound options registry in context")
}
options, loaded := registry.CreateOptions(h.Type)
if !loaded {
return E.New("unknown outbound type: ", h.Type)
}
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Outbound)(h), options)
if err != nil {
return err
}
h.Options = options
return nil
}

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"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
)
type _Rule struct {
@@ -28,7 +29,7 @@ func (r Rule) MarshalJSON() ([]byte, error) {
default:
return nil, E.New("unknown rule type: " + r.Type)
}
return MarshallObjects((_Rule)(r), v)
return badjson.MarshallObjects((_Rule)(r), v)
}
func (r *Rule) UnmarshalJSON(bytes []byte) error {
@@ -46,7 +47,7 @@ func (r *Rule) UnmarshalJSON(bytes []byte) error {
default:
return E.New("unknown rule type: " + r.Type)
}
err = UnmarshallExcluded(bytes, (*_Rule)(r), v)
err = badjson.UnmarshallExcluded(bytes, (*_Rule)(r), v)
if err != nil {
return err
}
@@ -64,7 +65,7 @@ func (r Rule) IsValid() bool {
}
}
type DefaultRule struct {
type RawDefaultRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
Network Listable[string] `json:"network,omitempty"`
@@ -98,26 +99,58 @@ type DefaultRule struct {
RuleSet Listable[string] `json:"rule_set,omitempty"`
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
}
type DefaultRule struct {
RawDefaultRule
RuleAction
}
func (r *DefaultRule) MarshalJSON() ([]byte, error) {
return badjson.MarshallObjects(r.RawDefaultRule, r.RuleAction)
}
func (r *DefaultRule) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &r.RawDefaultRule)
if err != nil {
return err
}
return badjson.UnmarshallExcluded(data, &r.RawDefaultRule, &r.RuleAction)
}
func (r *DefaultRule) IsValid() bool {
var defaultValue DefaultRule
defaultValue.Invert = r.Invert
defaultValue.Outbound = r.Outbound
defaultValue.Action = r.Action
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalRule struct {
Mode string `json:"mode"`
Rules []Rule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Outbound string `json:"outbound,omitempty"`
type _LogicalRule struct {
Mode string `json:"mode"`
Rules []Rule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
}
func (r LogicalRule) IsValid() bool {
type LogicalRule struct {
_LogicalRule
RuleAction
}
func (r *LogicalRule) MarshalJSON() ([]byte, error) {
return badjson.MarshallObjects(r._LogicalRule, r.RuleAction)
}
func (r *LogicalRule) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &r._LogicalRule)
if err != nil {
return err
}
return badjson.UnmarshallExcluded(data, &r._LogicalRule, &r.RuleAction)
}
func (r *LogicalRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, Rule.IsValid)
}

173
option/rule_action.go Normal file
View File

@@ -0,0 +1,173 @@
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 _RuleAction struct {
Action string `json:"action,omitempty"`
RouteOptions RouteActionOptions `json:"-"`
RejectOptions RejectActionOptions `json:"-"`
SniffOptions RouteActionSniff `json:"-"`
ResolveOptions RouteActionResolve `json:"-"`
}
type RuleAction _RuleAction
func (r RuleAction) MarshalJSON() ([]byte, error) {
var v any
switch r.Action {
case C.RuleActionTypeRoute:
r.Action = ""
v = r.RouteOptions
case C.RuleActionTypeReturn:
v = nil
case C.RuleActionTypeReject:
v = r.RejectOptions
case C.RuleActionTypeHijackDNS:
v = nil
case C.RuleActionTypeSniff:
v = r.SniffOptions
case C.RuleActionTypeResolve:
v = r.ResolveOptions
default:
return nil, E.New("unknown rule action: " + r.Action)
}
if v == nil {
return badjson.MarshallObjects((_RuleAction)(r))
}
return badjson.MarshallObjects((_RuleAction)(r), v)
}
func (r *RuleAction) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, (*_RuleAction)(r))
if err != nil {
return err
}
var v any
switch r.Action {
case "", C.RuleActionTypeRoute:
r.Action = C.RuleActionTypeRoute
v = &r.RouteOptions
case C.RuleActionTypeReturn:
v = nil
case C.RuleActionTypeReject:
v = &r.RejectOptions
case C.RuleActionTypeHijackDNS:
v = nil
case C.RuleActionTypeSniff:
v = &r.SniffOptions
case C.RuleActionTypeResolve:
v = &r.ResolveOptions
default:
return E.New("unknown rule action: " + r.Action)
}
if v == nil {
// check unknown fields
return json.UnmarshalDisallowUnknownFields(data, &_RuleAction{})
}
return badjson.UnmarshallExcluded(data, (*_RuleAction)(r), v)
}
type _DNSRuleAction struct {
Action string `json:"action,omitempty"`
RouteOptions DNSRouteActionOptions `json:"-"`
RejectOptions RejectActionOptions `json:"-"`
SniffOptions RouteActionSniff `json:"-"`
ResolveOptions RouteActionResolve `json:"-"`
}
type DNSRuleAction _DNSRuleAction
func (r DNSRuleAction) MarshalJSON() ([]byte, error) {
var v any
switch r.Action {
case C.RuleActionTypeRoute:
r.Action = ""
v = r.RouteOptions
case C.RuleActionTypeReturn:
v = nil
case C.RuleActionTypeReject:
v = r.RejectOptions
default:
return nil, E.New("unknown DNS rule action: " + r.Action)
}
if v == nil {
return badjson.MarshallObjects((_DNSRuleAction)(r))
}
return badjson.MarshallObjects((_DNSRuleAction)(r), v)
}
func (r *DNSRuleAction) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, (*_DNSRuleAction)(r))
if err != nil {
return err
}
var v any
switch r.Action {
case "", C.RuleActionTypeRoute:
r.Action = C.RuleActionTypeRoute
v = &r.RouteOptions
case C.RuleActionTypeReturn:
v = nil
case C.RuleActionTypeReject:
v = &r.RejectOptions
default:
return E.New("unknown DNS rule action: " + r.Action)
}
if v == nil {
// check unknown fields
return json.UnmarshalDisallowUnknownFields(data, &_DNSRuleAction{})
}
return badjson.UnmarshallExcluded(data, (*_DNSRuleAction)(r), v)
}
type RouteActionOptions struct {
Outbound string `json:"outbound"`
UDPDisableDomainUnmapping bool `json:"udp_disable_domain_unmapping,omitempty"`
}
type DNSRouteActionOptions struct {
Server string `json:"server"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
}
type _RejectActionOptions struct {
Method string `json:"method,omitempty"`
}
type RejectActionOptions _RejectActionOptions
func (r *RejectActionOptions) UnmarshalJSON(bytes []byte) error {
err := json.Unmarshal(bytes, (*_RejectActionOptions)(r))
if err != nil {
return err
}
switch r.Method {
case "", C.RuleActionRejectMethodDefault:
r.Method = C.RuleActionRejectMethodDefault
case C.RuleActionRejectMethodReset,
C.RuleActionRejectMethodNetworkUnreachable,
C.RuleActionRejectMethodHostUnreachable,
C.RuleActionRejectMethodPortUnreachable,
C.RuleActionRejectMethodDrop:
default:
return E.New("unknown reject method: " + r.Method)
}
return nil
}
type RouteActionSniff struct {
Sniffer Listable[string] `json:"sniffer,omitempty"`
Timeout Duration `json:"timeout,omitempty"`
}
type RouteActionResolve struct {
Strategy DomainStrategy `json:"strategy,omitempty"`
Server string `json:"server,omitempty"`
}

View File

@@ -7,6 +7,7 @@ import (
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
)
type _DNSRule struct {
@@ -28,7 +29,7 @@ func (r DNSRule) MarshalJSON() ([]byte, error) {
default:
return nil, E.New("unknown rule type: " + r.Type)
}
return MarshallObjects((_DNSRule)(r), v)
return badjson.MarshallObjects((_DNSRule)(r), v)
}
func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
@@ -46,7 +47,7 @@ func (r *DNSRule) UnmarshalJSON(bytes []byte) error {
default:
return E.New("unknown rule type: " + r.Type)
}
err = UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
err = badjson.UnmarshallExcluded(bytes, (*_DNSRule)(r), v)
if err != nil {
return err
}
@@ -64,7 +65,7 @@ func (r DNSRule) IsValid() bool {
}
}
type DefaultDNSRule struct {
type RawDefaultDNSRule struct {
Inbound Listable[string] `json:"inbound,omitempty"`
IPVersion int `json:"ip_version,omitempty"`
QueryType Listable[DNSQueryType] `json:"query_type,omitempty"`
@@ -100,35 +101,58 @@ type DefaultDNSRule struct {
RuleSetIPCIDRMatchSource bool `json:"rule_set_ip_cidr_match_source,omitempty"`
RuleSetIPCIDRAcceptEmpty bool `json:"rule_set_ip_cidr_accept_empty,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
// Deprecated: renamed to rule_set_ip_cidr_match_source
Deprecated_RulesetIPCIDRMatchSource bool `json:"rule_set_ipcidr_match_source,omitempty"`
}
type DefaultDNSRule struct {
RawDefaultDNSRule
DNSRuleAction
}
func (r *DefaultDNSRule) MarshalJSON() ([]byte, error) {
return badjson.MarshallObjects(r.RawDefaultDNSRule, r.DNSRuleAction)
}
func (r *DefaultDNSRule) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &r.RawDefaultDNSRule)
if err != nil {
return err
}
return badjson.UnmarshallExcluded(data, &r.RawDefaultDNSRule, &r.DNSRuleAction)
}
func (r *DefaultDNSRule) IsValid() bool {
var defaultValue DefaultDNSRule
defaultValue.Invert = r.Invert
defaultValue.Server = r.Server
defaultValue.DisableCache = r.DisableCache
defaultValue.RewriteTTL = r.RewriteTTL
defaultValue.ClientSubnet = r.ClientSubnet
defaultValue.DNSRuleAction = r.DNSRuleAction
return !reflect.DeepEqual(r, defaultValue)
}
type LogicalDNSRule struct {
Mode string `json:"mode"`
Rules []DNSRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
Server string `json:"server,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *AddrPrefix `json:"client_subnet,omitempty"`
type _LogicalDNSRule struct {
Mode string `json:"mode"`
Rules []DNSRule `json:"rules,omitempty"`
Invert bool `json:"invert,omitempty"`
}
func (r LogicalDNSRule) IsValid() bool {
type LogicalDNSRule struct {
_LogicalDNSRule
DNSRuleAction
}
func (r *LogicalDNSRule) MarshalJSON() ([]byte, error) {
return badjson.MarshallObjects(r._LogicalDNSRule, r.DNSRuleAction)
}
func (r *LogicalDNSRule) UnmarshalJSON(data []byte) error {
err := json.Unmarshal(data, &r._LogicalDNSRule)
if err != nil {
return err
}
return badjson.UnmarshallExcluded(data, &r._LogicalDNSRule, &r.DNSRuleAction)
}
func (r *LogicalDNSRule) IsValid() bool {
return len(r.Rules) > 0 && common.All(r.Rules, DNSRule.IsValid)
}

View File

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

View File

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

View File

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

View File

@@ -81,8 +81,11 @@ func (a *AddrPrefix) UnmarshalJSON(content []byte) error {
return prefixErr
}
func (a AddrPrefix) Build() netip.Prefix {
return netip.Prefix(a)
func (a *AddrPrefix) Build() netip.Prefix {
if a == nil {
return netip.Prefix{}
}
return netip.Prefix(*a)
}
type NetworkList string
@@ -143,12 +146,29 @@ func (l *Listable[T]) UnmarshalJSON(content []byte) error {
type DomainStrategy dns.DomainStrategy
func (s DomainStrategy) String() string {
switch dns.DomainStrategy(s) {
case dns.DomainStrategyAsIS:
return ""
case dns.DomainStrategyPreferIPv4:
return "prefer_ipv4"
case dns.DomainStrategyPreferIPv6:
return "prefer_ipv6"
case dns.DomainStrategyUseIPv4:
return "ipv4_only"
case dns.DomainStrategyUseIPv6:
return "ipv6_only"
default:
panic(E.New("unknown domain strategy: ", s))
}
}
func (s DomainStrategy) MarshalJSON() ([]byte, error) {
var value string
switch dns.DomainStrategy(s) {
case dns.DomainStrategyAsIS:
value = ""
// value = "AsIS"
// value = "as_is"
case dns.DomainStrategyPreferIPv4:
value = "prefer_ipv4"
case dns.DomainStrategyPreferIPv6:

View File

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

View File

@@ -1,52 +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
}
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
}
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,60 +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 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,
}
}

Some files were not shown because too many files have changed in this diff Show More