mirror of
https://github.com/shtorm-7/sing-box-extended.git
synced 2026-07-06 16:39:17 +03:00
Compare commits
25 Commits
v1.11.0-al
...
renovate/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05a431d203 | ||
|
|
e68a9e623f | ||
|
|
a9bb07db4a | ||
|
|
a07219d16d | ||
|
|
6362b778c9 | ||
|
|
cb46b648d7 | ||
|
|
04613c27e2 | ||
|
|
795e1cf2f0 | ||
|
|
1e36c75336 | ||
|
|
5a2c7037fe | ||
|
|
852c07c050 | ||
|
|
c2ab497ff4 | ||
|
|
1e3c136440 | ||
|
|
7a2cd77798 | ||
|
|
1cdaea49b6 | ||
|
|
a50b0bc1a9 | ||
|
|
00e6210928 | ||
|
|
2dbeb63b4e | ||
|
|
744cc985ba | ||
|
|
bd122478fc | ||
|
|
a18179ad24 | ||
|
|
388c75a815 | ||
|
|
8d0d49c388 | ||
|
|
03f5d6e4f2 | ||
|
|
25065d766a |
11
Makefile
11
Makefile
@@ -155,16 +155,7 @@ upload_macos_dmg:
|
|||||||
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
cp SFM.dmg "SFM-${VERSION}-universal.dmg" && \
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dmg"
|
||||||
|
|
||||||
upload_macos_dsyms:
|
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg
|
||||||
pushd ../sing-box-for-apple/build/SFM.System.xcarchive && \
|
|
||||||
zip -r SFM.dSYMs.zip dSYMs && \
|
|
||||||
mv SFM.dSYMs.zip ../../../sing-box/dist/SFM && \
|
|
||||||
popd && \
|
|
||||||
cd dist/SFM && \
|
|
||||||
cp SFM.dSYMs.zip "SFM-${VERSION}-universal.dSYMs.zip" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFM-${VERSION}-universal.dSYMs.zip"
|
|
||||||
|
|
||||||
release_macos_standalone: build_macos_standalone build_macos_dmg upload_macos_dmg upload_macos_dsyms
|
|
||||||
|
|
||||||
build_tvos:
|
build_tvos:
|
||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
|
|||||||
104
adapter/conn_router.go
Normal file
104
adapter/conn_router.go
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -6,53 +6,27 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deprecated
|
|
||||||
type ConnectionHandler interface {
|
type ConnectionHandler interface {
|
||||||
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
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 {
|
type PacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, metadata InboundContext) error
|
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 {
|
type OOBPacketHandler interface {
|
||||||
NewPacket(ctx context.Context, conn N.PacketConn, buffer *buf.Buffer, oob []byte, metadata InboundContext) error
|
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 {
|
type PacketConnectionHandler interface {
|
||||||
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
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 {
|
type UpstreamHandlerAdapter interface {
|
||||||
N.TCPConnectionHandler
|
N.TCPConnectionHandler
|
||||||
N.UDPConnectionHandler
|
N.UDPConnectionHandler
|
||||||
E.Handler
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpstreamHandlerAdapterEx interface {
|
|
||||||
N.TCPConnectionHandlerEx
|
|
||||||
N.UDPConnectionHandlerEx
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,12 +2,13 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Inbound interface {
|
type Inbound interface {
|
||||||
@@ -16,19 +17,11 @@ type Inbound interface {
|
|||||||
Tag() string
|
Tag() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TCPInjectableInbound interface {
|
type InjectableInbound interface {
|
||||||
Inbound
|
Inbound
|
||||||
ConnectionHandlerEx
|
Network() []string
|
||||||
}
|
NewConnection(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
|
NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
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 {
|
type InboundContext struct {
|
||||||
@@ -50,17 +43,10 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
// Deprecated: implement in rule action
|
InboundDetour string
|
||||||
InboundDetour string
|
LastInbound string
|
||||||
LastInbound string
|
OriginDestination M.Socksaddr
|
||||||
OriginDestination M.Socksaddr
|
InboundOptions option.InboundOptions
|
||||||
// Deprecated
|
|
||||||
InboundOptions option.InboundOptions
|
|
||||||
UDPDisableDomainUnmapping bool
|
|
||||||
UDPConnect bool
|
|
||||||
|
|
||||||
DNSServer string
|
|
||||||
|
|
||||||
DestinationAddresses []netip.Addr
|
DestinationAddresses []netip.Addr
|
||||||
SourceGeoIPCode string
|
SourceGeoIPCode string
|
||||||
GeoIPCode string
|
GeoIPCode string
|
||||||
@@ -105,6 +91,15 @@ func ContextFrom(ctx context.Context) *InboundContext {
|
|||||||
return metadata.(*InboundContext)
|
return metadata.(*InboundContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AppendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
|
metadata := ContextFrom(ctx)
|
||||||
|
if metadata != nil {
|
||||||
|
return ctx, metadata
|
||||||
|
}
|
||||||
|
metadata = new(InboundContext)
|
||||||
|
return WithContext(ctx, metadata), metadata
|
||||||
|
}
|
||||||
|
|
||||||
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
func ExtendContext(ctx context.Context) (context.Context, *InboundContext) {
|
||||||
var newMetadata InboundContext
|
var newMetadata InboundContext
|
||||||
if metadata := ContextFrom(ctx); metadata != nil {
|
if metadata := ContextFrom(ctx); metadata != nil {
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,8 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,9 +15,6 @@ type Outbound interface {
|
|||||||
Network() []string
|
Network() []string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
N.Dialer
|
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,13 @@ package adapter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/geoip"
|
"github.com/sagernet/sing-box/common/geoip"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-dns"
|
"github.com/sagernet/sing-dns"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -34,8 +30,6 @@ type Router interface {
|
|||||||
FakeIPStore() FakeIPStore
|
FakeIPStore() FakeIPStore
|
||||||
|
|
||||||
ConnectionRouter
|
ConnectionRouter
|
||||||
PreMatch(metadata InboundContext) error
|
|
||||||
ConnectionRouterEx
|
|
||||||
|
|
||||||
GeoIPReader() *geoip.Reader
|
GeoIPReader() *geoip.Reader
|
||||||
LoadGeosite(code string) (Rule, error)
|
LoadGeosite(code string) (Rule, error)
|
||||||
@@ -72,18 +66,6 @@ type Router interface {
|
|||||||
ResetNetwork() error
|
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 {
|
func ContextWithRouter(ctx context.Context, router Router) context.Context {
|
||||||
return service.ContextWith(ctx, router)
|
return service.ContextWith(ctx, router)
|
||||||
}
|
}
|
||||||
@@ -92,9 +74,31 @@ func RouterFromContext(ctx context.Context) Router {
|
|||||||
return service.FromContext[Router](ctx)
|
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 {
|
type RuleSet interface {
|
||||||
Name() string
|
Name() string
|
||||||
StartContext(ctx context.Context, startContext *HTTPStartContext) error
|
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||||
PostStart() error
|
PostStart() error
|
||||||
Metadata() RuleSetMetadata
|
Metadata() RuleSetMetadata
|
||||||
ExtractIPSet() []*netipx.IPSet
|
ExtractIPSet() []*netipx.IPSet
|
||||||
@@ -114,42 +118,10 @@ type RuleSetMetadata struct {
|
|||||||
ContainsWIFIRule bool
|
ContainsWIFIRule bool
|
||||||
ContainsIPCIDRRule bool
|
ContainsIPCIDRRule bool
|
||||||
}
|
}
|
||||||
type HTTPStartContext struct {
|
|
||||||
access sync.Mutex
|
|
||||||
httpClientCache map[string]*http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTTPStartContext() *HTTPStartContext {
|
type RuleSetStartContext interface {
|
||||||
return &HTTPStartContext{
|
HTTPClient(detour string, dialer N.Dialer) *http.Client
|
||||||
httpClientCache: make(map[string]*http.Client),
|
Close()
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HTTPStartContext) HTTPClient(detour string, dialer N.Dialer) *http.Client {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
if httpClient, loaded := c.httpClientCache[detour]; loaded {
|
|
||||||
return httpClient
|
|
||||||
}
|
|
||||||
httpClient := &http.Client{
|
|
||||||
Transport: &http.Transport{
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
TLSHandshakeTimeout: C.TCPTimeout,
|
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
|
||||||
return dialer.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.httpClientCache[detour] = httpClient
|
|
||||||
return httpClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *HTTPStartContext) Close() {
|
|
||||||
c.access.Lock()
|
|
||||||
defer c.access.Unlock()
|
|
||||||
for _, client := range c.httpClientCache {
|
|
||||||
client.CloseIdleConnections()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type InterfaceUpdateListener interface {
|
type InterfaceUpdateListener interface {
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,165 +4,112 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
ConnectionHandlerFuncEx = func(ctx context.Context, conn net.Conn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
ConnectionHandlerFunc = func(ctx context.Context, conn net.Conn, metadata InboundContext) error
|
||||||
PacketConnectionHandlerFuncEx = func(ctx context.Context, conn N.PacketConn, metadata InboundContext, onClose N.CloseHandlerFunc)
|
PacketConnectionHandlerFunc = func(ctx context.Context, conn N.PacketConn, metadata InboundContext) error
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUpstreamHandlerEx(
|
func NewUpstreamHandler(
|
||||||
metadata InboundContext,
|
metadata InboundContext,
|
||||||
connectionHandler ConnectionHandlerFuncEx,
|
connectionHandler ConnectionHandlerFunc,
|
||||||
packetHandler PacketConnectionHandlerFuncEx,
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
) UpstreamHandlerAdapterEx {
|
errorHandler E.Handler,
|
||||||
return &myUpstreamHandlerWrapperEx{
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamHandlerWrapper{
|
||||||
metadata: metadata,
|
metadata: metadata,
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapterEx = (*myUpstreamHandlerWrapperEx)(nil)
|
var _ UpstreamHandlerAdapter = (*myUpstreamHandlerWrapper)(nil)
|
||||||
|
|
||||||
type myUpstreamHandlerWrapperEx struct {
|
type myUpstreamHandlerWrapper struct {
|
||||||
metadata InboundContext
|
metadata InboundContext
|
||||||
connectionHandler ConnectionHandlerFuncEx
|
connectionHandler ConnectionHandlerFunc
|
||||||
packetHandler PacketConnectionHandlerFuncEx
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.connectionHandler(ctx, conn, myMetadata, onClose)
|
return w.connectionHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
myMetadata := w.metadata
|
myMetadata := w.metadata
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.packetHandler(ctx, conn, myMetadata, onClose)
|
return w.packetHandler(ctx, conn, myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ UpstreamHandlerAdapterEx = (*myUpstreamContextHandlerWrapperEx)(nil)
|
func (w *myUpstreamHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
|
w.errorHandler.NewError(ctx, err)
|
||||||
type myUpstreamContextHandlerWrapperEx struct {
|
|
||||||
connectionHandler ConnectionHandlerFuncEx
|
|
||||||
packetHandler PacketConnectionHandlerFuncEx
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUpstreamContextHandlerEx(
|
func UpstreamMetadata(metadata InboundContext) M.Metadata {
|
||||||
connectionHandler ConnectionHandlerFuncEx,
|
return M.Metadata{
|
||||||
packetHandler PacketConnectionHandlerFuncEx,
|
Source: metadata.Source,
|
||||||
) UpstreamHandlerAdapterEx {
|
Destination: metadata.Destination,
|
||||||
return &myUpstreamContextHandlerWrapperEx{
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type myUpstreamContextHandlerWrapper struct {
|
||||||
|
connectionHandler ConnectionHandlerFunc
|
||||||
|
packetHandler PacketConnectionHandlerFunc
|
||||||
|
errorHandler E.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpstreamContextHandler(
|
||||||
|
connectionHandler ConnectionHandlerFunc,
|
||||||
|
packetHandler PacketConnectionHandlerFunc,
|
||||||
|
errorHandler E.Handler,
|
||||||
|
) UpstreamHandlerAdapter {
|
||||||
|
return &myUpstreamContextHandlerWrapper{
|
||||||
connectionHandler: connectionHandler,
|
connectionHandler: connectionHandler,
|
||||||
packetHandler: packetHandler,
|
packetHandler: packetHandler,
|
||||||
|
errorHandler: errorHandler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewConnectionEx(ctx context.Context, conn net.Conn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapper) NewConnection(ctx context.Context, conn net.Conn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.connectionHandler(ctx, conn, *myMetadata, onClose)
|
return w.connectionHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *myUpstreamContextHandlerWrapperEx) NewPacketConnectionEx(ctx context.Context, conn N.PacketConn, source M.Socksaddr, destination M.Socksaddr, onClose N.CloseHandlerFunc) {
|
func (w *myUpstreamContextHandlerWrapper) NewPacketConnection(ctx context.Context, conn N.PacketConn, metadata M.Metadata) error {
|
||||||
myMetadata := ContextFrom(ctx)
|
myMetadata := ContextFrom(ctx)
|
||||||
if source.IsValid() {
|
if metadata.Source.IsValid() {
|
||||||
myMetadata.Source = source
|
myMetadata.Source = metadata.Source
|
||||||
}
|
}
|
||||||
if destination.IsValid() {
|
if metadata.Destination.IsValid() {
|
||||||
myMetadata.Destination = destination
|
myMetadata.Destination = metadata.Destination
|
||||||
}
|
}
|
||||||
w.packetHandler(ctx, conn, *myMetadata, onClose)
|
return w.packetHandler(ctx, conn, *myMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouteHandlerEx(
|
func (w *myUpstreamContextHandlerWrapper) NewError(ctx context.Context, err error) {
|
||||||
metadata InboundContext,
|
w.errorHandler.NewError(ctx, err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +16,8 @@ type V2RayServerTransport interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type V2RayServerTransportHandler interface {
|
type V2RayServerTransportHandler interface {
|
||||||
N.TCPConnectionHandlerEx
|
N.TCPConnectionHandler
|
||||||
|
E.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
type V2RayClientTransport interface {
|
type V2RayClientTransport interface {
|
||||||
|
|||||||
91
box.go
91
box.go
@@ -14,9 +14,10 @@ import (
|
|||||||
"github.com/sagernet/sing-box/experimental"
|
"github.com/sagernet/sing-box/experimental"
|
||||||
"github.com/sagernet/sing-box/experimental/cachefile"
|
"github.com/sagernet/sing-box/experimental/cachefile"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/protocol/direct"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing-box/route"
|
"github.com/sagernet/sing-box/route"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -43,37 +44,16 @@ type Box struct {
|
|||||||
type Options struct {
|
type Options struct {
|
||||||
option.Options
|
option.Options
|
||||||
Context context.Context
|
Context context.Context
|
||||||
|
PlatformInterface platform.Interface
|
||||||
PlatformLogWriter log.PlatformWriter
|
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) {
|
func New(options Options) (*Box, error) {
|
||||||
createdAt := time.Now()
|
createdAt := time.Now()
|
||||||
ctx := options.Context
|
ctx := options.Context
|
||||||
if ctx == nil {
|
if ctx == nil {
|
||||||
ctx = context.Background()
|
ctx = context.Background()
|
||||||
}
|
}
|
||||||
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 = service.ContextWithDefaultRegistry(ctx)
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -90,9 +70,8 @@ func New(options Options) (*Box, error) {
|
|||||||
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" {
|
||||||
needV2RayAPI = true
|
needV2RayAPI = true
|
||||||
}
|
}
|
||||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
|
||||||
var defaultLogWriter io.Writer
|
var defaultLogWriter io.Writer
|
||||||
if platformInterface != nil {
|
if options.PlatformInterface != nil {
|
||||||
defaultLogWriter = io.Discard
|
defaultLogWriter = io.Discard
|
||||||
}
|
}
|
||||||
logFactory, err := log.New(log.Options{
|
logFactory, err := log.New(log.Options{
|
||||||
@@ -113,92 +92,64 @@ func New(options Options) (*Box, error) {
|
|||||||
common.PtrValueOrDefault(options.DNS),
|
common.PtrValueOrDefault(options.DNS),
|
||||||
common.PtrValueOrDefault(options.NTP),
|
common.PtrValueOrDefault(options.NTP),
|
||||||
options.Inbounds,
|
options.Inbounds,
|
||||||
|
options.PlatformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse route options")
|
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))
|
inbounds := make([]adapter.Inbound, 0, len(options.Inbounds))
|
||||||
//nolint:staticcheck
|
|
||||||
if len(options.LegacyOutbounds) > 0 {
|
|
||||||
for _, legacyOutbound := range options.LegacyOutbounds {
|
|
||||||
options.Outbounds = append(options.Outbounds, option.Outbound{
|
|
||||||
Type: legacyOutbound.Type,
|
|
||||||
Tag: legacyOutbound.Tag,
|
|
||||||
Options: common.Must1(legacyOutbound.RawOptions()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
outbounds := make([]adapter.Outbound, 0, len(options.Outbounds))
|
||||||
for i, inboundOptions := range options.Inbounds {
|
for i, inboundOptions := range options.Inbounds {
|
||||||
var currentInbound adapter.Inbound
|
var in adapter.Inbound
|
||||||
var tag string
|
var tag string
|
||||||
if inboundOptions.Tag != "" {
|
if inboundOptions.Tag != "" {
|
||||||
tag = inboundOptions.Tag
|
tag = inboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
currentInbound, err = inboundRegistry.CreateInbound(
|
in, err = inbound.New(
|
||||||
ctx,
|
ctx,
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
inboundOptions.Type,
|
inboundOptions,
|
||||||
inboundOptions.Options,
|
options.PlatformInterface,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse inbound[", i, "]")
|
return nil, E.Cause(err, "parse inbound[", i, "]")
|
||||||
}
|
}
|
||||||
inbounds = append(inbounds, currentInbound)
|
inbounds = append(inbounds, in)
|
||||||
}
|
}
|
||||||
for i, outboundOptions := range options.Outbounds {
|
for i, outboundOptions := range options.Outbounds {
|
||||||
var currentOutbound adapter.Outbound
|
var out adapter.Outbound
|
||||||
var tag string
|
var tag string
|
||||||
if outboundOptions.Tag != "" {
|
if outboundOptions.Tag != "" {
|
||||||
tag = outboundOptions.Tag
|
tag = outboundOptions.Tag
|
||||||
} else {
|
} else {
|
||||||
tag = F.ToString(i)
|
tag = F.ToString(i)
|
||||||
}
|
}
|
||||||
outboundCtx := ctx
|
out, err = outbound.New(
|
||||||
if tag != "" {
|
ctx,
|
||||||
// TODO: remove this
|
|
||||||
outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{
|
|
||||||
Outbound: tag,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
currentOutbound, err = outboundRegistry.CreateOutbound(
|
|
||||||
outboundCtx,
|
|
||||||
router,
|
router,
|
||||||
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")),
|
||||||
tag,
|
tag,
|
||||||
outboundOptions.Type,
|
outboundOptions)
|
||||||
outboundOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse outbound[", i, "]")
|
return nil, E.Cause(err, "parse outbound[", i, "]")
|
||||||
}
|
}
|
||||||
outbounds = append(outbounds, currentOutbound)
|
outbounds = append(outbounds, out)
|
||||||
}
|
}
|
||||||
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
err = router.Initialize(inbounds, outbounds, func() adapter.Outbound {
|
||||||
defaultOutbound, cErr := direct.NewOutbound(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{})
|
out, oErr := outbound.New(ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.Outbound{Type: "direct", Tag: "default"})
|
||||||
common.Must(cErr)
|
common.Must(oErr)
|
||||||
outbounds = append(outbounds, defaultOutbound)
|
outbounds = append(outbounds, out)
|
||||||
return defaultOutbound
|
return out
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if platformInterface != nil {
|
if options.PlatformInterface != nil {
|
||||||
err = platformInterface.Initialize(ctx, router)
|
err = options.PlatformInterface.Initialize(ctx, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "initialize platform interface")
|
return nil, E.Cause(err, "initialize platform interface")
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule clients/android updated: 45a1f5f0aa...2aa661d507
Submodule clients/apple updated: c7d9b49de7...c223f50ffb
@@ -58,7 +58,7 @@ func FindSDK() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func findNDK() bool {
|
func findNDK() bool {
|
||||||
const fixedVersion = "27.2.12479018"
|
const fixedVersion = "26.2.11394342"
|
||||||
const versionFile = "source.properties"
|
const versionFile = "source.properties"
|
||||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||||
androidNDKPath = fixedPath
|
androidNDKPath = fixedPath
|
||||||
@@ -86,7 +86,7 @@ func findNDK() bool {
|
|||||||
})
|
})
|
||||||
for _, versionName := range versionNames {
|
for _, versionName := range versionNames {
|
||||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||||
if rw.IsFile(filepath.Join(currentNDKPath, versionFile)) {
|
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
|
||||||
androidNDKPath = currentNDKPath
|
androidNDKPath = currentNDKPath
|
||||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -7,11 +7,8 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
_ "github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
"github.com/sagernet/sing/service/filemanager"
|
"github.com/sagernet/sing/service/filemanager"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -68,6 +65,4 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
|
||||||
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func check() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(globalCtx)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func format() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, optionsEntry := range optionsList {
|
for _, optionsEntry := range optionsList {
|
||||||
optionsEntry.options, err = badjson.Omitempty(globalCtx, optionsEntry.options)
|
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,19 +68,29 @@ func merge(outputPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mergePathResources(options *option.Options) error {
|
func mergePathResources(options *option.Options) error {
|
||||||
for _, inbound := range options.Inbounds {
|
for index, inbound := range options.Inbounds {
|
||||||
if tlsOptions, containsTLSOptions := inbound.Options.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
rawOptions, err := inbound.RawOptions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
|
||||||
}
|
}
|
||||||
|
options.Inbounds[index] = inbound
|
||||||
}
|
}
|
||||||
for _, outbound := range options.Outbounds {
|
for index, outbound := range options.Outbounds {
|
||||||
|
rawOptions, err := outbound.RawOptions()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
switch outbound.Type {
|
switch outbound.Type {
|
||||||
case C.TypeSSH:
|
case C.TypeSSH:
|
||||||
mergeSSHOutboundOptions(outbound.Options.(*option.SSHOutboundOptions))
|
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
|
||||||
}
|
}
|
||||||
if tlsOptions, containsTLSOptions := outbound.Options.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
|
||||||
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
|
||||||
}
|
}
|
||||||
|
options.Outbounds[index] = outbound
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -128,12 +138,13 @@ func mergeTLSOutboundOptions(options *option.OutboundTLSOptions) *option.Outboun
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func mergeSSHOutboundOptions(options *option.SSHOutboundOptions) {
|
func mergeSSHOutboundOptions(options option.SSHOutboundOptions) option.SSHOutboundOptions {
|
||||||
if options.PrivateKeyPath != "" {
|
if options.PrivateKeyPath != "" {
|
||||||
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
if content, err := os.ReadFile(os.ExpandEnv(options.PrivateKeyPath)); err == nil {
|
||||||
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
options.PrivateKey = trimStringArray(strings.Split(string(content), "\n"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func trimStringArray(array []string) []string {
|
func trimStringArray(array []string) []string {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
@@ -84,7 +84,7 @@ func ruleSetMatch(sourcePath string, domain string) error {
|
|||||||
}
|
}
|
||||||
for i, ruleOptions := range plainRuleSet.Rules {
|
for i, ruleOptions := range plainRuleSet.Rules {
|
||||||
var currentRule adapter.HeadlessRule
|
var currentRule adapter.HeadlessRule
|
||||||
currentRule, err = rule.NewHeadlessRule(nil, ruleOptions)
|
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ func readConfigAt(path string) (*OptionsEntry, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "read config at ", path)
|
return nil, E.Cause(err, "read config at ", path)
|
||||||
}
|
}
|
||||||
options, err := json.UnmarshalExtendedContext[option.Options](globalCtx, configContent)
|
options, err := json.UnmarshalExtended[option.Options](configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "decode config at ", path)
|
return nil, E.Cause(err, "decode config at ", path)
|
||||||
}
|
}
|
||||||
@@ -109,13 +109,13 @@ func readConfigAndMerge() (option.Options, error) {
|
|||||||
}
|
}
|
||||||
var mergedMessage json.RawMessage
|
var mergedMessage json.RawMessage
|
||||||
for _, options := range optionsList {
|
for _, options := range optionsList {
|
||||||
mergedMessage, err = badjson.MergeJSON(globalCtx, options.options.RawMessage, mergedMessage, false)
|
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var mergedOptions option.Options
|
var mergedOptions option.Options
|
||||||
err = mergedOptions.UnmarshalJSONContext(globalCtx, mergedMessage)
|
err = mergedOptions.UnmarshalJSON(mergedMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
return option.Options{}, E.Cause(err, "unmarshal merged config")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -26,9 +23,7 @@ func init() {
|
|||||||
func createPreStartedClient() (*box.Box, error) {
|
func createPreStartedClient() (*box.Box, error) {
|
||||||
options, err := readConfigAndMerge()
|
options, err := readConfigAndMerge()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !(errors.Is(err, os.ErrNotExist) && len(configDirectories) == 0 && len(configPaths) == 1) || configPaths[0] != "config.json" {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
instance, err := box.New(box.Options{Options: options})
|
instance, err := box.New(box.Options{Options: options})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@@ -26,7 +26,7 @@ example.arpa
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.org",
|
"example.org",
|
||||||
@@ -85,7 +85,7 @@ func TestHosts(t *testing.T) {
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"google.com",
|
"google.com",
|
||||||
@@ -115,7 +115,7 @@ www.example.org
|
|||||||
`))
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(nil, rules[0])
|
rule, err := route.NewHeadlessRule(nil, rules[0])
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
matchDomain := []string{
|
matchDomain := []string{
|
||||||
"example.com",
|
"example.com",
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
if options.ConnectTimeout != 0 {
|
if options.ConnectTimeout != 0 {
|
||||||
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
dialer.Timeout = time.Duration(options.ConnectTimeout)
|
||||||
} else {
|
} else {
|
||||||
dialer.Timeout = C.TCPConnectTimeout
|
dialer.Timeout = C.TCPTimeout
|
||||||
}
|
}
|
||||||
// TODO: Add an option to customize the keep alive period
|
// TODO: Add an option to customize the keep alive period
|
||||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||||
@@ -125,7 +125,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
|||||||
setMultiPathTCP(&dialer4)
|
setMultiPathTCP(&dialer4)
|
||||||
}
|
}
|
||||||
if options.IsWireGuardListener {
|
if options.IsWireGuardListener {
|
||||||
for _, controlFn := range WgControlFns {
|
for _, controlFn := range wgControlFns {
|
||||||
listener.Control = control.Append(listener.Control, controlFn)
|
listener.Control = control.Append(listener.Control, controlFn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ package dialer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/control"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type WireGuardListener interface {
|
type WireGuardListener interface {
|
||||||
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
ListenPacketCompat(network, address string) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var WgControlFns []control.Func
|
|
||||||
|
|||||||
11
common/dialer/wireguard_control.go
Normal file
11
common/dialer/wireguard_control.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
//go:build with_wireguard
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/wireguard-go/conn"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ WireGuardListener = (conn.Listener)(nil)
|
||||||
|
|
||||||
|
var wgControlFns = conn.ControlFns
|
||||||
9
common/dialer/wiregurad_stub.go
Normal file
9
common/dialer/wiregurad_stub.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
//go:build !with_wireguard
|
||||||
|
|
||||||
|
package dialer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/sagernet/sing/common/control"
|
||||||
|
)
|
||||||
|
|
||||||
|
var wgControlFns []control.Func
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
//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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
//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))
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
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() {
|
|
||||||
}
|
|
||||||
@@ -15,11 +15,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouterEx
|
router adapter.ConnectionRouter
|
||||||
service *mux.Service
|
service *mux.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouterEx, error) {
|
func NewRouterWithOptions(router adapter.ConnectionRouter, logger logger.ContextLogger, options option.InboundMultiplexOptions) (adapter.ConnectionRouter, error) {
|
||||||
if !options.Enabled {
|
if !options.Enabled {
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,6 @@ func NewRouterWithOptions(router adapter.ConnectionRouterEx, logger logger.Conte
|
|||||||
|
|
||||||
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
func (r *Router) RouteConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext) error {
|
||||||
if metadata.Destination == mux.Destination {
|
if metadata.Destination == mux.Destination {
|
||||||
// TODO: check if WithContext is necessary
|
|
||||||
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
return r.service.NewConnection(adapter.WithContext(ctx, &metadata), conn, adapter.UpstreamMetadata(metadata))
|
||||||
} else {
|
} else {
|
||||||
return r.router.RouteConnection(ctx, conn, metadata)
|
return r.router.RouteConnection(ctx, conn, metadata)
|
||||||
@@ -64,15 +63,3 @@ 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 {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
32
common/mux/v2ray_legacy.go
Normal file
32
common/mux/v2ray_legacy.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ type (
|
|||||||
PacketSniffer = func(ctx context.Context, metadata *adapter.InboundContext, packet []byte) error
|
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
|
// skip server first protocols
|
||||||
switch metadata.Destination.Port {
|
switch metadata.Destination.Port {
|
||||||
case 25, 465, 587:
|
case 25, 465, 587:
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ import (
|
|||||||
"github.com/sagernet/sing/common/uot"
|
"github.com/sagernet/sing/common/uot"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.ConnectionRouterEx = (*Router)(nil)
|
var _ adapter.ConnectionRouter = (*Router)(nil)
|
||||||
|
|
||||||
type Router struct {
|
type Router struct {
|
||||||
router adapter.ConnectionRouterEx
|
router adapter.ConnectionRouter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(router adapter.ConnectionRouterEx, logger logger.ContextLogger) *Router {
|
func NewRouter(router adapter.ConnectionRouter, logger logger.ContextLogger) *Router {
|
||||||
return &Router{router, logger}
|
return &Router{router, logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,36 +51,3 @@ 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 {
|
func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext) error {
|
||||||
return r.router.RoutePacketConnection(ctx, conn, metadata)
|
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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
@@ -114,7 +113,6 @@ func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err e
|
|||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
},
|
},
|
||||||
Timeout: C.TCPTimeout,
|
|
||||||
}
|
}
|
||||||
defer client.CloseIdleConnections()
|
defer client.CloseIdleConnections()
|
||||||
resp, err := client.Do(req.WithContext(ctx))
|
resp, err := client.Do(req.WithContext(ctx))
|
||||||
|
|||||||
@@ -22,18 +22,3 @@ const (
|
|||||||
RuleSetVersion1 = 1 + iota
|
RuleSetVersion1 = 1 + iota
|
||||||
RuleSetVersion2
|
RuleSetVersion2
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
RuleActionTypeRoute = "route"
|
|
||||||
RuleActionTypeRouteOptions = "route-options"
|
|
||||||
RuleActionTypeDirect = "direct"
|
|
||||||
RuleActionTypeReject = "reject"
|
|
||||||
RuleActionTypeHijackDNS = "hijack-dns"
|
|
||||||
RuleActionTypeSniff = "sniff"
|
|
||||||
RuleActionTypeResolve = "resolve"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
RuleActionRejectMethodDefault = "default"
|
|
||||||
RuleActionRejectMethodDrop = "drop"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import "time"
|
|||||||
const (
|
const (
|
||||||
TCPKeepAliveInitial = 10 * time.Minute
|
TCPKeepAliveInitial = 10 * time.Minute
|
||||||
TCPKeepAliveInterval = 75 * time.Second
|
TCPKeepAliveInterval = 75 * time.Second
|
||||||
TCPConnectTimeout = 5 * time.Second
|
TCPTimeout = 5 * time.Second
|
||||||
TCPTimeout = 15 * time.Second
|
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
QUICTimeout = 30 * time.Second
|
QUICTimeout = 30 * time.Second
|
||||||
|
|||||||
@@ -2,153 +2,10 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.11.0-alpha.7
|
#### 1.10.0-beta.12
|
||||||
|
|
||||||
* Introducing rule actions **1**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
New rule actions replace legacy inbound fields and special outbound fields,
|
|
||||||
and can be used for pre-matching **2**.
|
|
||||||
|
|
||||||
See [Rule](/configuration/route/rule/),
|
|
||||||
[Rule Action](/configuration/route/rule_action/),
|
|
||||||
[DNS Rule](/configuration/dns/rule/) and
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/).
|
|
||||||
|
|
||||||
For migration, see
|
|
||||||
[Migrate legacy special outbounds to rule actions](/migration/#migrate-legacy-special-outbounds-to-rule-actions),
|
|
||||||
[Migrate legacy inbound fields to rule actions](/migration/#migrate-legacy-inbound-fields-to-rule-actions)
|
|
||||||
and [Migrate legacy DNS route options to rule actions](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Similar to Surge's pre-matching.
|
|
||||||
|
|
||||||
Specifically, the new rule actions allow you to reject connections with
|
|
||||||
TCP RST (for TCP connections) and ICMP port unreachable (for UDP packets)
|
|
||||||
before connection established to improve tun's compatibility.
|
|
||||||
|
|
||||||
See [Rule Action](/configuration/route/rule_action/).
|
|
||||||
|
|
||||||
#### 1.11.0-alpha.6
|
|
||||||
|
|
||||||
* Update quic-go to v0.48.1
|
|
||||||
* Set gateway for tun correctly
|
|
||||||
* 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
|
* Fixes and improvements
|
||||||
|
|
||||||
### 1.10.0
|
|
||||||
|
|
||||||
Important changes since 1.9:
|
|
||||||
|
|
||||||
* Introducing auto-redirect **1**
|
|
||||||
* Add AdGuard DNS Filter support **2**
|
|
||||||
* TUN address fields are merged **3**
|
|
||||||
* Add custom options for `auto-route` and `auto-redirect` **4**
|
|
||||||
* Drop support for go1.18 and go1.19 **5**
|
|
||||||
* Add tailing comma support in JSON configuration
|
|
||||||
* Improve sniffers **6**
|
|
||||||
* Add new `inline` rule-set type **7**
|
|
||||||
* Add access control options for Clash API **8**
|
|
||||||
* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **9**
|
|
||||||
* Add auto reload support for local rule-set
|
|
||||||
* Update fsnotify usages **10**
|
|
||||||
* Add IP address support for `rule-set match` command
|
|
||||||
* Add `rule-set decompile` command
|
|
||||||
* Add `process_path_regex` rule item
|
|
||||||
* Update uTLS to v1.6.7 **11**
|
|
||||||
* Optimize memory usages of rule-sets **12**
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
The new auto-redirect feature allows TUN to automatically
|
|
||||||
configure connection redirection to improve proxy performance.
|
|
||||||
|
|
||||||
When auto-redirect is enabled, new route address set options will allow you to
|
|
||||||
automatically configure destination IP CIDR rules from a specified rule set to the firewall.
|
|
||||||
|
|
||||||
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
|
||||||
(for example, keep hardware offloading of direct traffics on the router).
|
|
||||||
|
|
||||||
See [TUN](/configuration/inbound/tun).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
The new feature allows you to use AdGuard DNS Filter lists in a sing-box without AdGuard Home.
|
|
||||||
|
|
||||||
See [AdGuard DNS Filter](/configuration/rule-set/adguard/).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
See [Migration](/migration/#tun-address-fields-are-merged).
|
|
||||||
|
|
||||||
**4**:
|
|
||||||
|
|
||||||
See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),
|
|
||||||
[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),
|
|
||||||
[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and
|
|
||||||
[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).
|
|
||||||
|
|
||||||
**5**:
|
|
||||||
|
|
||||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
|
||||||
|
|
||||||
**6**:
|
|
||||||
|
|
||||||
BitTorrent, DTLS, RDP, SSH sniffers are added.
|
|
||||||
|
|
||||||
Now the QUIC sniffer can correctly extract the server name from Chromium requests and
|
|
||||||
can identify common QUIC clients, including
|
|
||||||
Chromium, Safari, Firefox, quic-go (including uquic disguised as Chrome).
|
|
||||||
|
|
||||||
**7**:
|
|
||||||
|
|
||||||
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
|
||||||
allows you to write headless rules directly without creating a rule-set file.
|
|
||||||
|
|
||||||
**8**:
|
|
||||||
|
|
||||||
With the new access control options, not only can you allow Clash dashboards
|
|
||||||
to access the Clash API on your local network,
|
|
||||||
you can also manually limit the websites that can access the API instead of allowing everyone.
|
|
||||||
|
|
||||||
See [Clash API](/configuration/experimental/clash-api/).
|
|
||||||
|
|
||||||
**9**:
|
|
||||||
|
|
||||||
See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).
|
|
||||||
|
|
||||||
**10**:
|
|
||||||
|
|
||||||
sing-box now uses fsnotify correctly and will not cancel watching
|
|
||||||
if the target file is deleted or recreated via rename (e.g. `mv`).
|
|
||||||
|
|
||||||
This affects all path options that support reload, including
|
|
||||||
`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.
|
|
||||||
|
|
||||||
**11**:
|
|
||||||
|
|
||||||
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
|
||||||
see [utls](/configuration/shared/tls#utls).
|
|
||||||
|
|
||||||
**12**:
|
|
||||||
|
|
||||||
See [Source Format](/configuration/rule-set/source-format/#version).
|
|
||||||
|
|
||||||
### 1.9.7
|
### 1.9.7
|
||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
@@ -156,20 +13,18 @@ See [Source Format](/configuration/rule-set/source-format/#version).
|
|||||||
#### 1.10.0-beta.11
|
#### 1.10.0-beta.11
|
||||||
|
|
||||||
* Update uTLS to v1.6.7 **1**
|
* Update uTLS to v1.6.7 **1**
|
||||||
|
* Add ipk in release artifacts
|
||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
Some legacy chrome fingerprints have been removed and will fallback to chrome,
|
Some legacy chrome fingerprints have been removed and will fallback to chrome, see [utls](/configuration/shared/tls#utls).
|
||||||
see [utls](/configuration/shared/tls#utls).
|
|
||||||
|
|
||||||
#### 1.10.0-beta.10
|
#### 1.10.0-beta.10
|
||||||
|
|
||||||
* Add `process_path_regex` rule item
|
* Add `process_path_regex` rule item
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of
|
_The macOS standalone versions of sing-box (>=1.9.5/<1.10.0-beta.11) now silently fail and require manual granting of the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We will prompt users about this in feature versions._
|
||||||
the **Full Disk Access** permission to system extension to start, probably due to Apple's changed security policy. We
|
|
||||||
will prompt users about this in feature versions._
|
|
||||||
|
|
||||||
### 1.9.6
|
### 1.9.6
|
||||||
|
|
||||||
@@ -199,8 +54,7 @@ We are still working on getting all sing-box apps back on the App Store, which s
|
|||||||
|
|
||||||
* Fixes and improvements
|
* Fixes and improvements
|
||||||
|
|
||||||
_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be
|
_With the help of a netizen, we are in the process of getting sing-box apps back on the App Store, which should be completed within a month (TestFlight is already available)._
|
||||||
completed within a month (TestFlight is already available)._
|
|
||||||
|
|
||||||
#### 1.10.0-beta.7
|
#### 1.10.0-beta.7
|
||||||
|
|
||||||
@@ -305,9 +159,11 @@ See [Source Format](/configuration/rule-set/source-format/#version).
|
|||||||
|
|
||||||
**1**:
|
**1**:
|
||||||
|
|
||||||
The new [rule-set](/configuration/rule-set/) type inline (which also becomes the default type)
|
The new [rule-set] type inline (which also becomes the default type)
|
||||||
allows you to write headless rules directly without creating a rule-set file.
|
allows you to write headless rules directly without creating a rule-set file.
|
||||||
|
|
||||||
|
[rule-set]: /configuration/rule-set/
|
||||||
|
|
||||||
**2**:
|
**2**:
|
||||||
|
|
||||||
sing-box now uses fsnotify correctly and will not cancel watching
|
sing-box now uses fsnotify correctly and will not cancel watching
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -2,14 +2,6 @@
|
|||||||
icon: material/new-box
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
|
||||||
:material-alert: [server](#server)
|
|
||||||
:material-alert: [disable_cache](#disable_cache)
|
|
||||||
:material-alert: [rewrite_ttl](#rewrite_ttl)
|
|
||||||
:material-alert: [client_subnet](#client_subnet)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
@@ -22,7 +14,7 @@ icon: material/new-box
|
|||||||
:material-plus: [geoip](#geoip)
|
:material-plus: [geoip](#geoip)
|
||||||
:material-plus: [ip_cidr](#ip_cidr)
|
:material-plus: [ip_cidr](#ip_cidr)
|
||||||
:material-plus: [ip_is_private](#ip_is_private)
|
:material-plus: [ip_is_private](#ip_is_private)
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.8.0"
|
!!! quote "Changes in sing-box 1.8.0"
|
||||||
@@ -143,15 +135,19 @@ icon: material/new-box
|
|||||||
"outbound": [
|
"outbound": [
|
||||||
"direct"
|
"direct"
|
||||||
],
|
],
|
||||||
"action": "route",
|
"server": "local",
|
||||||
"server": "local"
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100,
|
||||||
|
"client_subnet": "127.0.0.1/24"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"action": "route",
|
"server": "local",
|
||||||
"server": "local"
|
"disable_cache": false,
|
||||||
|
"rewrite_ttl": 100,
|
||||||
|
"client_subnet": "127.0.0.1/24"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -222,7 +218,7 @@ Match domain using regular expression.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
Match geosite.
|
Match geosite.
|
||||||
|
|
||||||
@@ -230,7 +226,7 @@ Match geosite.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match source geoip.
|
Match source geoip.
|
||||||
|
|
||||||
@@ -358,35 +354,29 @@ Match outbound.
|
|||||||
|
|
||||||
`any` can be used as a value to match any outbound.
|
`any` can be used as a value to match any outbound.
|
||||||
|
|
||||||
#### action
|
#### server
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
See [DNS Rule Actions](../rule_action/) for details.
|
Tag of the target dns server.
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
#### disable_cache
|
#### disable_cache
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
Disable cache and save cache in this query.
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
#### rewrite_ttl
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
Rewrite TTL in DNS responses.
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
#### client_subnet
|
#### client_subnet
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
!!! question "Since sing-box 1.9.0"
|
||||||
|
|
||||||
Moved to [DNS Rule Action](../rule_action#route).
|
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||||
|
|
||||||
|
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||||
|
|
||||||
|
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
||||||
|
|
||||||
### Address Filter Fields
|
### Address Filter Fields
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ icon: material/new-box
|
|||||||
:material-plus: [geoip](#geoip)
|
:material-plus: [geoip](#geoip)
|
||||||
:material-plus: [ip_cidr](#ip_cidr)
|
:material-plus: [ip_cidr](#ip_cidr)
|
||||||
:material-plus: [ip_is_private](#ip_is_private)
|
:material-plus: [ip_is_private](#ip_is_private)
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||||
|
|
||||||
!!! quote "sing-box 1.8.0 中的更改"
|
!!! quote "sing-box 1.8.0 中的更改"
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# DNS Rule Action
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // default
|
|
||||||
"server": "",
|
|
||||||
|
|
||||||
// for compatibility
|
|
||||||
"disable_cache": false,
|
|
||||||
"rewrite_ttl": 0,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` inherits the classic rule behavior of routing DNS requests to the specified server.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Tag of target server.
|
|
||||||
|
|
||||||
#### disable_cache/rewrite_ttl/client_subnet
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Legacy route options is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
### route-options
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route-options",
|
|
||||||
"disable_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
Disable cache and save cache in this query.
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
Rewrite TTL in DNS responses.
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
|
||||||
|
|
||||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
|
||||||
|
|
||||||
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // default
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` reject DNS requests.
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: Reply with NXDOMAIN.
|
|
||||||
- `drop`: Drop the request.
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
|
||||||
|
|
||||||
Not available when `method` is set to drop.
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# DNS 规则动作
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // 默认
|
|
||||||
"server": "",
|
|
||||||
|
|
||||||
// 兼容性
|
|
||||||
"disable_cache": false,
|
|
||||||
"rewrite_ttl": 0,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` 继承了将 DNS 请求 路由到指定服务器的经典规则动作。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
目标 DNS 服务器的标签。
|
|
||||||
|
|
||||||
#### disable_cache/rewrite_ttl/client_subnet
|
|
||||||
|
|
||||||
!!! failure "自 sing-box 1.11.0 起"
|
|
||||||
|
|
||||||
旧的路由选项已弃用,且将在 sing-box 1.12.0 中移除,参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
### route-options
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route-options",
|
|
||||||
"disable_cache": false,
|
|
||||||
"rewrite_ttl": null,
|
|
||||||
"client_subnet": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
#### disable_cache
|
|
||||||
|
|
||||||
在此查询中禁用缓存。
|
|
||||||
|
|
||||||
#### rewrite_ttl
|
|
||||||
|
|
||||||
重写 DNS 回应中的 TTL。
|
|
||||||
|
|
||||||
#### client_subnet
|
|
||||||
|
|
||||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
|
||||||
|
|
||||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
|
||||||
|
|
||||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // default
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` 拒绝 DNS 请求。
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: 返回 NXDOMAIN。
|
|
||||||
- `drop`: 丢弃请求。
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
|
||||||
|
|
||||||
当 `method` 设为 `drop` 时不可用。
|
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client_subnet](#client_subnet)
|
:material-plus: [client_subnet](#client_subnet)
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "Since sing-box 1.8.0"
|
!!! question "Since sing-box 1.8.0"
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.9.0"
|
!!! quote "Changes in sing-box 1.9.0"
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
---
|
||||||
|
icon: material/new-box
|
||||||
|
---
|
||||||
|
|
||||||
!!! question "自 sing-box 1.8.0 起"
|
!!! question "自 sing-box 1.8.0 起"
|
||||||
|
|
||||||
!!! quote "sing-box 1.9.0 中的更改"
|
!!! quote "sing-box 1.9.0 中的更改"
|
||||||
|
|||||||
@@ -232,12 +232,12 @@ Automatically configure iptables/nftables to redirect connections.
|
|||||||
|
|
||||||
*In Android*:
|
*In Android*:
|
||||||
|
|
||||||
Only local IPv4 connections are forwarded. To share your VPN connection over hotspot or repeater,
|
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||||
|
|
||||||
*In Linux*:
|
*In Linux*:
|
||||||
|
|
||||||
`auto_route` with `auto_redirect` works as expected on routers **without intervention**.
|
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||||
|
|
||||||
自动配置 iptables/nftables 以重定向连接。
|
自动配置 iptables 以重定向 TCP 连接。
|
||||||
|
|
||||||
*在 Android 中*:
|
*在 Android 中*:
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
*在 Linux 中*:
|
*在 Linux 中*:
|
||||||
|
|
||||||
带有 `auto_redirect `的 `auto_route` 可以在路由器上按预期工作,**无需干预**。
|
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
||||||
|
|
||||||
#### auto_redirect_input_mark
|
#### auto_redirect_input_mark
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
---
|
`block` outbound closes all incoming requests.
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json F
|
```json
|
||||||
{
|
{
|
||||||
"type": "block",
|
"type": "block",
|
||||||
"tag": "block"
|
"tag": "block"
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`block` 出站关闭所有传入请求。
|
`block` 出站关闭所有传入请求。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
@@ -19,4 +11,4 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
### 字段
|
### 字段
|
||||||
|
|
||||||
无字段。
|
无字段。
|
||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Legacy special outbounds are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`dns` outbound is a internal DNS server.
|
`dns` outbound is a internal DNS server.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|||||||
@@ -1,11 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且将在 sing-box 1.13.0 中被移除, 参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
`dns` 出站是一个内部 DNS 服务器。
|
`dns` 出站是一个内部 DNS 服务器。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ icon: material/delete-clock
|
|||||||
|
|
||||||
!!! failure "已在 sing-box 1.8.0 废弃"
|
!!! failure "已在 sing-box 1.8.0 废弃"
|
||||||
|
|
||||||
Geosite 已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
|
||||||
:material-alert: [outbound](#outbound)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.10.0"
|
!!! quote "Changes in sing-box 1.10.0"
|
||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
@@ -134,7 +129,6 @@ icon: material/new-box
|
|||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -142,7 +136,6 @@ icon: material/new-box
|
|||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -216,7 +209,7 @@ Match domain using regular expression.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||||
|
|
||||||
Match geosite.
|
Match geosite.
|
||||||
|
|
||||||
@@ -224,7 +217,7 @@ Match geosite.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match source geoip.
|
Match source geoip.
|
||||||
|
|
||||||
@@ -232,7 +225,7 @@ Match source geoip.
|
|||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.8.0"
|
!!! failure "Deprecated in sing-box 1.8.0"
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
||||||
|
|
||||||
Match geoip.
|
Match geoip.
|
||||||
|
|
||||||
@@ -364,17 +357,11 @@ Make `ip_cidr` in rule-sets match the source IP.
|
|||||||
|
|
||||||
Invert match result.
|
Invert match result.
|
||||||
|
|
||||||
#### action
|
#### outbound
|
||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
See [Rule Actions](../rule_action/) for details.
|
Tag of the target outbound.
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Moved to [Rule Action](../rule_action#route).
|
|
||||||
|
|
||||||
### Logical Fields
|
### Logical Fields
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [action](#action)
|
|
||||||
:material-alert: [outbound](#outbound)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.10.0 中的更改"
|
!!! quote "sing-box 1.10.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [client](#client)
|
:material-plus: [client](#client)
|
||||||
@@ -132,7 +127,6 @@ icon: material/new-box
|
|||||||
"rule_set_ipcidr_match_source": false,
|
"rule_set_ipcidr_match_source": false,
|
||||||
"rule_set_ip_cidr_match_source": false,
|
"rule_set_ip_cidr_match_source": false,
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -140,7 +134,6 @@ icon: material/new-box
|
|||||||
"mode": "and",
|
"mode": "and",
|
||||||
"rules": [],
|
"rules": [],
|
||||||
"invert": false,
|
"invert": false,
|
||||||
"action": "route",
|
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -362,17 +355,11 @@ icon: material/new-box
|
|||||||
|
|
||||||
反选匹配结果。
|
反选匹配结果。
|
||||||
|
|
||||||
#### action
|
#### outbound
|
||||||
|
|
||||||
==必填==
|
==必填==
|
||||||
|
|
||||||
参阅 [规则行动](../rule_action/)。
|
目标出站的标签。
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
已移动到 [规则行动](../rule_action#route).
|
|
||||||
|
|
||||||
### 逻辑字段
|
### 逻辑字段
|
||||||
|
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# Rule Action
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.11.0"
|
|
||||||
|
|
||||||
## Final actions
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // default
|
|
||||||
"outbound": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` inherits the classic rule behavior of routing connection to the specified outbound.
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Tag of target outbound.
|
|
||||||
|
|
||||||
### route-options
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route-options",
|
|
||||||
"udp_disable_domain_unmapping": false,
|
|
||||||
"udp_connect": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route-options` set options for routing.
|
|
||||||
|
|
||||||
#### udp_disable_domain_unmapping
|
|
||||||
|
|
||||||
If enabled, for UDP proxy requests addressed to a domain,
|
|
||||||
the original packet address will be sent in the response instead of the mapped domain.
|
|
||||||
|
|
||||||
This option is used for compatibility with clients that
|
|
||||||
do not support receiving UDP packets with domain addresses, such as Surge.
|
|
||||||
|
|
||||||
#### udp_connect
|
|
||||||
|
|
||||||
If enabled, attempts to connect UDP connection to the destination instead of listen.
|
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // default
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` reject connections
|
|
||||||
|
|
||||||
The specified method is used for reject tun connections if `sniff` action has not been performed yet.
|
|
||||||
|
|
||||||
For non-tun connections and already established connections, will just be closed.
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: Reply with TCP RST for TCP connections, and ICMP port unreachable for UDP packets.
|
|
||||||
- `drop`: Drop packets.
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
If not enabled, `method` will be temporarily overwritten to `drop` after 50 triggers in 30s.
|
|
||||||
|
|
||||||
Not available when `method` is set to drop.
|
|
||||||
|
|
||||||
### hijack-dns
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`hijack-dns` hijack DNS requests to the sing-box DNS module.
|
|
||||||
|
|
||||||
## Non-final actions
|
|
||||||
|
|
||||||
### sniff
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "sniff",
|
|
||||||
"sniffer": [],
|
|
||||||
"timeout": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`sniff` performs protocol sniffing on connections.
|
|
||||||
|
|
||||||
For deprecated `inbound.sniff` options, it is considered to `sniff()` performed before routing.
|
|
||||||
|
|
||||||
#### sniffer
|
|
||||||
|
|
||||||
Enabled sniffers.
|
|
||||||
|
|
||||||
All sniffers enabled by default.
|
|
||||||
|
|
||||||
Available protocol values an be found on in [Protocol Sniff](../sniff/)
|
|
||||||
|
|
||||||
#### timeout
|
|
||||||
|
|
||||||
Timeout for sniffing.
|
|
||||||
|
|
||||||
`300ms` is used by default.
|
|
||||||
|
|
||||||
### resolve
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "",
|
|
||||||
"server": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`resolve` resolve request destination from domain to IP addresses.
|
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
DNS resolution strategy, available values are: `prefer_ipv4`, `prefer_ipv6`, `ipv4_only`, `ipv6_only`.
|
|
||||||
|
|
||||||
`dns.strategy` will be used by default.
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
Specifies DNS server tag to use instead of selecting through DNS routing.
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
# 规则动作
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.11.0 起"
|
|
||||||
|
|
||||||
## 最终动作
|
|
||||||
|
|
||||||
### route
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route", // 默认
|
|
||||||
"outbound": "",
|
|
||||||
"udp_disable_domain_unmapping": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`route` 继承了将连接路由到指定出站的经典规则动作。
|
|
||||||
|
|
||||||
#### outbound
|
|
||||||
|
|
||||||
==必填==
|
|
||||||
|
|
||||||
目标出站的标签。
|
|
||||||
|
|
||||||
### route-options
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "route-options",
|
|
||||||
"udp_disable_domain_unmapping": false,
|
|
||||||
"udp_connect": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### udp_disable_domain_unmapping
|
|
||||||
|
|
||||||
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
|
||||||
|
|
||||||
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
|
||||||
|
|
||||||
#### udp_connect
|
|
||||||
|
|
||||||
如果启用,将尝试将 UDP 连接 connect 到目标而不是 listen。
|
|
||||||
|
|
||||||
### reject
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "reject",
|
|
||||||
"method": "default", // 默认
|
|
||||||
"no_drop": false
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`reject` 拒绝连接。
|
|
||||||
|
|
||||||
如果尚未执行 `sniff` 操作,则将使用指定方法拒绝 tun 连接。
|
|
||||||
|
|
||||||
对于非 tun 连接和已建立的连接,将直接关闭。
|
|
||||||
|
|
||||||
#### method
|
|
||||||
|
|
||||||
- `default`: 对于 TCP 连接回复 RST,对于 UDP 包回复 ICMP 端口不可达。
|
|
||||||
- `drop`: 丢弃数据包。
|
|
||||||
|
|
||||||
#### no_drop
|
|
||||||
|
|
||||||
如果未启用,则 30 秒内触发 50 次后,`method` 将被暂时覆盖为 `drop`。
|
|
||||||
|
|
||||||
当 `method` 设为 `drop` 时不可用。
|
|
||||||
|
|
||||||
### hijack-dns
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "hijack-dns"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`hijack-dns` 劫持 DNS 请求至 sing-box DNS 模块。
|
|
||||||
|
|
||||||
## 非最终动作
|
|
||||||
|
|
||||||
### sniff
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "sniff",
|
|
||||||
"sniffer": [],
|
|
||||||
"timeout": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`sniff` 对连接执行协议嗅探。
|
|
||||||
|
|
||||||
对于已弃用的 `inbound.sniff` 选项,被视为在路由之前执行的 `sniff`。
|
|
||||||
|
|
||||||
#### sniffer
|
|
||||||
|
|
||||||
启用的探测器。
|
|
||||||
|
|
||||||
默认启用所有探测器。
|
|
||||||
|
|
||||||
可用的协议值可以在 [协议嗅探](../sniff/) 中找到。
|
|
||||||
|
|
||||||
#### timeout
|
|
||||||
|
|
||||||
探测超时时间。
|
|
||||||
|
|
||||||
默认使用 300ms。
|
|
||||||
|
|
||||||
### resolve
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "",
|
|
||||||
"server": ""
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`resolve` 将请求的目标从域名解析为 IP 地址。
|
|
||||||
|
|
||||||
#### strategy
|
|
||||||
|
|
||||||
DNS 解析策略,可用值有:`prefer_ipv4`、`prefer_ipv6`、`ipv4_only`、`ipv6_only`。
|
|
||||||
|
|
||||||
默认使用 `dns.strategy`。
|
|
||||||
|
|
||||||
#### server
|
|
||||||
|
|
||||||
指定要使用的 DNS 服务器的标签,而不是通过 DNS 路由进行选择。
|
|
||||||
@@ -1,15 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
|
||||||
|
|
||||||
:material-delete-clock: [sniff](#sniff)
|
|
||||||
:material-delete-clock: [sniff_override_destination](#sniff_override_destination)
|
|
||||||
:material-delete-clock: [sniff_timeout](#sniff_timeout)
|
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
|
||||||
:material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping)
|
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -80,40 +68,24 @@ Requires target inbound support, see [Injectable](/configuration/inbound/#fields
|
|||||||
|
|
||||||
#### sniff
|
#### sniff
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
Enable sniffing.
|
Enable sniffing.
|
||||||
|
|
||||||
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
See [Protocol Sniff](/configuration/route/sniff/) for details.
|
||||||
|
|
||||||
#### sniff_override_destination
|
#### sniff_override_destination
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
Override the connection destination address with the sniffed domain.
|
Override the connection destination address with the sniffed domain.
|
||||||
|
|
||||||
If the domain name is invalid (like tor), this will not work.
|
If the domain name is invalid (like tor), this will not work.
|
||||||
|
|
||||||
#### sniff_timeout
|
#### sniff_timeout
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
Timeout for sniffing.
|
Timeout for sniffing.
|
||||||
|
|
||||||
`300ms` is used by default.
|
300ms is used by default.
|
||||||
|
|
||||||
#### domain_strategy
|
#### domain_strategy
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||||
|
|
||||||
If set, the requested domain name will be resolved to IP before routing.
|
If set, the requested domain name will be resolved to IP before routing.
|
||||||
@@ -122,10 +94,6 @@ If `sniff_override_destination` is in effect, its value will be taken as a fallb
|
|||||||
|
|
||||||
#### udp_disable_domain_unmapping
|
#### udp_disable_domain_unmapping
|
||||||
|
|
||||||
!!! failure "Deprecated in sing-box 1.11.0"
|
|
||||||
|
|
||||||
Inbound fields are deprecated and will be removed in sing-box 1.13.0, check [Migration](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
If enabled, for UDP proxy requests addressed to a domain,
|
If enabled, for UDP proxy requests addressed to a domain,
|
||||||
the original packet address will be sent in the response instead of the mapped domain.
|
the original packet address will be sent in the response instead of the mapped domain.
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,3 @@
|
|||||||
---
|
|
||||||
icon: material/delete-clock
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
|
||||||
|
|
||||||
:material-delete-clock: [sniff](#sniff)
|
|
||||||
:material-delete-clock: [sniff_override_destination](#sniff_override_destination)
|
|
||||||
:material-delete-clock: [sniff_timeout](#sniff_timeout)
|
|
||||||
:material-delete-clock: [domain_strategy](#domain_strategy)
|
|
||||||
:material-delete-clock: [udp_disable_domain_unmapping](#udp_disable_domain_unmapping)
|
|
||||||
|
|
||||||
### 结构
|
### 结构
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -81,40 +69,24 @@ UDP NAT 过期时间,以秒为单位。
|
|||||||
|
|
||||||
#### sniff
|
#### sniff
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
启用协议探测。
|
启用协议探测。
|
||||||
|
|
||||||
参阅 [协议探测](/zh/configuration/route/sniff/)
|
参阅 [协议探测](/zh/configuration/route/sniff/)
|
||||||
|
|
||||||
#### sniff_override_destination
|
#### sniff_override_destination
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除。
|
|
||||||
|
|
||||||
用探测出的域名覆盖连接目标地址。
|
用探测出的域名覆盖连接目标地址。
|
||||||
|
|
||||||
如果域名无效(如 Tor),将不生效。
|
如果域名无效(如 Tor),将不生效。
|
||||||
|
|
||||||
#### sniff_timeout
|
#### sniff_timeout
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
探测超时时间。
|
探测超时时间。
|
||||||
|
|
||||||
默认使用 300ms。
|
默认使用 300ms。
|
||||||
|
|
||||||
#### domain_strategy
|
#### domain_strategy
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
可选值: `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`。
|
||||||
|
|
||||||
如果设置,请求的域名将在路由之前解析为 IP。
|
如果设置,请求的域名将在路由之前解析为 IP。
|
||||||
@@ -123,10 +95,6 @@ UDP NAT 过期时间,以秒为单位。
|
|||||||
|
|
||||||
#### udp_disable_domain_unmapping
|
#### udp_disable_domain_unmapping
|
||||||
|
|
||||||
!!! failure "已在 sing-box 1.11.0 废弃"
|
|
||||||
|
|
||||||
入站字段已废弃且将在 sing-box 1.12.0 中被移除,参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
如果启用,对于地址为域的 UDP 代理请求,将在响应中发送原始包地址而不是映射的域。
|
||||||
|
|
||||||
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
此选项用于兼容不支持接收带有域地址的 UDP 包的客户端,如 Surge。
|
||||||
|
|||||||
@@ -4,32 +4,6 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# Deprecated Feature List
|
# Deprecated Feature List
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
#### Legacy special outbounds
|
|
||||||
|
|
||||||
Legacy special outbounds (`block` / `dns`) are deprecated
|
|
||||||
and can be replaced by rule actions,
|
|
||||||
check [Migration](../migration/#migrate-legacy-special-outbounds-to-rule-actions).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
#### Legacy inbound fields
|
|
||||||
|
|
||||||
Legacy inbound fields (`inbound.<sniff/domain_strategy/...>` are deprecated
|
|
||||||
and can be replaced by rule actions,
|
|
||||||
check [Migration](../migration/#migrate-legacy-inbound-fields-to-rule-actions).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.13.0.
|
|
||||||
|
|
||||||
#### Legacy DNS route options
|
|
||||||
|
|
||||||
Legacy DNS route options (`disable_cache`, `rewrite_ttl`, `client_subnet`) are deprecated
|
|
||||||
and can be replaced by rule actions,
|
|
||||||
check [Migration](../migration/#migrate-legacy-dns-route-options-to-rule-actions).
|
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.12.0.
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
#### TUN address fields are merged
|
#### TUN address fields are merged
|
||||||
@@ -38,12 +12,7 @@ Old fields will be removed in sing-box 1.12.0.
|
|||||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
Old fields will be removed in sing-box 1.12.0.
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
#### Match source rule items are renamed
|
|
||||||
|
|
||||||
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
|
||||||
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
|
||||||
|
|
||||||
#### Drop support for go1.18 and go1.19
|
#### Drop support for go1.18 and go1.19
|
||||||
|
|
||||||
@@ -58,7 +27,7 @@ check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-o
|
|||||||
|
|
||||||
#### GeoIP
|
#### GeoIP
|
||||||
|
|
||||||
GeoIP is deprecated and will be removed in sing-box 1.12.0.
|
GeoIP is deprecated and may be removed in the future.
|
||||||
|
|
||||||
The maxmind GeoIP National Database, as an IP classification database,
|
The maxmind GeoIP National Database, as an IP classification database,
|
||||||
is not entirely suitable for traffic bypassing,
|
is not entirely suitable for traffic bypassing,
|
||||||
@@ -69,7 +38,7 @@ check [Migration](/migration/#migrate-geoip-to-rule-sets).
|
|||||||
|
|
||||||
#### Geosite
|
#### Geosite
|
||||||
|
|
||||||
Geosite is deprecated and will be removed in sing-box 1.12.0.
|
Geosite is deprecated and may be removed in the future.
|
||||||
|
|
||||||
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
|
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
|
||||||
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
|
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
|
||||||
|
|||||||
@@ -4,43 +4,15 @@ icon: material/delete-alert
|
|||||||
|
|
||||||
# 废弃功能列表
|
# 废弃功能列表
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
#### 旧的特殊出站
|
|
||||||
|
|
||||||
旧的特殊出站(`block` / `dns`)已废弃且可以通过规则动作替代,
|
|
||||||
参阅 [迁移指南](/migration/#migrate-legacy-special-outbounds-to-rule-actions)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.13.0 中被移除。
|
|
||||||
|
|
||||||
#### 旧的入站字段
|
|
||||||
|
|
||||||
旧的入站字段(`inbound.<sniff/domain_strategy/...>`)已废弃且可以通过规则动作替代,
|
|
||||||
参阅 [迁移指南](/migration/#migrate-legacy-inbound-fields-to-rule-actions)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.13.0 中被移除。
|
|
||||||
|
|
||||||
#### 旧的 DNS 路由参数
|
|
||||||
|
|
||||||
旧的 DNS 路由参数(`disable_cache`、`rewrite_ttl`、`client_subnet`)已废弃且可以通过规则动作替代,
|
|
||||||
参阅 [迁移指南](/migration/#migrate-legacy-dns-route-options-to-rule-actions)。
|
|
||||||
|
|
||||||
旧字段将在 sing-box 1.12.0 中被移除。
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
#### Match source 规则项已重命名
|
|
||||||
|
|
||||||
`rule_set_ipcidr_match_source` 路由和 DNS 规则项已被重命名为
|
|
||||||
`rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 中被移除。
|
|
||||||
|
|
||||||
#### TUN 地址字段已合并
|
#### TUN 地址字段已合并
|
||||||
|
|
||||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||||
|
|
||||||
旧字段将在 sing-box 1.11.0 中被移除。
|
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||||
|
|
||||||
#### 移除对 go1.18 和 go1.19 的支持
|
#### 移除对 go1.18 和 go1.19 的支持
|
||||||
|
|
||||||
@@ -55,7 +27,7 @@ Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `
|
|||||||
|
|
||||||
#### GeoIP
|
#### GeoIP
|
||||||
|
|
||||||
GeoIP 已废弃且将在 sing-box 1.12.0 中被移除。
|
GeoIP 已废弃且可能在不久的将来移除。
|
||||||
|
|
||||||
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
|
||||||
且现有的实现均存在内存使用大与管理困难的问题。
|
且现有的实现均存在内存使用大与管理困难的问题。
|
||||||
@@ -65,7 +37,7 @@ sing-box 1.8.0 引入了[规则集](/configuration/rule-set/),
|
|||||||
|
|
||||||
#### Geosite
|
#### Geosite
|
||||||
|
|
||||||
Geosite 已废弃且将在 sing-box 1.12.0 中被移除。
|
Geosite 已废弃且可能在不久的将来移除。
|
||||||
|
|
||||||
Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
|
Geosite,即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
|
||||||
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
|
||||||
|
|||||||
@@ -2,212 +2,6 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
### Migrate legacy special outbounds to rule actions
|
|
||||||
|
|
||||||
Legacy special outbounds are deprecated and can be replaced by rule actions.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[Rule Action](/configuration/route/rule_action/) /
|
|
||||||
[Block](/configuration/outbound/block/) /
|
|
||||||
[DNS](/configuration/outbound/dns)
|
|
||||||
|
|
||||||
=== "Block"
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"outbound": "block"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"action": "reject"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "DNS"
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"sniff": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "dns",
|
|
||||||
"type": "dns"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"action": "dns-hijack"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrate legacy inbound fields to rule actions
|
|
||||||
|
|
||||||
Inbound fields are deprecated and can be replaced by rule actions.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[Listen Fields](/configuration/inbound/listen/) /
|
|
||||||
[Rule](/configuration/route/rule/) /
|
|
||||||
[Rule Action](/configuration/route/rule_action/) /
|
|
||||||
[DNS Rule](/configuration/dns/rule/) /
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"sniff": true,
|
|
||||||
"sniff_timeout": "1s",
|
|
||||||
"domain_strategy": "prefer_ipv4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"tag": "in"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "prefer_ipv4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "sniff",
|
|
||||||
"timeout": "1s"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migrate legacy DNS route options to rule actions
|
|
||||||
|
|
||||||
Legacy DNS route options are deprecated and can be replaced by rule actions.
|
|
||||||
|
|
||||||
!!! info "References"
|
|
||||||
|
|
||||||
[DNS Rule](/configuration/dns/rule/) /
|
|
||||||
[DNS Rule Action](/configuration/dns/rule_action/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: Deprecated"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"server": "local",
|
|
||||||
"disable_cache": true,
|
|
||||||
"rewrite_ttl": 600,
|
|
||||||
"client_subnet": "1.1.1.1/24"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"action": "route-options",
|
|
||||||
"disable_cache": true,
|
|
||||||
"rewrite_ttl": 600,
|
|
||||||
"client_subnet": "1.1.1.1/24"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"server": "local"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
### TUN address fields are merged
|
### TUN address fields are merged
|
||||||
@@ -216,6 +10,8 @@ Legacy DNS route options are deprecated and can be replaced by rule actions.
|
|||||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||||
|
|
||||||
|
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||||
|
|
||||||
!!! info "References"
|
!!! info "References"
|
||||||
|
|
||||||
[TUN](/configuration/inbound/tun/)
|
[TUN](/configuration/inbound/tun/)
|
||||||
|
|||||||
@@ -2,212 +2,6 @@
|
|||||||
icon: material/arrange-bring-forward
|
icon: material/arrange-bring-forward
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1.11.0
|
|
||||||
|
|
||||||
### 迁移旧的特殊出站到规则动作
|
|
||||||
|
|
||||||
旧的特殊出站已被弃用,且可以被规则动作替代。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[规则动作](/zh/configuration/route/rule_action/) /
|
|
||||||
[Block](/zh/configuration/outbound/block/) /
|
|
||||||
[DNS](/zh/configuration/outbound/dns)
|
|
||||||
|
|
||||||
=== "Block"
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"type": "block",
|
|
||||||
"tag": "block"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"outbound": "block"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"action": "reject"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "DNS"
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbound": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"sniff": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"outbounds": [
|
|
||||||
{
|
|
||||||
"tag": "dns",
|
|
||||||
"type": "dns"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"outbound": "dns"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"protocol": "dns",
|
|
||||||
"action": "dns-hijack"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 迁移旧的入站字段到规则动作
|
|
||||||
|
|
||||||
入站选项已被弃用,且可以被规则动作替代。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[监听字段](/zh/configuration/shared/listen/) /
|
|
||||||
[规则](/zh/configuration/route/rule/) /
|
|
||||||
[规则动作](/zh/configuration/route/rule_action/) /
|
|
||||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
|
||||||
[DNS 规则动作](/zh/configuration/dns/rule_action/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"sniff": true,
|
|
||||||
"sniff_timeout": "1s",
|
|
||||||
"domain_strategy": "prefer_ipv4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: New"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"inbounds": [
|
|
||||||
{
|
|
||||||
"type": "mixed",
|
|
||||||
"tag": "in"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "resolve",
|
|
||||||
"strategy": "prefer_ipv4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"inbound": "in",
|
|
||||||
"action": "sniff",
|
|
||||||
"timeout": "1s"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 迁移旧的 DNS 路由选项到规则动作
|
|
||||||
|
|
||||||
旧的 DNS 路由选项已被弃用,且可以被规则动作替代。
|
|
||||||
|
|
||||||
!!! info "参考"
|
|
||||||
|
|
||||||
[DNS 规则](/zh/configuration/dns/rule/) /
|
|
||||||
[DNS 规则动作](/zh/configuration/dns/rule_action/)
|
|
||||||
|
|
||||||
=== ":material-card-remove: 弃用的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"server": "local",
|
|
||||||
"disable_cache": true,
|
|
||||||
"rewrite_ttl": 600,
|
|
||||||
"client_subnet": "1.1.1.1/24"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== ":material-card-multiple: 新的"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"action": "route-options",
|
|
||||||
"disable_cache": true,
|
|
||||||
"rewrite_ttl": 600,
|
|
||||||
"client_subnet": "1.1.1.1/24"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
...,
|
|
||||||
|
|
||||||
"server": "local"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 1.10.0
|
## 1.10.0
|
||||||
|
|
||||||
### TUN 地址字段已合并
|
### TUN 地址字段已合并
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -59,7 +59,7 @@ func getGroup(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
|||||||
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||||
outboundGroup, ok := proxy.(adapter.OutboundGroup)
|
group, ok := proxy.(adapter.OutboundGroup)
|
||||||
if !ok {
|
if !ok {
|
||||||
render.Status(r, http.StatusNotFound)
|
render.Status(r, http.StatusNotFound)
|
||||||
render.JSON(w, r, ErrNotFound)
|
render.JSON(w, r, ErrNotFound)
|
||||||
@@ -82,10 +82,10 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var result map[string]uint16
|
var result map[string]uint16
|
||||||
if urlTestGroup, isURLTestGroup := outboundGroup.(adapter.URLTestGroup); isURLTestGroup {
|
if urlTestGroup, isURLTestGroup := group.(adapter.URLTestGroup); isURLTestGroup {
|
||||||
result, err = urlTestGroup.URLTest(ctx)
|
result, err = urlTestGroup.URLTest(ctx)
|
||||||
} else {
|
} else {
|
||||||
outbounds := common.FilterNotNil(common.Map(outboundGroup.All(), func(it string) adapter.Outbound {
|
outbounds := common.FilterNotNil(common.Map(group.All(), func(it string) adapter.Outbound {
|
||||||
itOutbound, _ := server.router.Outbound(it)
|
itOutbound, _ := server.router.Outbound(it)
|
||||||
return itOutbound
|
return itOutbound
|
||||||
}))
|
}))
|
||||||
@@ -95,7 +95,7 @@ func getGroupDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
var resultAccess sync.Mutex
|
var resultAccess sync.Mutex
|
||||||
for _, detour := range outbounds {
|
for _, detour := range outbounds {
|
||||||
tag := detour.Tag()
|
tag := detour.Tag()
|
||||||
realTag := group.RealTag(detour)
|
realTag := outbound.RealTag(detour)
|
||||||
if checked[realTag] {
|
if checked[realTag] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
F "github.com/sagernet/sing/common/format"
|
F "github.com/sagernet/sing/common/format"
|
||||||
"github.com/sagernet/sing/common/json/badjson"
|
"github.com/sagernet/sing/common/json/badjson"
|
||||||
@@ -168,7 +168,7 @@ func updateProxy(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
proxy := r.Context().Value(CtxKeyProxy).(adapter.Outbound)
|
||||||
selector, ok := proxy.(*group.Selector)
|
selector, ok := proxy.(*outbound.Selector)
|
||||||
if !ok {
|
if !ok {
|
||||||
render.Status(r, http.StatusBadRequest)
|
render.Status(r, http.StatusBadRequest)
|
||||||
render.JSON(w, r, newError("Must be a Selector"))
|
render.JSON(w, r, newError("Must be a Selector"))
|
||||||
@@ -204,7 +204,7 @@ func getProxyDelay(server *Server) func(w http.ResponseWriter, r *http.Request)
|
|||||||
|
|
||||||
delay, err := urltest.URLTest(ctx, url, proxy)
|
delay, err := urltest.URLTest(ctx, url, proxy)
|
||||||
defer func() {
|
defer func() {
|
||||||
realTag := group.RealTag(proxy)
|
realTag := outbound.RealTag(proxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
server.urlTestHistory.DeleteURLTestHistory(realTag)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ func getRules(router adapter.Router) func(w http.ResponseWriter, r *http.Request
|
|||||||
rules = append(rules, Rule{
|
rules = append(rules, Rule{
|
||||||
Type: rule.Type(),
|
Type: rule.Type(),
|
||||||
Payload: rule.String(),
|
Payload: rule.String(),
|
||||||
Proxy: rule.Action().String(),
|
Proxy: rule.Outbound(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
render.JSON(w, r, render.M{
|
render.JSON(w, r, render.M{
|
||||||
"rules": rules,
|
"rules": rules,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
@@ -60,7 +60,7 @@ func (s *Server) downloadExternalUI() error {
|
|||||||
httpClient := &http.Client{
|
httpClient := &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
ForceAttemptHTTP2: true,
|
ForceAttemptHTTP2: true,
|
||||||
TLSHandshakeTimeout: C.TCPTimeout,
|
TLSHandshakeTimeout: 5 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
return detour.DialContext(ctx, network, M.ParseSocksaddr(addr))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
R "github.com/sagernet/sing-box/route/rule"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/atomic"
|
"github.com/sagernet/sing/common/atomic"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
@@ -61,7 +60,7 @@ func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
|||||||
}
|
}
|
||||||
var rule string
|
var rule string
|
||||||
if t.Rule != nil {
|
if t.Rule != nil {
|
||||||
rule = F.ToString(t.Rule, " => ", t.Rule.Action())
|
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
||||||
} else {
|
} else {
|
||||||
rule = "final"
|
rule = "final"
|
||||||
}
|
}
|
||||||
@@ -132,21 +131,19 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundCont
|
|||||||
outbound string
|
outbound string
|
||||||
outboundType string
|
outboundType string
|
||||||
)
|
)
|
||||||
var action adapter.RuleAction
|
if rule == nil {
|
||||||
if rule != nil {
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||||
action = rule.Action()
|
next = defaultOutbound.Tag()
|
||||||
}
|
}
|
||||||
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
} else {
|
||||||
next = routeAction.Outbound
|
next = rule.Outbound()
|
||||||
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
|
||||||
next = defaultOutbound.Tag()
|
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
chain = append(chain, next)
|
||||||
detour, loaded := router.Outbound(next)
|
detour, loaded := router.Outbound(next)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
chain = append(chain, next)
|
|
||||||
outbound = detour.Tag()
|
outbound = detour.Tag()
|
||||||
outboundType = detour.Type()
|
outboundType = detour.Type()
|
||||||
group, isGroup := detour.(adapter.OutboundGroup)
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
@@ -221,21 +218,19 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.Inbound
|
|||||||
outbound string
|
outbound string
|
||||||
outboundType string
|
outboundType string
|
||||||
)
|
)
|
||||||
var action adapter.RuleAction
|
if rule == nil {
|
||||||
if rule != nil {
|
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||||
action = rule.Action()
|
next = defaultOutbound.Tag()
|
||||||
}
|
}
|
||||||
if routeAction, isRouteAction := action.(*R.RuleActionRoute); isRouteAction {
|
} else {
|
||||||
next = routeAction.Outbound
|
next = rule.Outbound()
|
||||||
} else if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
|
||||||
next = defaultOutbound.Tag()
|
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
chain = append(chain, next)
|
||||||
detour, loaded := router.Outbound(next)
|
detour, loaded := router.Outbound(next)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
chain = append(chain, next)
|
|
||||||
outbound = detour.Tag()
|
outbound = detour.Tag()
|
||||||
outboundType = detour.Type()
|
outboundType = detour.Type()
|
||||||
group, isGroup := detour.(adapter.OutboundGroup)
|
group, isGroup := detour.(adapter.OutboundGroup)
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
package deprecated
|
|
||||||
|
|
||||||
import (
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
F "github.com/sagernet/sing/common/format"
|
|
||||||
|
|
||||||
"golang.org/x/mod/semver"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Note struct {
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
DeprecatedVersion string
|
|
||||||
ScheduledVersion string
|
|
||||||
EnvName string
|
|
||||||
MigrationLink string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Note) Impending() bool {
|
|
||||||
if n.ScheduledVersion == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !semver.IsValid("v" + C.Version) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
versionMinor := semver.Compare(semver.MajorMinor("v"+C.Version), "v"+n.ScheduledVersion)
|
|
||||||
if versionMinor < 0 {
|
|
||||||
panic("invalid deprecated note: " + n.Name)
|
|
||||||
}
|
|
||||||
return versionMinor <= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Note) Message() string {
|
|
||||||
return F.ToString(
|
|
||||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
|
||||||
" and will be removed in sing-box ", n.ScheduledVersion, ", please checkout documentation for migration.",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n Note) MessageWithLink() string {
|
|
||||||
return F.ToString(
|
|
||||||
n.Description, " is deprecated in sing-box ", n.DeprecatedVersion,
|
|
||||||
" and will be removed in sing-box ", n.ScheduledVersion, ", checkout documentation for migration: ", n.MigrationLink,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionBadMatchSource = Note{
|
|
||||||
Name: "bad-match-source",
|
|
||||||
Description: "legacy match source rule item",
|
|
||||||
DeprecatedVersion: "1.10.0",
|
|
||||||
ScheduledVersion: "1.11.0",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/deprecated/#match-source-rule-items-are-renamed",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionGEOIP = Note{
|
|
||||||
Name: "geoip",
|
|
||||||
Description: "geoip database",
|
|
||||||
DeprecatedVersion: "1.8.0",
|
|
||||||
ScheduledVersion: "1.12.0",
|
|
||||||
EnvName: "GEOIP",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geoip-to-rule-sets",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionGEOSITE = Note{
|
|
||||||
Name: "geosite",
|
|
||||||
Description: "geosite database",
|
|
||||||
DeprecatedVersion: "1.8.0",
|
|
||||||
ScheduledVersion: "1.12.0",
|
|
||||||
EnvName: "GEOSITE",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-geosite-to-rule-sets",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionTUNAddressX = Note{
|
|
||||||
Name: "tun-address-x",
|
|
||||||
Description: "legacy tun address fields",
|
|
||||||
DeprecatedVersion: "1.10.0",
|
|
||||||
ScheduledVersion: "1.12.0",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#tun-address-fields-are-merged",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionSpecialOutbounds = Note{
|
|
||||||
Name: "special-outbounds",
|
|
||||||
Description: "legacy special outbounds",
|
|
||||||
DeprecatedVersion: "1.11.0",
|
|
||||||
ScheduledVersion: "1.13.0",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionInboundOptions = Note{
|
|
||||||
Name: "inbound-options",
|
|
||||||
Description: "legacy inbound fields",
|
|
||||||
DeprecatedVersion: "1.11.0",
|
|
||||||
ScheduledVersion: "1.13.0",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-special-outbounds-to-rule-actions",
|
|
||||||
}
|
|
||||||
|
|
||||||
var OptionLegacyDNSRouteOptions = Note{
|
|
||||||
Name: "legacy-dns-route-options",
|
|
||||||
Description: "legacy dns route options",
|
|
||||||
DeprecatedVersion: "1.11.0",
|
|
||||||
ScheduledVersion: "1.12.0",
|
|
||||||
MigrationLink: "https://sing-box.sagernet.org/migration/#migrate-legacy-dns-route-options-to-rule-actions",
|
|
||||||
}
|
|
||||||
|
|
||||||
var Options = []Note{
|
|
||||||
OptionBadMatchSource,
|
|
||||||
OptionGEOIP,
|
|
||||||
OptionGEOSITE,
|
|
||||||
OptionTUNAddressX,
|
|
||||||
OptionSpecialOutbounds,
|
|
||||||
OptionInboundOptions,
|
|
||||||
OptionLegacyDNSRouteOptions,
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package deprecated
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"runtime/debug"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Manager interface {
|
|
||||||
ReportDeprecated(feature Note)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Report(ctx context.Context, feature Note) {
|
|
||||||
manager := service.FromContext[Manager](ctx)
|
|
||||||
if manager == nil {
|
|
||||||
debug.PrintStack()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
manager.ReportDeprecated(feature)
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package deprecated
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stderrManager struct {
|
|
||||||
logger logger.Logger
|
|
||||||
reported map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStderrManager(logger logger.Logger) Manager {
|
|
||||||
return &stderrManager{
|
|
||||||
logger: logger,
|
|
||||||
reported: make(map[string]bool),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *stderrManager) ReportDeprecated(feature Note) {
|
|
||||||
if f.reported[feature.Name] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.reported[feature.Name] = true
|
|
||||||
if !feature.Impending() {
|
|
||||||
f.logger.Warn(feature.MessageWithLink())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enable, enableErr := strconv.ParseBool(os.Getenv("ENABLE_DEPRECATED_" + feature.EnvName))
|
|
||||||
if enableErr == nil && enable {
|
|
||||||
f.logger.Warn(feature.MessageWithLink())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.logger.Error(feature.MessageWithLink())
|
|
||||||
f.logger.Fatal("to continuing using this feature, set ENABLE_DEPRECATED_" + feature.EnvName + "=true")
|
|
||||||
}
|
|
||||||
@@ -16,5 +16,4 @@ const (
|
|||||||
CommandSetSystemProxyEnabled
|
CommandSetSystemProxyEnabled
|
||||||
CommandConnections
|
CommandConnections
|
||||||
CommandCloseConnection
|
CommandCloseConnection
|
||||||
CommandGetDeprecatedNotes
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,10 +18,6 @@ func (c *CommandClient) CloseConnection(connId string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandCloseConnection))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
writer := bufio.NewWriter(conn)
|
writer := bufio.NewWriter(conn)
|
||||||
err = varbin.Write(writer, binary.BigEndian, connId)
|
err = varbin.Write(writer, binary.BigEndian, connId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ func (c *CommandClient) handleConnectionsConn(conn net.Conn) {
|
|||||||
connections Connections
|
connections Connections
|
||||||
)
|
)
|
||||||
for {
|
for {
|
||||||
rawConnections = nil
|
|
||||||
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
|
err := varbin.Read(reader, binary.BigEndian, &rawConnections)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.handler.Disconnected(err.Error())
|
c.handler.Disconnected(err.Error())
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/varbin"
|
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *CommandClient) GetDeprecatedNotes() (DeprecatedNoteIterator, error) {
|
|
||||||
conn, err := c.directConnect()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
err = binary.Write(conn, binary.BigEndian, uint8(CommandGetDeprecatedNotes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = readError(conn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var features []deprecated.Note
|
|
||||||
err = varbin.Read(conn, binary.BigEndian, &features)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return newIterator(common.Map(features, func(it deprecated.Note) *DeprecatedNote { return (*DeprecatedNote)(&it) })), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *CommandServer) handleGetDeprecatedNotes(conn net.Conn) error {
|
|
||||||
boxService := s.service
|
|
||||||
if boxService == nil {
|
|
||||||
return writeError(conn, E.New("service not ready"))
|
|
||||||
}
|
|
||||||
err := writeError(conn, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return varbin.Write(conn, binary.BigEndian, service.FromContext[deprecated.Manager](boxService.ctx).(*deprecatedManager).Get())
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
"github.com/sagernet/sing/service"
|
"github.com/sagernet/sing/service"
|
||||||
@@ -118,14 +118,14 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|||||||
}
|
}
|
||||||
var groups []OutboundGroup
|
var groups []OutboundGroup
|
||||||
for _, iGroup := range iGroups {
|
for _, iGroup := range iGroups {
|
||||||
var outboundGroup OutboundGroup
|
var group OutboundGroup
|
||||||
outboundGroup.Tag = iGroup.Tag()
|
group.Tag = iGroup.Tag()
|
||||||
outboundGroup.Type = iGroup.Type()
|
group.Type = iGroup.Type()
|
||||||
_, outboundGroup.Selectable = iGroup.(*group.Selector)
|
_, group.Selectable = iGroup.(*outbound.Selector)
|
||||||
outboundGroup.Selected = iGroup.Now()
|
group.Selected = iGroup.Now()
|
||||||
if cacheFile != nil {
|
if cacheFile != nil {
|
||||||
if isExpand, loaded := cacheFile.LoadGroupExpand(outboundGroup.Tag); loaded {
|
if isExpand, loaded := cacheFile.LoadGroupExpand(group.Tag); loaded {
|
||||||
outboundGroup.IsExpand = isExpand
|
group.IsExpand = isExpand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,12 +142,12 @@ func writeGroups(writer io.Writer, boxService *BoxService) error {
|
|||||||
item.URLTestTime = history.Time.Unix()
|
item.URLTestTime = history.Time.Unix()
|
||||||
item.URLTestDelay = int32(history.Delay)
|
item.URLTestDelay = int32(history.Delay)
|
||||||
}
|
}
|
||||||
outboundGroup.ItemList = append(outboundGroup.ItemList, &item)
|
group.ItemList = append(group.ItemList, &item)
|
||||||
}
|
}
|
||||||
if len(outboundGroup.ItemList) < 2 {
|
if len(group.ItemList) < 2 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
groups = append(groups, outboundGroup)
|
groups = append(groups, group)
|
||||||
}
|
}
|
||||||
return varbin.Write(writer, binary.BigEndian, groups)
|
return varbin.Write(writer, binary.BigEndian, groups)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/varbin"
|
"github.com/sagernet/sing/common/varbin"
|
||||||
)
|
)
|
||||||
@@ -47,7 +47,7 @@ func (s *CommandServer) handleSelectOutbound(conn net.Conn) error {
|
|||||||
if !isLoaded {
|
if !isLoaded {
|
||||||
return writeError(conn, E.New("selector not found: ", groupTag))
|
return writeError(conn, E.New("selector not found: ", groupTag))
|
||||||
}
|
}
|
||||||
selector, isSelector := outboundGroup.(*group.Selector)
|
selector, isSelector := outboundGroup.(*outbound.Selector)
|
||||||
if !isSelector {
|
if !isSelector {
|
||||||
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
return writeError(conn, E.New("outbound is not a selector: ", groupTag))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,8 +174,6 @@ func (s *CommandServer) handleConnection(conn net.Conn) error {
|
|||||||
return s.handleConnectionsConn(conn)
|
return s.handleConnectionsConn(conn)
|
||||||
case CommandCloseConnection:
|
case CommandCloseConnection:
|
||||||
return s.handleCloseConnection(conn)
|
return s.handleCloseConnection(conn)
|
||||||
case CommandGetDeprecatedNotes:
|
|
||||||
return s.handleGetDeprecatedNotes(conn)
|
|
||||||
default:
|
default:
|
||||||
return E.New("unknown command: ", command)
|
return E.New("unknown command: ", command)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
"github.com/sagernet/sing-box/protocol/group"
|
"github.com/sagernet/sing-box/outbound"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/batch"
|
"github.com/sagernet/sing/common/batch"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -49,7 +49,7 @@ func (s *CommandServer) handleURLTest(conn net.Conn) error {
|
|||||||
if !isOutboundGroup {
|
if !isOutboundGroup {
|
||||||
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
return writeError(conn, E.New("outbound is not a group: ", groupTag))
|
||||||
}
|
}
|
||||||
urlTest, isURLTest := abstractOutboundGroup.(*group.URLTest)
|
urlTest, isURLTest := abstractOutboundGroup.(*outbound.URLTest)
|
||||||
if isURLTest {
|
if isURLTest {
|
||||||
go urlTest.CheckOutbounds()
|
go urlTest.CheckOutbounds()
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,8 +9,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box"
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/process"
|
"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-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -18,11 +16,10 @@ import (
|
|||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/common/x/list"
|
"github.com/sagernet/sing/common/x/list"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
func parseConfig(configContent string) (option.Options, error) {
|
||||||
options, err := json.UnmarshalExtendedContext[option.Options](ctx, []byte(configContent))
|
options, err := json.UnmarshalExtended[option.Options]([]byte(configContent))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return option.Options{}, E.Cause(err, "decode config")
|
return option.Options{}, E.Cause(err, "decode config")
|
||||||
}
|
}
|
||||||
@@ -30,17 +27,16 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckConfig(configContent string) error {
|
func CheckConfig(configContent string) error {
|
||||||
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
|
options, err := parseConfig(configContent)
|
||||||
options, err := parseConfig(ctx, configContent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
ctx = service.ContextWith[platform.Interface](ctx, (*platformInterfaceStub)(nil))
|
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
PlatformInterface: (*platformInterfaceStub)(nil),
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
instance.Close()
|
instance.Close()
|
||||||
@@ -58,7 +54,7 @@ func (s *platformInterfaceStub) UsePlatformAutoDetectInterfaceControl() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) AutoDetectInterfaceControl(fd int) error {
|
func (s *platformInterfaceStub) AutoDetectInterfaceControl() control.Func {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,12 +134,8 @@ func (s *interfaceMonitorStub) RegisterCallback(callback tun.DefaultInterfaceUpd
|
|||||||
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
func (s *interfaceMonitorStub) UnregisterCallback(element *list.Element[tun.DefaultInterfaceUpdateCallback]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *platformInterfaceStub) SendNotification(notification *platform.Notification) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatConfig(configContent string) (string, error) {
|
func FormatConfig(configContent string) (string, error) {
|
||||||
options, err := parseConfig(box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry()), configContent)
|
options, err := parseConfig(configContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ deprecated.Manager = (*deprecatedManager)(nil)
|
|
||||||
|
|
||||||
type deprecatedManager struct {
|
|
||||||
access sync.Mutex
|
|
||||||
features []deprecated.Note
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *deprecatedManager) ReportDeprecated(feature deprecated.Note) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.features = common.Uniq(append(m.features, feature))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *deprecatedManager) Get() []deprecated.Note {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
features := m.features
|
|
||||||
m.features = nil
|
|
||||||
return features
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = deprecated.Note(DeprecatedNote{})
|
|
||||||
|
|
||||||
type DeprecatedNote struct {
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
DeprecatedVersion string
|
|
||||||
ScheduledVersion string
|
|
||||||
EnvName string
|
|
||||||
MigrationLink string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n DeprecatedNote) Impending() bool {
|
|
||||||
return deprecated.Note(n).Impending()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n DeprecatedNote) Message() string {
|
|
||||||
return deprecated.Note(n).Message()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n DeprecatedNote) MessageWithLink() string {
|
|
||||||
return deprecated.Note(n).MessageWithLink()
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeprecatedNoteIterator interface {
|
|
||||||
HasNext() bool
|
|
||||||
Next() *DeprecatedNote
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -69,9 +69,8 @@ type httpClient struct {
|
|||||||
|
|
||||||
func NewHTTPClient() HTTPClient {
|
func NewHTTPClient() HTTPClient {
|
||||||
client := new(httpClient)
|
client := new(httpClient)
|
||||||
|
client.client.Timeout = 15 * time.Second
|
||||||
client.client.Transport = &client.transport
|
client.client.Transport = &client.transport
|
||||||
client.transport.ForceAttemptHTTP2 = true
|
|
||||||
client.transport.TLSHandshakeTimeout = C.TCPTimeout
|
|
||||||
client.transport.TLSClientConfig = &client.tls
|
client.transport.TLSClientConfig = &client.tls
|
||||||
client.transport.DisableKeepAlives = true
|
client.transport.DisableKeepAlives = true
|
||||||
return client
|
return client
|
||||||
@@ -128,6 +127,7 @@ func (c *httpClient) TrySocks5(port int32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpClient) KeepAlive() {
|
func (c *httpClient) KeepAlive() {
|
||||||
|
c.transport.ForceAttemptHTTP2 = true
|
||||||
c.transport.DisableKeepAlives = false
|
c.transport.DisableKeepAlives = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from net.linkFlags
|
|
||||||
func linkFlags(rawFlags uint32) net.Flags {
|
|
||||||
var f net.Flags
|
|
||||||
if rawFlags&syscall.IFF_UP != 0 {
|
|
||||||
f |= net.FlagUp
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_RUNNING != 0 {
|
|
||||||
f |= net.FlagRunning
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_BROADCAST != 0 {
|
|
||||||
f |= net.FlagBroadcast
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_LOOPBACK != 0 {
|
|
||||||
f |= net.FlagLoopback
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_POINTOPOINT != 0 {
|
|
||||||
f |= net.FlagPointToPoint
|
|
||||||
}
|
|
||||||
if rawFlags&syscall.IFF_MULTICAST != 0 {
|
|
||||||
f |= net.FlagMulticast
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
//go:build !linux
|
|
||||||
|
|
||||||
package libbox
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func linkFlags(rawFlags uint32) net.Flags {
|
|
||||||
panic("stub!")
|
|
||||||
}
|
|
||||||
@@ -22,7 +22,6 @@ type PlatformInterface interface {
|
|||||||
IncludeAllNetworks() bool
|
IncludeAllNetworks() bool
|
||||||
ReadWIFIState() *WIFIState
|
ReadWIFIState() *WIFIState
|
||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
SendNotification(notification *Notification) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TunInterface interface {
|
type TunInterface interface {
|
||||||
@@ -39,7 +38,6 @@ type NetworkInterface struct {
|
|||||||
MTU int32
|
MTU int32
|
||||||
Name string
|
Name string
|
||||||
Addresses StringIterator
|
Addresses StringIterator
|
||||||
Flags int32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WIFIState struct {
|
type WIFIState struct {
|
||||||
@@ -56,16 +54,6 @@ type NetworkInterfaceIterator interface {
|
|||||||
HasNext() bool
|
HasNext() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
Identifier string
|
|
||||||
TypeName string
|
|
||||||
TypeID int32
|
|
||||||
Title string
|
|
||||||
Subtitle string
|
|
||||||
Body string
|
|
||||||
OpenURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OnDemandRule interface {
|
type OnDemandRule interface {
|
||||||
Target() int32
|
Target() int32
|
||||||
DNSSearchDomainMatch() StringIterator
|
DNSSearchDomainMatch() StringIterator
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
type Interface interface {
|
type Interface interface {
|
||||||
Initialize(ctx context.Context, router adapter.Router) error
|
Initialize(ctx context.Context, router adapter.Router) error
|
||||||
UsePlatformAutoDetectInterfaceControl() bool
|
UsePlatformAutoDetectInterfaceControl() bool
|
||||||
AutoDetectInterfaceControl(fd int) error
|
AutoDetectInterfaceControl() control.Func
|
||||||
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error)
|
||||||
UsePlatformDefaultInterfaceMonitor() bool
|
UsePlatformDefaultInterfaceMonitor() bool
|
||||||
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
CreateDefaultInterfaceMonitor(logger logger.Logger) tun.DefaultInterfaceMonitor
|
||||||
@@ -25,15 +25,4 @@ type Interface interface {
|
|||||||
ClearDNSCache()
|
ClearDNSCache()
|
||||||
ReadWIFIState() adapter.WIFIState
|
ReadWIFIState() adapter.WIFIState
|
||||||
process.Searcher
|
process.Searcher
|
||||||
SendNotification(notification *Notification) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
Identifier string
|
|
||||||
TypeName string
|
|
||||||
TypeID int32
|
|
||||||
Title string
|
|
||||||
Subtitle string
|
|
||||||
Body string
|
|
||||||
OpenURL string
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,7 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = binary.Read(bReader, binary.BigEndian, &content.Type)
|
err = binary.Read(reader, binary.BigEndian, &content.Type)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -233,17 +233,17 @@ func DecodeProfileContent(data []byte) (*ProfileContent, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {
|
if content.Type == ProfileTypeRemote || (version == 0 && content.Type != ProfileTypeLocal) {
|
||||||
err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdate)
|
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if version >= 1 {
|
if version >= 1 {
|
||||||
err = binary.Read(bReader, binary.BigEndian, &content.AutoUpdateInterval)
|
err = binary.Read(reader, binary.BigEndian, &content.AutoUpdateInterval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = binary.Read(bReader, binary.BigEndian, &content.LastUpdated)
|
err = binary.Read(reader, binary.BigEndian, &content.LastUpdated)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/common/process"
|
"github.com/sagernet/sing-box/common/process"
|
||||||
"github.com/sagernet/sing-box/common/urltest"
|
"github.com/sagernet/sing-box/common/urltest"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
"github.com/sagernet/sing-box/experimental/libbox/internal/procfs"
|
||||||
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
"github.com/sagernet/sing-box/experimental/libbox/platform"
|
||||||
"github.com/sagernet/sing-box/include"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing-tun"
|
"github.com/sagernet/sing-tun"
|
||||||
@@ -42,22 +40,20 @@ type BoxService struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
func NewService(configContent string, platformInterface PlatformInterface) (*BoxService, error) {
|
||||||
ctx := box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry())
|
options, err := parseConfig(configContent)
|
||||||
ctx = service.ContextWith[deprecated.Manager](ctx, new(deprecatedManager))
|
|
||||||
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
|
||||||
options, err := parseConfig(ctx, configContent)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
runtimeDebug.FreeOSMemory()
|
runtimeDebug.FreeOSMemory()
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
ctx = filemanager.WithDefault(ctx, sWorkingPath, sTempPath, sUserID, sGroupID)
|
||||||
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
urlTestHistoryStorage := urltest.NewHistoryStorage()
|
||||||
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
ctx = service.ContextWithPtr(ctx, urlTestHistoryStorage)
|
||||||
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
|
platformWrapper := &platformInterfaceWrapper{iif: platformInterface, useProcFS: platformInterface.UseProcFS()}
|
||||||
ctx = service.ContextWith[platform.Interface](ctx, platformWrapper)
|
|
||||||
instance, err := box.New(box.Options{
|
instance, err := box.New(box.Options{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Options: options,
|
Options: options,
|
||||||
|
PlatformInterface: platformWrapper,
|
||||||
PlatformLogWriter: platformWrapper,
|
PlatformLogWriter: platformWrapper,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -118,8 +114,12 @@ func (w *platformInterfaceWrapper) UsePlatformAutoDetectInterfaceControl() bool
|
|||||||
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
return w.iif.UsePlatformAutoDetectInterfaceControl()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl(fd int) error {
|
func (w *platformInterfaceWrapper) AutoDetectInterfaceControl() control.Func {
|
||||||
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
return func(network, address string, conn syscall.RawConn) error {
|
||||||
|
return control.Raw(conn, func(fd uintptr) error {
|
||||||
|
return w.iif.AutoDetectInterfaceControl(int32(fd))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
func (w *platformInterfaceWrapper) OpenTun(options *tun.Options, platformOptions option.TunPlatformOptions) (tun.Tun, error) {
|
||||||
@@ -177,7 +177,6 @@ func (w *platformInterfaceWrapper) Interfaces() ([]control.Interface, error) {
|
|||||||
MTU: int(netInterface.MTU),
|
MTU: int(netInterface.MTU),
|
||||||
Name: netInterface.Name,
|
Name: netInterface.Name,
|
||||||
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
Addresses: common.Map(iteratorToArray[string](netInterface.Addresses), netip.MustParsePrefix),
|
||||||
Flags: linkFlags(uint32(netInterface.Flags)),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return interfaces, nil
|
return interfaces, nil
|
||||||
@@ -237,7 +236,3 @@ func (w *platformInterfaceWrapper) DisableColors() bool {
|
|||||||
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
|
func (w *platformInterfaceWrapper) WriteMessage(level log.Level, message string) {
|
||||||
w.iif.WriteLog(message)
|
w.iif.WriteLog(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *platformInterfaceWrapper) SendNotification(notification *platform.Notification) error {
|
|
||||||
return w.iif.SendNotification((*Notification)(notification))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/common/humanize"
|
"github.com/sagernet/sing-box/common/humanize"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
_ "github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
debug.SetPanicOnFault(true)
|
debug.SetPanicOnFault(true)
|
||||||
|
debug.SetTraceback("all")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {
|
func Setup(basePath string, workingPath string, tempPath string, isTVOS bool) {
|
||||||
|
|||||||
44
go.mod
44
go.mod
@@ -3,50 +3,51 @@ module github.com/sagernet/sing-box
|
|||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
berty.tech/go-libtor v1.0.385
|
||||||
github.com/caddyserver/certmagic v0.20.0
|
github.com/caddyserver/certmagic v0.20.0
|
||||||
github.com/cloudflare/circl v1.3.7
|
github.com/cloudflare/circl v1.3.7
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/go-chi/chi/v5 v5.0.12
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gofrs/uuid/v5 v5.3.0
|
github.com/gofrs/uuid/v5 v5.2.0
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2
|
||||||
github.com/libdns/alidns v1.0.3
|
github.com/libdns/alidns v1.0.3
|
||||||
github.com/libdns/cloudflare v0.1.1
|
github.com/libdns/cloudflare v0.1.1
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
|
github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d
|
||||||
github.com/mholt/acmez v1.2.0
|
github.com/mholt/acmez v1.2.0
|
||||||
github.com/miekg/dns v1.1.62
|
github.com/miekg/dns v1.1.61
|
||||||
|
github.com/ooni/go-libtor v1.1.8
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||||
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.4
|
github.com/sagernet/gomobile v0.1.4
|
||||||
github.com/sagernet/gvisor v0.0.0-20241021032506-a4324256e4a3
|
github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f
|
||||||
github.com/sagernet/quic-go v0.48.1-beta.1
|
github.com/sagernet/quic-go v0.47.0-beta.2
|
||||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691
|
||||||
github.com/sagernet/sing v0.5.1-0.20241105104305-c80c8f907c56
|
github.com/sagernet/sing v0.5.0-beta.2
|
||||||
github.com/sagernet/sing-dns v0.3.1-0.20241105104342-1914f319ddab
|
github.com/sagernet/sing-dns v0.3.0-beta.14
|
||||||
github.com/sagernet/sing-mux v0.2.1-0.20241020175909-fe6153f7a9ec
|
github.com/sagernet/sing-mux v0.2.0
|
||||||
github.com/sagernet/sing-quic v0.3.0-rc.2
|
github.com/sagernet/sing-quic v0.3.0-beta.3
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4
|
github.com/sagernet/sing-shadowtls v0.1.4
|
||||||
github.com/sagernet/sing-tun v0.4.0-rc.5.0.20241107062822-5a91eb99c90f
|
github.com/sagernet/sing-tun v0.4.0-beta.16
|
||||||
github.com/sagernet/sing-vmess v0.1.12
|
github.com/sagernet/sing-vmess v0.1.12
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
github.com/sagernet/utls v1.6.7
|
github.com/sagernet/utls v1.6.7
|
||||||
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8
|
github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.28.0
|
golang.org/x/crypto v0.25.0
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||||
golang.org/x/mod v0.20.0
|
golang.org/x/net v0.27.0
|
||||||
golang.org/x/net v0.30.0
|
golang.org/x/sys v0.25.0
|
||||||
golang.org/x/sys v0.26.0
|
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6
|
||||||
google.golang.org/grpc v1.63.2
|
google.golang.org/grpc v1.63.2
|
||||||
google.golang.org/protobuf v1.33.0
|
google.golang.org/protobuf v1.33.0
|
||||||
@@ -64,10 +65,10 @@ require (
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.2 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect
|
||||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/klauspost/compress v1.17.4 // indirect
|
github.com/klauspost/compress v1.17.4 // indirect
|
||||||
@@ -88,10 +89,11 @@ require (
|
|||||||
github.com/vishvananda/netns v0.0.4 // indirect
|
github.com/vishvananda/netns v0.0.4 // indirect
|
||||||
github.com/zeebo/blake3 v0.2.3 // indirect
|
github.com/zeebo/blake3 v0.2.3 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
golang.org/x/mod v0.19.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/text v0.19.0 // indirect
|
golang.org/x/text v0.18.0 // indirect
|
||||||
golang.org/x/time v0.7.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
golang.org/x/tools v0.23.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // 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/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user