Compare commits

...

28 Commits

Author SHA1 Message Date
世界
2220feb58c
documentation: Bump version 2023-12-20 11:53:49 +08:00
世界
b99803dafe
documentation: Update package managers 2023-12-20 11:51:31 +08:00
世界
89dfc04eee
documentation: Fix link format 2023-12-20 11:51:31 +08:00
世界
53f6515206
Add loopback detect for direct outbound 2023-12-20 11:51:31 +08:00
世界
ae0faa24b6
Fix missing handshake timeout for multiplex 2023-12-19 23:20:12 +08:00
世界
9142e70af4
Fix DNS dial context 2023-12-19 16:56:17 +08:00
世界
285278b7be
Make generated files have SUDO_USER's permissions if possible. 2023-12-18 22:27:11 +08:00
世界
6d4f54d56a
Refactor inbound/outbound options struct 2023-12-18 22:27:11 +08:00
世界
7740293a66
Improve configuration merge 2023-12-18 22:27:11 +08:00
世界
5052e86a5d
Update uTLS to 1.5.4 2023-12-18 22:27:11 +08:00
世界
0732b63609
Update tfo-go 2023-12-18 22:27:11 +08:00
世界
9dedf8be30
Update gomobile and add tag 2023-12-18 22:27:11 +08:00
世界
2b518d3819
Update cloudflare-tls to go1.21.5 2023-12-18 22:27:11 +08:00
世界
b0cd7af416
Remove unnecessary context wrappers 2023-12-18 22:27:11 +08:00
世界
a99c3108a7
Improve read wait interface &
Refactor Authenticator interface to struct &
Update smux &
Update gVisor to 20231204.0 &
Update wireguard-go &
Add GSO support for TUN/WireGuard &
Fix router pre-start &
Fix bind forwarder to interface for systems stack
2023-12-18 22:27:03 +08:00
世界
2f4f4e18ee
Make type check strict 2023-12-18 22:26:02 +08:00
世界
bc2d73b2d6
Remove comparable limit for Listable 2023-12-18 22:26:01 +08:00
世界
abd4d8b367
Avoid opening log output before start &
Replace tracing logs with task monitor
2023-12-18 22:26:01 +08:00
世界
f998ccecde
Update documentation 2023-12-18 22:26:01 +08:00
世界
fc9804b20f
Add idle_timeout for URLTest outbound 2023-12-18 22:26:01 +08:00
世界
d1ad342cb0
Skip internal fake-ip queries 2023-12-18 22:26:01 +08:00
世界
80cc0cd5db
Migrate contentjson and badjson to library &
Add omitempty in format
2023-12-18 22:25:54 +08:00
世界
3041f34dfa
Update documentation 2023-12-18 22:25:54 +08:00
世界
3a9b787a6e
Independent source_ip_is_private and ip_is_private rules 2023-12-18 22:25:53 +08:00
世界
bb70e8267e
Add rule-set 2023-12-18 22:25:53 +08:00
世界
411f02ee4f
Update buffer usage 2023-12-18 22:25:53 +08:00
世界
340e74eed4
Allow nested logical rules 2023-12-18 22:25:53 +08:00
世界
ad93b45021
Migrate to independent cache file 2023-12-18 22:25:53 +08:00
274 changed files with 8000 additions and 3552 deletions

1
.gitignore vendored
View File

@ -1,6 +1,7 @@
/.idea/ /.idea/
/vendor/ /vendor/
/*.json /*.json
/*.srs
/*.db /*.db
/site/ /site/
/bin/ /bin/

View File

@ -1,7 +1,7 @@
NAME = sing-box NAME = sing-box
COMMIT = $(shell git rev-parse --short HEAD) COMMIT = $(shell git rev-parse --short HEAD)
TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_utls,with_reality_server,with_clash_api TAGS_GO118 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api
TAGS_GO120 = with_quic,with_ech TAGS_GO120 = with_quic,with_ech,with_utls
TAGS ?= $(TAGS_GO118),$(TAGS_GO120) TAGS ?= $(TAGS_GO118),$(TAGS_GO120)
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
@ -178,9 +178,8 @@ lib:
go run ./cmd/internal/build_libbox -target ios go run ./cmd/internal/build_libbox -target ios
lib_install: lib_install:
go get -v -d go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.1
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.0.0-20230915142329-c6740b6d2950 go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.1
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.0.0-20230915142329-c6740b6d2950
docs: docs:
mkdocs serve mkdocs serve

View File

@ -1,11 +1,16 @@
package adapter package adapter
import ( import (
"bytes"
"context" "context"
"encoding/binary"
"io"
"net" "net"
"time"
"github.com/sagernet/sing-box/common/urltest" "github.com/sagernet/sing-box/common/urltest"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/rw"
) )
type ClashServer interface { type ClashServer interface {
@ -13,22 +18,83 @@ type ClashServer interface {
PreStarter PreStarter
Mode() string Mode() string
ModeList() []string ModeList() []string
StoreSelected() bool
StoreFakeIP() bool
CacheFile() ClashCacheFile
HistoryStorage() *urltest.HistoryStorage HistoryStorage() *urltest.HistoryStorage
RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker) RoutedConnection(ctx context.Context, conn net.Conn, metadata InboundContext, matchedRule Rule) (net.Conn, Tracker)
RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata InboundContext, matchedRule Rule) (N.PacketConn, Tracker)
} }
type ClashCacheFile interface { type CacheFile interface {
Service
PreStarter
StoreFakeIP() bool
FakeIPStorage
LoadMode() string LoadMode() string
StoreMode(mode string) error StoreMode(mode string) error
LoadSelected(group string) string LoadSelected(group string) string
StoreSelected(group string, selected string) error StoreSelected(group string, selected string) error
LoadGroupExpand(group string) (isExpand bool, loaded bool) LoadGroupExpand(group string) (isExpand bool, loaded bool)
StoreGroupExpand(group string, expand bool) error StoreGroupExpand(group string, expand bool) error
FakeIPStorage LoadRuleSet(tag string) *SavedRuleSet
SaveRuleSet(tag string, set *SavedRuleSet) error
}
type SavedRuleSet struct {
Content []byte
LastUpdated time.Time
LastEtag string
}
func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
var buffer bytes.Buffer
err := binary.Write(&buffer, binary.BigEndian, uint8(1))
if err != nil {
return nil, err
}
err = rw.WriteUVariant(&buffer, uint64(len(s.Content)))
if err != nil {
return nil, err
}
buffer.Write(s.Content)
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
if err != nil {
return nil, err
}
err = rw.WriteVString(&buffer, s.LastEtag)
if err != nil {
return nil, err
}
return buffer.Bytes(), nil
}
func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
reader := bytes.NewReader(data)
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return err
}
contentLen, err := rw.ReadUVariant(reader)
if err != nil {
return err
}
s.Content = make([]byte, contentLen)
_, err = io.ReadFull(reader, s.Content)
if err != nil {
return err
}
var lastUpdated int64
err = binary.Read(reader, binary.BigEndian, &lastUpdated)
if err != nil {
return err
}
s.LastUpdated = time.Unix(lastUpdated, 0)
s.LastEtag, err = rw.ReadVString(reader)
if err != nil {
return err
}
return nil
} }
type Tracker interface { type Tracker interface {

View File

@ -46,11 +46,24 @@ type InboundContext struct {
SourceGeoIPCode string SourceGeoIPCode string
GeoIPCode string GeoIPCode string
ProcessInfo *process.Info ProcessInfo *process.Info
QueryType uint16
FakeIP bool FakeIP bool
// dns cache // rule cache
QueryType uint16 IPCIDRMatchSource bool
SourceAddressMatch bool
SourcePortMatch bool
DestinationAddressMatch bool
DestinationPortMatch bool
}
func (c *InboundContext) ResetRuleCache() {
c.IPCIDRMatchSource = false
c.SourceAddressMatch = false
c.SourcePortMatch = false
c.DestinationAddressMatch = false
c.DestinationPortMatch = false
} }
type inboundContextKey struct{} type inboundContextKey struct{}

View File

@ -2,12 +2,14 @@ package adapter
import ( import (
"context" "context"
"net/http"
"net/netip" "net/netip"
"github.com/sagernet/sing-box/common/geoip" "github.com/sagernet/sing-box/common/geoip"
"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"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/service" "github.com/sagernet/sing/service"
mdns "github.com/miekg/dns" mdns "github.com/miekg/dns"
@ -15,11 +17,12 @@ import (
type Router interface { type Router interface {
Service Service
PreStarter
PostStarter PostStarter
Outbounds() []Outbound Outbounds() []Outbound
Outbound(tag string) (Outbound, bool) Outbound(tag string) (Outbound, bool)
DefaultOutbound(network string) Outbound DefaultOutbound(network string) (Outbound, error)
FakeIPStore() FakeIPStore FakeIPStore() FakeIPStore
@ -28,6 +31,8 @@ type Router interface {
GeoIPReader() *geoip.Reader GeoIPReader() *geoip.Reader
LoadGeosite(code string) (Rule, error) LoadGeosite(code string) (Rule, error)
RuleSet(tag string) (RuleSet, bool)
Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error) Exchange(ctx context.Context, message *mdns.Msg) (*mdns.Msg, error)
Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error) Lookup(ctx context.Context, domain string, strategy dns.DomainStrategy) ([]netip.Addr, error)
LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error) LookupDefault(ctx context.Context, domain string) ([]netip.Addr, error)
@ -62,11 +67,15 @@ func RouterFromContext(ctx context.Context) Router {
return service.FromContext[Router](ctx) return service.FromContext[Router](ctx)
} }
type HeadlessRule interface {
Match(metadata *InboundContext) bool
}
type Rule interface { type Rule interface {
HeadlessRule
Service Service
Type() string Type() string
UpdateGeosite() error UpdateGeosite() error
Match(metadata *InboundContext) bool
Outbound() string Outbound() string
String() string String() string
} }
@ -77,6 +86,24 @@ type DNSRule interface {
RewriteTTL() *uint32 RewriteTTL() *uint32
} }
type RuleSet interface {
StartContext(ctx context.Context, startContext RuleSetStartContext) error
PostStart() error
Metadata() RuleSetMetadata
Close() error
HeadlessRule
}
type RuleSetMetadata struct {
ContainsProcessRule bool
ContainsWIFIRule bool
}
type RuleSetStartContext interface {
HTTPClient(detour string, dialer N.Dialer) *http.Client
Close()
}
type InterfaceUpdateListener interface { type InterfaceUpdateListener interface {
InterfaceUpdated() InterfaceUpdated()
} }

104
box.go
View File

@ -9,7 +9,10 @@ import (
"time" "time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
C "github.com/sagernet/sing-box/constant"
"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/libbox/platform" "github.com/sagernet/sing-box/experimental/libbox/platform"
"github.com/sagernet/sing-box/inbound" "github.com/sagernet/sing-box/inbound"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@ -32,7 +35,8 @@ type Box struct {
outbounds []adapter.Outbound outbounds []adapter.Outbound
logFactory log.Factory logFactory log.Factory
logger log.ContextLogger logger log.ContextLogger
preServices map[string]adapter.Service preServices1 map[string]adapter.Service
preServices2 map[string]adapter.Service
postServices map[string]adapter.Service postServices map[string]adapter.Service
done chan struct{} done chan struct{}
} }
@ -45,17 +49,21 @@ type Options struct {
} }
func New(options Options) (*Box, error) { func New(options Options) (*Box, error) {
createdAt := time.Now()
ctx := options.Context ctx := options.Context
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
ctx = service.ContextWithDefaultRegistry(ctx) ctx = service.ContextWithDefaultRegistry(ctx)
ctx = pause.ContextWithDefaultManager(ctx) ctx = pause.WithDefaultManager(ctx)
createdAt := time.Now()
experimentalOptions := common.PtrValueOrDefault(options.Experimental) experimentalOptions := common.PtrValueOrDefault(options.Experimental)
applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug))
var needCacheFile bool
var needClashAPI bool var needClashAPI bool
var needV2RayAPI bool var needV2RayAPI bool
if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil {
needCacheFile = true
}
if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil { if experimentalOptions.ClashAPI != nil || options.PlatformLogWriter != nil {
needClashAPI = true needClashAPI = true
} }
@ -145,8 +153,17 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "initialize platform interface") return nil, E.Cause(err, "initialize platform interface")
} }
} }
preServices := make(map[string]adapter.Service) preServices1 := make(map[string]adapter.Service)
preServices2 := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service) postServices := make(map[string]adapter.Service)
if needCacheFile {
cacheFile := service.FromContext[adapter.CacheFile](ctx)
if cacheFile == nil {
cacheFile = cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
}
preServices1["cache file"] = cacheFile
}
if needClashAPI { if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options)
@ -155,7 +172,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create clash api server") return nil, E.Cause(err, "create clash api server")
} }
router.SetClashServer(clashServer) router.SetClashServer(clashServer)
preServices["clash api"] = clashServer preServices2["clash api"] = clashServer
} }
if needV2RayAPI { if needV2RayAPI {
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
@ -163,7 +180,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create v2ray api server") return nil, E.Cause(err, "create v2ray api server")
} }
router.SetV2RayServer(v2rayServer) router.SetV2RayServer(v2rayServer)
preServices["v2ray api"] = v2rayServer preServices2["v2ray api"] = v2rayServer
} }
return &Box{ return &Box{
router: router, router: router,
@ -172,7 +189,8 @@ func New(options Options) (*Box, error) {
createdAt: createdAt, createdAt: createdAt,
logFactory: logFactory, logFactory: logFactory,
logger: logFactory.Logger(), logger: logFactory.Logger(),
preServices: preServices, preServices1: preServices1,
preServices2: preServices2,
postServices: postServices, postServices: postServices,
done: make(chan struct{}), done: make(chan struct{}),
}, nil }, nil
@ -217,16 +235,38 @@ func (s *Box) Start() error {
} }
func (s *Box) preStart() error { func (s *Box) preStart() error {
for serviceName, service := range s.preServices { monitor := taskmonitor.New(s.logger, C.DefaultStartTimeout)
monitor.Start("start logger")
err := s.logFactory.Start()
monitor.Finish()
if err != nil {
return E.Cause(err, "start logger")
}
for serviceName, service := range s.preServices1 {
if preService, isPreService := service.(adapter.PreStarter); isPreService { if preService, isPreService := service.(adapter.PreStarter); isPreService {
s.logger.Trace("pre-start ", serviceName) monitor.Start("pre-start ", serviceName)
err := preService.PreStart() err := preService.PreStart()
monitor.Finish()
if err != nil { if err != nil {
return E.Cause(err, "pre-starting ", serviceName) return E.Cause(err, "pre-start ", serviceName)
} }
} }
} }
err := s.startOutbounds() for serviceName, service := range s.preServices2 {
if preService, isPreService := service.(adapter.PreStarter); isPreService {
monitor.Start("pre-start ", serviceName)
err := preService.PreStart()
monitor.Finish()
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
}
}
}
err = s.router.PreStart()
if err != nil {
return E.Cause(err, "pre-start router")
}
err = s.startOutbounds()
if err != nil { if err != nil {
return err return err
} }
@ -238,8 +278,13 @@ func (s *Box) start() error {
if err != nil { if err != nil {
return err return err
} }
for serviceName, service := range s.preServices { for serviceName, service := range s.preServices1 {
s.logger.Trace("starting ", serviceName) err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for serviceName, service := range s.preServices2 {
err = service.Start() err = service.Start()
if err != nil { if err != nil {
return E.Cause(err, "start ", serviceName) return E.Cause(err, "start ", serviceName)
@ -252,7 +297,6 @@ func (s *Box) start() error {
} else { } else {
tag = in.Tag() tag = in.Tag()
} }
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
err = in.Start() err = in.Start()
if err != nil { if err != nil {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]") return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
@ -263,7 +307,6 @@ func (s *Box) start() error {
func (s *Box) postStart() error { func (s *Box) postStart() error {
for serviceName, service := range s.postServices { for serviceName, service := range s.postServices {
s.logger.Trace("starting ", service)
err := service.Start() err := service.Start()
if err != nil { if err != nil {
return E.Cause(err, "start ", serviceName) return E.Cause(err, "start ", serviceName)
@ -271,14 +314,13 @@ func (s *Box) postStart() error {
} }
for _, outbound := range s.outbounds { for _, outbound := range s.outbounds {
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound { if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
s.logger.Trace("post-starting outbound/", outbound.Tag())
err := lateOutbound.PostStart() err := lateOutbound.PostStart()
if err != nil { if err != nil {
return E.Cause(err, "post-start outbound/", outbound.Tag()) return E.Cause(err, "post-start outbound/", outbound.Tag())
} }
} }
} }
s.logger.Trace("post-starting router")
return s.router.PostStart() return s.router.PostStart()
} }
@ -289,41 +331,53 @@ func (s *Box) Close() error {
default: default:
close(s.done) close(s.done)
} }
monitor := taskmonitor.New(s.logger, C.DefaultStopTimeout)
var errors error var errors error
for serviceName, service := range s.postServices { for serviceName, service := range s.postServices {
s.logger.Trace("closing ", serviceName) monitor.Start("close ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error { errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName) return E.Cause(err, "close ", serviceName)
}) })
monitor.Finish()
} }
for i, in := range s.inbounds { for i, in := range s.inbounds {
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]") monitor.Start("close inbound/", in.Type(), "[", i, "]")
errors = E.Append(errors, in.Close(), func(err error) error { errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]") return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
}) })
monitor.Finish()
} }
for i, out := range s.outbounds { for i, out := range s.outbounds {
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]") monitor.Start("close outbound/", out.Type(), "[", i, "]")
errors = E.Append(errors, common.Close(out), func(err error) error { errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]") return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
}) })
monitor.Finish()
} }
s.logger.Trace("closing router") monitor.Start("close router")
if err := common.Close(s.router); err != nil { if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error { errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router") return E.Cause(err, "close router")
}) })
} }
for serviceName, service := range s.preServices { monitor.Finish()
s.logger.Trace("closing ", serviceName) for serviceName, service := range s.preServices1 {
monitor.Start("close ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error { errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName) return E.Cause(err, "close ", serviceName)
}) })
monitor.Finish()
}
for serviceName, service := range s.preServices2 {
monitor.Start("close ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
monitor.Finish()
} }
s.logger.Trace("closing log factory")
if err := common.Close(s.logFactory); err != nil { if err := common.Close(s.logFactory); err != nil {
errors = E.Append(errors, err, func(err error) error { errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close log factory") return E.Cause(err, "close logger")
}) })
} }
return errors return errors

View File

@ -4,12 +4,15 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/taskmonitor"
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"
F "github.com/sagernet/sing/common/format" F "github.com/sagernet/sing/common/format"
) )
func (s *Box) startOutbounds() error { func (s *Box) startOutbounds() error {
monitor := taskmonitor.New(s.logger, C.DefaultStartTimeout)
outboundTags := make(map[adapter.Outbound]string) outboundTags := make(map[adapter.Outbound]string)
outbounds := make(map[string]adapter.Outbound) outbounds := make(map[string]adapter.Outbound)
for i, outboundToStart := range s.outbounds { for i, outboundToStart := range s.outbounds {
@ -43,8 +46,9 @@ func (s *Box) startOutbounds() error {
started[outboundTag] = true started[outboundTag] = true
canContinue = true canContinue = true
if starter, isStarter := outboundToStart.(common.Starter); isStarter { if starter, isStarter := outboundToStart.(common.Starter); isStarter {
s.logger.Trace("initializing outbound/", outboundToStart.Type(), "[", outboundTag, "]") monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
err := starter.Start() err := starter.Start()
monitor.Finish()
if err != nil { if err != nil {
return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]") return E.Cause(err, "initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
} }

View File

@ -7,7 +7,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
_ "github.com/sagernet/gomobile/event/key" _ "github.com/sagernet/gomobile"
"github.com/sagernet/sing-box/cmd/internal/build_shared" "github.com/sagernet/sing-box/cmd/internal/build_shared"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"

View File

@ -5,10 +5,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -38,6 +38,10 @@ func format() error {
return err return err
} }
for _, optionsEntry := range optionsList { for _, optionsEntry := range optionsList {
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
if err != nil {
return err
}
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer) encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ") encoder.SetIndent("", " ")
@ -69,41 +73,3 @@ func format() error {
} }
return nil return nil
} }
func formatOne(configPath string) error {
configContent, err := os.ReadFile(configPath)
if err != nil {
return E.Cause(err, "read config")
}
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return E.Cause(err, "decode config")
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
return E.Cause(err, "encode config")
}
if !commandFormatFlagWrite {
os.Stdout.WriteString(buffer.String() + "\n")
return nil
}
if bytes.Equal(configContent, buffer.Bytes()) {
return nil
}
output, err := os.Create(configPath)
if err != nil {
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
return E.Cause(err, "write output")
}
outputPath, _ := filepath.Abs(configPath)
os.Stderr.WriteString(outputPath + "\n")
return nil
}

43
cmd/sing-box/cmd_geoip.go Normal file
View File

@ -0,0 +1,43 @@
package main
import (
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/oschwald/maxminddb-golang"
"github.com/spf13/cobra"
)
var (
geoipReader *maxminddb.Reader
commandGeoIPFlagFile string
)
var commandGeoip = &cobra.Command{
Use: "geoip",
Short: "GeoIP tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geoipPreRun()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.PersistentFlags().StringVarP(&commandGeoIPFlagFile, "file", "f", "geoip.db", "geoip file")
mainCommand.AddCommand(commandGeoip)
}
func geoipPreRun() error {
reader, err := maxminddb.Open(commandGeoIPFlagFile)
if err != nil {
return err
}
if reader.Metadata.DatabaseType != "sing-geoip" {
reader.Close()
return E.New("incorrect database type, expected sing-geoip, got ", reader.Metadata.DatabaseType)
}
geoipReader = reader
return nil
}

View File

@ -0,0 +1,98 @@
package main
import (
"io"
"net"
"os"
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/oschwald/maxminddb-golang"
"github.com/spf13/cobra"
)
var flagGeoipExportOutput string
const flagGeoipExportDefaultOutput = "geoip-<country>.srs"
var commandGeoipExport = &cobra.Command{
Use: "export <country>",
Short: "Export geoip country as rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geoipExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoipExport.Flags().StringVarP(&flagGeoipExportOutput, "output", "o", flagGeoipExportDefaultOutput, "Output path")
commandGeoip.AddCommand(commandGeoipExport)
}
func geoipExport(countryCode string) error {
networks := geoipReader.Networks(maxminddb.SkipAliasedNetworks)
countryMap := make(map[string][]*net.IPNet)
var (
ipNet *net.IPNet
nextCountryCode string
err error
)
for networks.Next() {
ipNet, err = networks.Network(&nextCountryCode)
if err != nil {
return err
}
countryMap[nextCountryCode] = append(countryMap[nextCountryCode], ipNet)
}
ipNets := countryMap[strings.ToLower(countryCode)]
if len(ipNets) == 0 {
return E.New("country code not found: ", countryCode)
}
var (
outputFile *os.File
outputWriter io.Writer
)
if flagGeoipExportOutput == "stdout" {
outputWriter = os.Stdout
} else if flagGeoipExportOutput == flagGeoipExportDefaultOutput {
outputFile, err = os.Create("geoip-" + countryCode + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(flagGeoipExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}
encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
headlessRule.IPCIDR = make([]string, 0, len(ipNets))
for _, cidr := range ipNets {
headlessRule.IPCIDR = append(headlessRule.IPCIDR, cidr.String())
}
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion1
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}

View File

@ -0,0 +1,31 @@
package main
import (
"os"
"github.com/sagernet/sing-box/log"
"github.com/spf13/cobra"
)
var commandGeoipList = &cobra.Command{
Use: "list",
Short: "List geoip country codes",
Run: func(cmd *cobra.Command, args []string) {
err := listGeoip()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.AddCommand(commandGeoipList)
}
func listGeoip() error {
for _, code := range geoipReader.Metadata.Languages {
os.Stdout.WriteString(code + "\n")
}
return nil
}

View File

@ -0,0 +1,47 @@
package main
import (
"net/netip"
"os"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/spf13/cobra"
)
var commandGeoipLookup = &cobra.Command{
Use: "lookup <address>",
Short: "Lookup if an IP address is contained in the GeoIP database",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geoipLookup(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoip.AddCommand(commandGeoipLookup)
}
func geoipLookup(address string) error {
addr, err := netip.ParseAddr(address)
if err != nil {
return E.Cause(err, "parse address")
}
if !N.IsPublicAddr(addr) {
os.Stdout.WriteString("private\n")
return nil
}
var code string
_ = geoipReader.Lookup(addr.AsSlice(), &code)
if code != "" {
os.Stdout.WriteString(code + "\n")
return nil
}
os.Stdout.WriteString("unknown\n")
return nil
}

View File

@ -0,0 +1,41 @@
package main
import (
"github.com/sagernet/sing-box/common/geosite"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var (
commandGeoSiteFlagFile string
geositeReader *geosite.Reader
geositeCodeList []string
)
var commandGeoSite = &cobra.Command{
Use: "geosite",
Short: "Geosite tools",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := geositePreRun()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.PersistentFlags().StringVarP(&commandGeoSiteFlagFile, "file", "f", "geosite.db", "geosite file")
mainCommand.AddCommand(commandGeoSite)
}
func geositePreRun() error {
reader, codeList, err := geosite.Open(commandGeoSiteFlagFile)
if err != nil {
return E.Cause(err, "open geosite file")
}
geositeReader = reader
geositeCodeList = codeList
return nil
}

View File

@ -0,0 +1,81 @@
package main
import (
"encoding/json"
"io"
"os"
"github.com/sagernet/sing-box/common/geosite"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/spf13/cobra"
)
var commandGeositeExportOutput string
const commandGeositeExportDefaultOutput = "geosite-<category>.json"
var commandGeositeExport = &cobra.Command{
Use: "export <category>",
Short: "Export geosite category as rule-set",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := geositeExport(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeositeExport.Flags().StringVarP(&commandGeositeExportOutput, "output", "o", commandGeositeExportDefaultOutput, "Output path")
commandGeoSite.AddCommand(commandGeositeExport)
}
func geositeExport(category string) error {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
var (
outputFile *os.File
outputWriter io.Writer
)
if commandGeositeExportOutput == "stdout" {
outputWriter = os.Stdout
} else if commandGeositeExportOutput == commandGeositeExportDefaultOutput {
outputFile, err = os.Create("geosite-" + category + ".json")
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
} else {
outputFile, err = os.Create(commandGeositeExportOutput)
if err != nil {
return err
}
defer outputFile.Close()
outputWriter = outputFile
}
encoder := json.NewEncoder(outputWriter)
encoder.SetIndent("", " ")
var headlessRule option.DefaultHeadlessRule
defaultRule := geosite.Compile(sourceSet)
headlessRule.Domain = defaultRule.Domain
headlessRule.DomainSuffix = defaultRule.DomainSuffix
headlessRule.DomainKeyword = defaultRule.DomainKeyword
headlessRule.DomainRegex = defaultRule.DomainRegex
var plainRuleSet option.PlainRuleSetCompat
plainRuleSet.Version = C.RuleSetVersion1
plainRuleSet.Options.Rules = []option.HeadlessRule{
{
Type: C.RuleTypeDefault,
DefaultOptions: headlessRule,
},
}
return encoder.Encode(plainRuleSet)
}

View File

@ -0,0 +1,50 @@
package main
import (
"os"
"sort"
"github.com/sagernet/sing-box/log"
F "github.com/sagernet/sing/common/format"
"github.com/spf13/cobra"
)
var commandGeositeList = &cobra.Command{
Use: "list <category>",
Short: "List geosite categories",
Run: func(cmd *cobra.Command, args []string) {
err := geositeList()
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.AddCommand(commandGeositeList)
}
func geositeList() error {
var geositeEntry []struct {
category string
items int
}
for _, category := range geositeCodeList {
sourceSet, err := geositeReader.Read(category)
if err != nil {
return err
}
geositeEntry = append(geositeEntry, struct {
category string
items int
}{category, len(sourceSet)})
}
sort.SliceStable(geositeEntry, func(i, j int) bool {
return geositeEntry[i].items < geositeEntry[j].items
})
for _, entry := range geositeEntry {
os.Stdout.WriteString(F.ToString(entry.category, " (", entry.items, ")\n"))
}
return nil
}

View File

@ -0,0 +1,97 @@
package main
import (
"os"
"sort"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/spf13/cobra"
)
var commandGeositeLookup = &cobra.Command{
Use: "lookup [category] <domain>",
Short: "Check if a domain is in the geosite",
Args: cobra.RangeArgs(1, 2),
Run: func(cmd *cobra.Command, args []string) {
var (
source string
target string
)
switch len(args) {
case 1:
target = args[0]
case 2:
source = args[0]
target = args[1]
}
err := geositeLookup(source, target)
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandGeoSite.AddCommand(commandGeositeLookup)
}
func geositeLookup(source string, target string) error {
var sourceMatcherList []struct {
code string
matcher *searchGeositeMatcher
}
if source != "" {
sourceSet, err := geositeReader.Read(source)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+source)
}
sourceMatcherList = []struct {
code string
matcher *searchGeositeMatcher
}{
{
code: source,
matcher: sourceMatcher,
},
}
} else {
for _, code := range geositeCodeList {
sourceSet, err := geositeReader.Read(code)
if err != nil {
return err
}
sourceMatcher, err := newSearchGeositeMatcher(sourceSet)
if err != nil {
return E.Cause(err, "compile code: "+code)
}
sourceMatcherList = append(sourceMatcherList, struct {
code string
matcher *searchGeositeMatcher
}{
code: code,
matcher: sourceMatcher,
})
}
}
sort.SliceStable(sourceMatcherList, func(i, j int) bool {
return sourceMatcherList[i].code < sourceMatcherList[j].code
})
for _, matcherItem := range sourceMatcherList {
if matchRule := matcherItem.matcher.Match(target); matchRule != "" {
os.Stdout.WriteString("Match code (")
os.Stdout.WriteString(matcherItem.code)
os.Stdout.WriteString(") ")
os.Stdout.WriteString(matchRule)
os.Stdout.WriteString("\n")
}
}
return nil
}

View File

@ -0,0 +1,56 @@
package main
import (
"regexp"
"strings"
"github.com/sagernet/sing-box/common/geosite"
)
type searchGeositeMatcher struct {
domainMap map[string]bool
suffixList []string
keywordList []string
regexList []string
}
func newSearchGeositeMatcher(items []geosite.Item) (*searchGeositeMatcher, error) {
options := geosite.Compile(items)
domainMap := make(map[string]bool)
for _, domain := range options.Domain {
domainMap[domain] = true
}
rule := &searchGeositeMatcher{
domainMap: domainMap,
suffixList: options.DomainSuffix,
keywordList: options.DomainKeyword,
regexList: options.DomainRegex,
}
return rule, nil
}
func (r *searchGeositeMatcher) Match(domain string) string {
if r.domainMap[domain] {
return "domain=" + domain
}
for _, suffix := range r.suffixList {
if strings.HasSuffix(domain, suffix) {
return "domain_suffix=" + suffix
}
}
for _, keyword := range r.keywordList {
if strings.Contains(domain, keyword) {
return "domain_keyword=" + keyword
}
}
for _, regexStr := range r.regexList {
regex, err := regexp.Compile(regexStr)
if err != nil {
continue
}
if regex.MatchString(domain) {
return "domain_regex=" + regexStr
}
}
return ""
}

View File

@ -6,19 +6,19 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/sagernet/sing-box/common/json"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/rw" "github.com/sagernet/sing/common/rw"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var commandMerge = &cobra.Command{ var commandMerge = &cobra.Command{
Use: "merge [output]", Use: "merge <output>",
Short: "Merge configurations", Short: "Merge configurations",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
err := merge(args[0]) err := merge(args[0])
@ -65,50 +65,26 @@ func merge(outputPath string) error {
func mergePathResources(options *option.Options) error { func mergePathResources(options *option.Options) error {
for index, inbound := range options.Inbounds { for index, inbound := range options.Inbounds {
switch inbound.Type { rawOptions, err := inbound.RawOptions()
case C.TypeHTTP: if err != nil {
inbound.HTTPOptions.TLS = mergeTLSInboundOptions(inbound.HTTPOptions.TLS) return err
case C.TypeMixed: }
inbound.MixedOptions.TLS = mergeTLSInboundOptions(inbound.MixedOptions.TLS) if tlsOptions, containsTLSOptions := rawOptions.(option.InboundTLSOptionsWrapper); containsTLSOptions {
case C.TypeVMess: tlsOptions.ReplaceInboundTLSOptions(mergeTLSInboundOptions(tlsOptions.TakeInboundTLSOptions()))
inbound.VMessOptions.TLS = mergeTLSInboundOptions(inbound.VMessOptions.TLS)
case C.TypeTrojan:
inbound.TrojanOptions.TLS = mergeTLSInboundOptions(inbound.TrojanOptions.TLS)
case C.TypeNaive:
inbound.NaiveOptions.TLS = mergeTLSInboundOptions(inbound.NaiveOptions.TLS)
case C.TypeHysteria:
inbound.HysteriaOptions.TLS = mergeTLSInboundOptions(inbound.HysteriaOptions.TLS)
case C.TypeVLESS:
inbound.VLESSOptions.TLS = mergeTLSInboundOptions(inbound.VLESSOptions.TLS)
case C.TypeTUIC:
inbound.TUICOptions.TLS = mergeTLSInboundOptions(inbound.TUICOptions.TLS)
case C.TypeHysteria2:
inbound.Hysteria2Options.TLS = mergeTLSInboundOptions(inbound.Hysteria2Options.TLS)
default:
continue
} }
options.Inbounds[index] = inbound options.Inbounds[index] = inbound
} }
for index, 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.TypeHTTP:
outbound.HTTPOptions.TLS = mergeTLSOutboundOptions(outbound.HTTPOptions.TLS)
case C.TypeVMess:
outbound.VMessOptions.TLS = mergeTLSOutboundOptions(outbound.VMessOptions.TLS)
case C.TypeTrojan:
outbound.TrojanOptions.TLS = mergeTLSOutboundOptions(outbound.TrojanOptions.TLS)
case C.TypeHysteria:
outbound.HysteriaOptions.TLS = mergeTLSOutboundOptions(outbound.HysteriaOptions.TLS)
case C.TypeSSH: case C.TypeSSH:
outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions) outbound.SSHOptions = mergeSSHOutboundOptions(outbound.SSHOptions)
case C.TypeVLESS: }
outbound.VLESSOptions.TLS = mergeTLSOutboundOptions(outbound.VLESSOptions.TLS) if tlsOptions, containsTLSOptions := rawOptions.(option.OutboundTLSOptionsWrapper); containsTLSOptions {
case C.TypeTUIC: tlsOptions.ReplaceOutboundTLSOptions(mergeTLSOutboundOptions(tlsOptions.TakeOutboundTLSOptions()))
outbound.TUICOptions.TLS = mergeTLSOutboundOptions(outbound.TUICOptions.TLS)
case C.TypeHysteria2:
outbound.Hysteria2Options.TLS = mergeTLSOutboundOptions(outbound.Hysteria2Options.TLS)
default:
continue
} }
options.Outbounds[index] = outbound options.Outbounds[index] = outbound
} }

View File

@ -0,0 +1,14 @@
package main
import (
"github.com/spf13/cobra"
)
var commandRuleSet = &cobra.Command{
Use: "rule-set",
Short: "Manage rule sets",
}
func init() {
mainCommand.AddCommand(commandRuleSet)
}

View File

@ -0,0 +1,80 @@
package main
import (
"io"
"os"
"strings"
"github.com/sagernet/sing-box/common/srs"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var flagRuleSetCompileOutput string
const flagRuleSetCompileDefaultOutput = "<file_name>.srs"
var commandRuleSetCompile = &cobra.Command{
Use: "compile [source-path]",
Short: "Compile rule-set json to binary",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := compileRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSet.AddCommand(commandRuleSetCompile)
commandRuleSetCompile.Flags().StringVarP(&flagRuleSetCompileOutput, "output", "o", flagRuleSetCompileDefaultOutput, "Output file")
}
func compileRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
decoder := json.NewDecoder(json.NewCommentFilter(reader))
decoder.DisallowUnknownFields()
var plainRuleSet option.PlainRuleSetCompat
err = decoder.Decode(&plainRuleSet)
if err != nil {
return err
}
ruleSet := plainRuleSet.Upgrade()
var outputPath string
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".json") {
outputPath = sourcePath[:len(sourcePath)-5] + ".srs"
} else {
outputPath = sourcePath + ".srs"
}
} else {
outputPath = flagRuleSetCompileOutput
}
outputFile, err := os.Create(outputPath)
if err != nil {
return err
}
err = srs.Write(outputFile, ruleSet)
if err != nil {
outputFile.Close()
os.Remove(outputPath)
return err
}
outputFile.Close()
return nil
}

View File

@ -0,0 +1,87 @@
package main
import (
"bytes"
"io"
"os"
"path/filepath"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
)
var commandRuleSetFormatFlagWrite bool
var commandRuleSetFormat = &cobra.Command{
Use: "format <source-path>",
Short: "Format rule-set json",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
err := formatRuleSet(args[0])
if err != nil {
log.Fatal(err)
}
},
}
func init() {
commandRuleSetFormat.Flags().BoolVarP(&commandRuleSetFormatFlagWrite, "write", "w", false, "write result to (source) file instead of stdout")
commandRuleSet.AddCommand(commandRuleSetFormat)
}
func formatRuleSet(sourcePath string) error {
var (
reader io.Reader
err error
)
if sourcePath == "stdin" {
reader = os.Stdin
} else {
reader, err = os.Open(sourcePath)
if err != nil {
return err
}
}
content, err := io.ReadAll(reader)
if err != nil {
return err
}
decoder := json.NewDecoder(json.NewCommentFilter(bytes.NewReader(content)))
decoder.DisallowUnknownFields()
var plainRuleSet option.PlainRuleSetCompat
err = decoder.Decode(&plainRuleSet)
if err != nil {
return err
}
ruleSet := plainRuleSet.Upgrade()
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(ruleSet)
if err != nil {
return E.Cause(err, "encode config")
}
outputPath, _ := filepath.Abs(sourcePath)
if !commandRuleSetFormatFlagWrite || sourcePath == "stdin" {
os.Stdout.WriteString(buffer.String() + "\n")
return nil
}
if bytes.Equal(content, buffer.Bytes()) {
return nil
}
output, err := os.Create(sourcePath)
if err != nil {
return E.Cause(err, "open output")
}
_, err = output.Write(buffer.Bytes())
output.Close()
if err != nil {
return E.Cause(err, "write output")
}
os.Stderr.WriteString(outputPath + "\n")
return nil
}

View File

@ -13,10 +13,12 @@ import (
"time" "time"
"github.com/sagernet/sing-box" "github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/common/badjsonmerge" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -55,8 +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)
} }
var options option.Options options, err := json.UnmarshalExtended[option.Options](configContent)
err = options.UnmarshalJSON(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)
} }
@ -106,13 +107,18 @@ func readConfigAndMerge() (option.Options, error) {
if len(optionsList) == 1 { if len(optionsList) == 1 {
return optionsList[0].options, nil return optionsList[0].options, nil
} }
var mergedOptions option.Options var mergedMessage json.RawMessage
for _, options := range optionsList { for _, options := range optionsList {
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions) mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage)
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
err = mergedOptions.UnmarshalJSON(mergedMessage)
if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged config")
}
return mergedOptions, nil return mergedOptions, nil
} }
@ -127,7 +133,7 @@ func create() (*box.Box, context.CancelFunc, error) {
} }
options.Log.DisableColor = true options.Log.DisableColor = true
} }
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(globalCtx)
instance, err := box.New(box.Options{ instance, err := box.New(box.Options{
Context: ctx, Context: ctx,
Options: options, Options: options,
@ -193,7 +199,7 @@ func run() error {
} }
func closeMonitor(ctx context.Context) { func closeMonitor(ctx context.Context) {
time.Sleep(3 * time.Second) time.Sleep(C.DefaultStopFatalTimeout)
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return

View File

@ -38,11 +38,7 @@ func createPreStartedClient() (*box.Box, error) {
func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) { func createDialer(instance *box.Box, network string, outboundTag string) (N.Dialer, error) {
if outboundTag == "" { if outboundTag == "" {
outbound := instance.Router().DefaultOutbound(N.NetworkName(network)) return instance.Router().DefaultOutbound(N.NetworkName(network))
if outbound == nil {
return nil, E.New("missing default outbound")
}
return outbound, nil
} else { } else {
outbound, loaded := instance.Router().Outbound(outboundTag) outbound, loaded := instance.Router().Outbound(outboundTag)
if !loaded { if !loaded {

View File

@ -18,7 +18,7 @@ import (
var commandConnectFlagNetwork string var commandConnectFlagNetwork string
var commandConnect = &cobra.Command{ var commandConnect = &cobra.Command{
Use: "connect [address]", Use: "connect <address>",
Short: "Connect to an address", Short: "Connect to an address",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {

View File

@ -1,16 +1,21 @@
package main package main
import ( import (
"context"
"os" "os"
"os/user"
"strconv"
"time" "time"
_ "github.com/sagernet/sing-box/include" _ "github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/service/filemanager"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ( var (
globalCtx context.Context
configPaths []string configPaths []string
configDirectories []string configDirectories []string
workingDir string workingDir string
@ -36,15 +41,30 @@ func main() {
} }
func preRun(cmd *cobra.Command, args []string) { func preRun(cmd *cobra.Command, args []string) {
globalCtx = context.Background()
sudoUser := os.Getenv("SUDO_USER")
sudoUID, _ := strconv.Atoi(os.Getenv("SUDO_UID"))
sudoGID, _ := strconv.Atoi(os.Getenv("SUDO_GID"))
if sudoUID == 0 && sudoGID == 0 && sudoUser != "" {
sudoUserObject, _ := user.Lookup(sudoUser)
if sudoUserObject != nil {
sudoUID, _ = strconv.Atoi(sudoUserObject.Uid)
sudoGID, _ = strconv.Atoi(sudoUserObject.Gid)
}
}
if sudoUID > 0 && sudoGID > 0 {
globalCtx = filemanager.WithDefault(globalCtx, "", "", sudoUID, sudoGID)
}
if disableColor { if disableColor {
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger()) log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
} }
if workingDir != "" { if workingDir != "" {
_, err := os.Stat(workingDir) _, err := os.Stat(workingDir)
if err != nil { if err != nil {
os.MkdirAll(workingDir, 0o777) filemanager.MkdirAll(globalCtx, workingDir, 0o777)
} }
if err := os.Chdir(workingDir); err != nil { err = os.Chdir(workingDir)
if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -1,46 +0,0 @@
package badjson
import (
"bytes"
"github.com/sagernet/sing-box/common/json"
E "github.com/sagernet/sing/common/exceptions"
)
type JSONArray []any
func (a JSONArray) MarshalJSON() ([]byte, error) {
return json.Marshal([]any(a))
}
func (a *JSONArray) UnmarshalJSON(content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content))
arrayStart, err := decoder.Token()
if err != nil {
return err
} else if arrayStart != json.Delim('[') {
return E.New("excepted array start, but got ", arrayStart)
}
err = a.decodeJSON(decoder)
if err != nil {
return err
}
arrayEnd, err := decoder.Token()
if err != nil {
return err
} else if arrayEnd != json.Delim(']') {
return E.New("excepted array end, but got ", arrayEnd)
}
return nil
}
func (a *JSONArray) decodeJSON(decoder *json.Decoder) error {
for decoder.More() {
item, err := decodeJSON(decoder)
if err != nil {
return err
}
*a = append(*a, item)
}
return nil
}

View File

@ -1,54 +0,0 @@
package badjson
import (
"bytes"
"github.com/sagernet/sing-box/common/json"
E "github.com/sagernet/sing/common/exceptions"
)
func Decode(content []byte) (any, error) {
decoder := json.NewDecoder(bytes.NewReader(content))
return decodeJSON(decoder)
}
func decodeJSON(decoder *json.Decoder) (any, error) {
rawToken, err := decoder.Token()
if err != nil {
return nil, err
}
switch token := rawToken.(type) {
case json.Delim:
switch token {
case '{':
var object JSONObject
err = object.decodeJSON(decoder)
if err != nil {
return nil, err
}
rawToken, err = decoder.Token()
if err != nil {
return nil, err
} else if rawToken != json.Delim('}') {
return nil, E.New("excepted object end, but got ", rawToken)
}
return &object, nil
case '[':
var array JSONArray
err = array.decodeJSON(decoder)
if err != nil {
return nil, err
}
rawToken, err = decoder.Token()
if err != nil {
return nil, err
} else if rawToken != json.Delim(']') {
return nil, E.New("excepted array end, but got ", rawToken)
}
return array, nil
default:
return nil, E.New("excepted object or array end: ", token)
}
}
return rawToken, nil
}

View File

@ -1,79 +0,0 @@
package badjson
import (
"bytes"
"strings"
"github.com/sagernet/sing-box/common/json"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/x/linkedhashmap"
)
type JSONObject struct {
linkedhashmap.Map[string, any]
}
func (m JSONObject) MarshalJSON() ([]byte, error) {
buffer := new(bytes.Buffer)
buffer.WriteString("{")
items := m.Entries()
iLen := len(items)
for i, entry := range items {
keyContent, err := json.Marshal(entry.Key)
if err != nil {
return nil, err
}
buffer.WriteString(strings.TrimSpace(string(keyContent)))
buffer.WriteString(": ")
valueContent, err := json.Marshal(entry.Value)
if err != nil {
return nil, err
}
buffer.WriteString(strings.TrimSpace(string(valueContent)))
if i < iLen-1 {
buffer.WriteString(", ")
}
}
buffer.WriteString("}")
return buffer.Bytes(), nil
}
func (m *JSONObject) UnmarshalJSON(content []byte) error {
decoder := json.NewDecoder(bytes.NewReader(content))
m.Clear()
objectStart, err := decoder.Token()
if err != nil {
return err
} else if objectStart != json.Delim('{') {
return E.New("expected json object start, but starts with ", objectStart)
}
err = m.decodeJSON(decoder)
if err != nil {
return E.Cause(err, "decode json object content")
}
objectEnd, err := decoder.Token()
if err != nil {
return err
} else if objectEnd != json.Delim('}') {
return E.New("expected json object end, but ends with ", objectEnd)
}
return nil
}
func (m *JSONObject) decodeJSON(decoder *json.Decoder) error {
for decoder.More() {
var entryKey string
keyToken, err := decoder.Token()
if err != nil {
return err
}
entryKey = keyToken.(string)
var entryValue any
entryValue, err = decodeJSON(decoder)
if err != nil {
return E.Cause(err, "decode value for ", entryKey)
}
m.Put(entryKey, entryValue)
}
return nil
}

View File

@ -1,80 +0,0 @@
package badjsonmerge
import (
"encoding/json"
"reflect"
"github.com/sagernet/sing-box/common/badjson"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
)
func MergeOptions(source option.Options, destination option.Options) (option.Options, error) {
rawSource, err := json.Marshal(source)
if err != nil {
return option.Options{}, E.Cause(err, "marshal source")
}
rawDestination, err := json.Marshal(destination)
if err != nil {
return option.Options{}, E.Cause(err, "marshal destination")
}
rawMerged, err := MergeJSON(rawSource, rawDestination)
if err != nil {
return option.Options{}, E.Cause(err, "merge options")
}
var merged option.Options
err = json.Unmarshal(rawMerged, &merged)
if err != nil {
return option.Options{}, E.Cause(err, "unmarshal merged options")
}
return merged, nil
}
func MergeJSON(rawSource json.RawMessage, rawDestination json.RawMessage) (json.RawMessage, error) {
source, err := badjson.Decode(rawSource)
if err != nil {
return nil, E.Cause(err, "decode source")
}
destination, err := badjson.Decode(rawDestination)
if err != nil {
return nil, E.Cause(err, "decode destination")
}
merged, err := mergeJSON(source, destination)
if err != nil {
return nil, err
}
return json.Marshal(merged)
}
func mergeJSON(anySource any, anyDestination any) (any, error) {
switch destination := anyDestination.(type) {
case badjson.JSONArray:
switch source := anySource.(type) {
case badjson.JSONArray:
destination = append(destination, source...)
default:
destination = append(destination, source)
}
return destination, nil
case *badjson.JSONObject:
switch source := anySource.(type) {
case *badjson.JSONObject:
for _, entry := range source.Entries() {
oldValue, loaded := destination.Get(entry.Key)
if loaded {
var err error
entry.Value, err = mergeJSON(entry.Value, oldValue)
if err != nil {
return nil, E.Cause(err, "merge object item ", entry.Key)
}
}
destination.Put(entry.Key, entry.Value)
}
default:
return nil, E.New("cannot merge json object into ", reflect.TypeOf(destination))
}
return destination, nil
default:
return destination, nil
}
}

View File

@ -1,59 +0,0 @@
package badjsonmerge
import (
"testing"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
N "github.com/sagernet/sing/common/network"
"github.com/stretchr/testify/require"
)
func TestMergeJSON(t *testing.T) {
t.Parallel()
options := option.Options{
Log: &option.LogOptions{
Level: "info",
},
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Network: []string{N.NetworkTCP},
Outbound: "direct",
},
},
},
},
}
anotherOptions := option.Options{
Outbounds: []option.Outbound{
{
Type: C.TypeDirect,
Tag: "direct",
},
},
}
thirdOptions := option.Options{
Route: &option.RouteOptions{
Rules: []option.Rule{
{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Network: []string{N.NetworkUDP},
Outbound: "direct",
},
},
},
},
}
mergeOptions, err := MergeOptions(options, anotherOptions)
require.NoError(t, err)
mergeOptions, err = MergeOptions(thirdOptions, mergeOptions)
require.NoError(t, err)
require.Equal(t, "info", mergeOptions.Log.Level)
require.Equal(t, 2, len(mergeOptions.Route.Rules))
require.Equal(t, C.TypeDirect, mergeOptions.Outbounds[0].Type)
}

View File

@ -1,233 +0,0 @@
//go:build go1.20 && !go1.21
package badtls
import (
"crypto/cipher"
"crypto/rand"
"crypto/tls"
"encoding/binary"
"io"
"net"
"reflect"
"sync"
"sync/atomic"
"unsafe"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
aTLS "github.com/sagernet/sing/common/tls"
)
type Conn struct {
*tls.Conn
writer N.ExtendedWriter
isHandshakeComplete *atomic.Bool
activeCall *atomic.Int32
closeNotifySent *bool
version *uint16
rand io.Reader
halfAccess *sync.Mutex
halfError *error
cipher cipher.AEAD
explicitNonceLen int
halfPtr uintptr
halfSeq []byte
halfScratchBuf []byte
}
func TryCreate(conn aTLS.Conn) aTLS.Conn {
tlsConn, ok := conn.(*tls.Conn)
if !ok {
return conn
}
badConn, err := Create(tlsConn)
if err != nil {
log.Warn("initialize badtls: ", err)
return conn
}
return badConn
}
func Create(conn *tls.Conn) (aTLS.Conn, error) {
rawConn := reflect.Indirect(reflect.ValueOf(conn))
rawIsHandshakeComplete := rawConn.FieldByName("isHandshakeComplete")
if !rawIsHandshakeComplete.IsValid() || rawIsHandshakeComplete.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid isHandshakeComplete")
}
isHandshakeComplete := (*atomic.Bool)(unsafe.Pointer(rawIsHandshakeComplete.UnsafeAddr()))
if !isHandshakeComplete.Load() {
return nil, E.New("handshake not finished")
}
rawActiveCall := rawConn.FieldByName("activeCall")
if !rawActiveCall.IsValid() || rawActiveCall.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid active call")
}
activeCall := (*atomic.Int32)(unsafe.Pointer(rawActiveCall.UnsafeAddr()))
rawHalfConn := rawConn.FieldByName("out")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn")
}
rawVersion := rawConn.FieldByName("vers")
if !rawVersion.IsValid() || rawVersion.Kind() != reflect.Uint16 {
return nil, E.New("badtls: invalid version")
}
version := (*uint16)(unsafe.Pointer(rawVersion.UnsafeAddr()))
rawCloseNotifySent := rawConn.FieldByName("closeNotifySent")
if !rawCloseNotifySent.IsValid() || rawCloseNotifySent.Kind() != reflect.Bool {
return nil, E.New("badtls: invalid notify")
}
closeNotifySent := (*bool)(unsafe.Pointer(rawCloseNotifySent.UnsafeAddr()))
rawConfig := reflect.Indirect(rawConn.FieldByName("config"))
if !rawConfig.IsValid() || rawConfig.Kind() != reflect.Struct {
return nil, E.New("badtls: bad config")
}
config := (*tls.Config)(unsafe.Pointer(rawConfig.UnsafeAddr()))
randReader := config.Rand
if randReader == nil {
randReader = rand.Reader
}
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half mutex")
}
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
rawHalfError := rawHalfConn.FieldByName("err")
if !rawHalfError.IsValid() || rawHalfError.Kind() != reflect.Interface {
return nil, E.New("badtls: invalid half error")
}
halfError := (*error)(unsafe.Pointer(rawHalfError.UnsafeAddr()))
rawHalfCipherInterface := rawHalfConn.FieldByName("cipher")
if !rawHalfCipherInterface.IsValid() || rawHalfCipherInterface.Kind() != reflect.Interface {
return nil, E.New("badtls: invalid cipher interface")
}
rawHalfCipher := rawHalfCipherInterface.Elem()
aeadCipher, loaded := valueInterface(rawHalfCipher, false).(cipher.AEAD)
if !loaded {
return nil, E.New("badtls: invalid AEAD cipher")
}
var explicitNonceLen int
switch cipherName := reflect.Indirect(rawHalfCipher).Type().String(); cipherName {
case "tls.prefixNonceAEAD":
explicitNonceLen = aeadCipher.NonceSize()
case "tls.xorNonceAEAD":
default:
return nil, E.New("badtls: unknown cipher type: ", cipherName)
}
rawHalfSeq := rawHalfConn.FieldByName("seq")
if !rawHalfSeq.IsValid() || rawHalfSeq.Kind() != reflect.Array {
return nil, E.New("badtls: invalid seq")
}
halfSeq := rawHalfSeq.Bytes()
rawHalfScratchBuf := rawHalfConn.FieldByName("scratchBuf")
if !rawHalfScratchBuf.IsValid() || rawHalfScratchBuf.Kind() != reflect.Array {
return nil, E.New("badtls: invalid scratchBuf")
}
halfScratchBuf := rawHalfScratchBuf.Bytes()
return &Conn{
Conn: conn,
writer: bufio.NewExtendedWriter(conn.NetConn()),
isHandshakeComplete: isHandshakeComplete,
activeCall: activeCall,
closeNotifySent: closeNotifySent,
version: version,
halfAccess: halfAccess,
halfError: halfError,
cipher: aeadCipher,
explicitNonceLen: explicitNonceLen,
rand: randReader,
halfPtr: rawHalfConn.UnsafeAddr(),
halfSeq: halfSeq,
halfScratchBuf: halfScratchBuf,
}, nil
}
func (c *Conn) WriteBuffer(buffer *buf.Buffer) error {
if buffer.Len() > maxPlaintext {
defer buffer.Release()
return common.Error(c.Write(buffer.Bytes()))
}
for {
x := c.activeCall.Load()
if x&1 != 0 {
return net.ErrClosed
}
if c.activeCall.CompareAndSwap(x, x+2) {
break
}
}
defer c.activeCall.Add(-2)
c.halfAccess.Lock()
defer c.halfAccess.Unlock()
if err := *c.halfError; err != nil {
return err
}
if *c.closeNotifySent {
return errShutdown
}
dataLen := buffer.Len()
dataBytes := buffer.Bytes()
outBuf := buffer.ExtendHeader(recordHeaderLen + c.explicitNonceLen)
outBuf[0] = 23
version := *c.version
if version == 0 {
version = tls.VersionTLS10
} else if version == tls.VersionTLS13 {
version = tls.VersionTLS12
}
binary.BigEndian.PutUint16(outBuf[1:], version)
var nonce []byte
if c.explicitNonceLen > 0 {
nonce = outBuf[5 : 5+c.explicitNonceLen]
if c.explicitNonceLen < 16 {
copy(nonce, c.halfSeq)
} else {
if _, err := io.ReadFull(c.rand, nonce); err != nil {
return err
}
}
}
if len(nonce) == 0 {
nonce = c.halfSeq
}
if *c.version == tls.VersionTLS13 {
buffer.FreeBytes()[0] = 23
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+1+c.cipher.Overhead()))
c.cipher.Seal(outBuf, nonce, outBuf[recordHeaderLen:recordHeaderLen+c.explicitNonceLen+dataLen+1], outBuf[:recordHeaderLen])
buffer.Extend(1 + c.cipher.Overhead())
} else {
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen))
additionalData := append(c.halfScratchBuf[:0], c.halfSeq...)
additionalData = append(additionalData, outBuf[:recordHeaderLen]...)
c.cipher.Seal(outBuf, nonce, dataBytes, additionalData)
buffer.Extend(c.cipher.Overhead())
binary.BigEndian.PutUint16(outBuf[3:], uint16(dataLen+c.explicitNonceLen+c.cipher.Overhead()))
}
incSeq(c.halfPtr)
log.Trace("badtls write ", buffer.Len())
return c.writer.WriteBuffer(buffer)
}
func (c *Conn) FrontHeadroom() int {
return recordHeaderLen + c.explicitNonceLen
}
func (c *Conn) RearHeadroom() int {
return 1 + c.cipher.Overhead()
}
func (c *Conn) WriterMTU() int {
return maxPlaintext
}
func (c *Conn) Upstream() any {
return c.Conn
}
func (c *Conn) UpstreamWriter() any {
return c.NetConn()
}

View File

@ -1,14 +0,0 @@
//go:build !go1.19 || go1.21
package badtls
import (
"crypto/tls"
"os"
aTLS "github.com/sagernet/sing/common/tls"
)
func Create(conn *tls.Conn) (aTLS.Conn, error) {
return nil, os.ErrInvalid
}

View File

@ -1,22 +0,0 @@
//go:build go1.20 && !go.1.21
package badtls
import (
"reflect"
_ "unsafe"
)
const (
maxPlaintext = 16384 // maximum plaintext payload length
recordHeaderLen = 5 // record header length
)
//go:linkname errShutdown crypto/tls.errShutdown
var errShutdown error
//go:linkname incSeq crypto/tls.(*halfConn).incSeq
func incSeq(conn uintptr)
//go:linkname valueInterface reflect.valueInterface
func valueInterface(v reflect.Value, safe bool) any

115
common/badtls/read_wait.go Normal file
View File

@ -0,0 +1,115 @@
//go:build go1.21 && !without_badtls
package badtls
import (
"bytes"
"os"
"reflect"
"sync"
"unsafe"
"github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions"
N "github.com/sagernet/sing/common/network"
"github.com/sagernet/sing/common/tls"
)
var _ N.ReadWaiter = (*ReadWaitConn)(nil)
type ReadWaitConn struct {
*tls.STDConn
halfAccess *sync.Mutex
rawInput *bytes.Buffer
input *bytes.Reader
hand *bytes.Buffer
readWaitOptions N.ReadWaitOptions
}
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
stdConn, isSTDConn := conn.(*tls.STDConn)
if !isSTDConn {
return nil, os.ErrInvalid
}
rawConn := reflect.Indirect(reflect.ValueOf(stdConn))
rawHalfConn := rawConn.FieldByName("in")
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half conn")
}
rawHalfMutex := rawHalfConn.FieldByName("Mutex")
if !rawHalfMutex.IsValid() || rawHalfMutex.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid half mutex")
}
halfAccess := (*sync.Mutex)(unsafe.Pointer(rawHalfMutex.UnsafeAddr()))
rawRawInput := rawConn.FieldByName("rawInput")
if !rawRawInput.IsValid() || rawRawInput.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid raw input")
}
rawInput := (*bytes.Buffer)(unsafe.Pointer(rawRawInput.UnsafeAddr()))
rawInput0 := rawConn.FieldByName("input")
if !rawInput0.IsValid() || rawInput0.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid input")
}
input := (*bytes.Reader)(unsafe.Pointer(rawInput0.UnsafeAddr()))
rawHand := rawConn.FieldByName("hand")
if !rawHand.IsValid() || rawHand.Kind() != reflect.Struct {
return nil, E.New("badtls: invalid hand")
}
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
return &ReadWaitConn{
STDConn: stdConn,
halfAccess: halfAccess,
rawInput: rawInput,
input: input,
hand: hand,
}, nil
}
func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy bool) {
c.readWaitOptions = options
return false
}
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
err = c.Handshake()
if err != nil {
return
}
c.halfAccess.Lock()
defer c.halfAccess.Unlock()
for c.input.Len() == 0 {
err = tlsReadRecord(c.STDConn)
if err != nil {
return
}
for c.hand.Len() > 0 {
err = tlsHandlePostHandshakeMessage(c.STDConn)
if err != nil {
return
}
}
}
buffer = c.readWaitOptions.NewBuffer()
n, err := c.input.Read(buffer.FreeBytes())
if err != nil {
buffer.Release()
return
}
buffer.Truncate(n)
if n != 0 && c.input.Len() == 0 && c.rawInput.Len() > 0 &&
// recordType(c.rawInput.Bytes()[0]) == recordTypeAlert {
c.rawInput.Bytes()[0] == 21 {
_ = tlsReadRecord(c.STDConn)
// return n, err // will be io.EOF on closeNotify
}
c.readWaitOptions.PostReturn(buffer)
return
}
//go:linkname tlsReadRecord crypto/tls.(*Conn).readRecord
func tlsReadRecord(c *tls.STDConn) error
//go:linkname tlsHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
func tlsHandlePostHandshakeMessage(c *tls.STDConn) error

View File

@ -0,0 +1,13 @@
//go:build !go1.21 || without_badtls
package badtls
import (
"os"
"github.com/sagernet/sing/common/tls"
)
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
return nil, os.ErrInvalid
}

View File

@ -1,6 +1,6 @@
package badversion package badversion
import "github.com/sagernet/sing-box/common/json" import "github.com/sagernet/sing/common/json"
func (v Version) MarshalJSON() ([]byte, error) { func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String()) return json.Marshal(v.String())

View File

@ -15,14 +15,17 @@ import (
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
var _ WireGuardListener = (*DefaultDialer)(nil)
type DefaultDialer struct { type DefaultDialer struct {
dialer4 tcpDialer dialer4 tcpDialer
dialer6 tcpDialer dialer6 tcpDialer
udpDialer4 net.Dialer udpDialer4 net.Dialer
udpDialer6 net.Dialer udpDialer6 net.Dialer
udpListener net.ListenConfig udpListener net.ListenConfig
udpAddr4 string udpAddr4 string
udpAddr6 string udpAddr6 string
isWireGuardListener bool
} }
func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) { func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDialer, error) {
@ -98,6 +101,11 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
} }
setMultiPathTCP(&dialer4) setMultiPathTCP(&dialer4)
} }
if options.IsWireGuardListener {
for _, controlFn := range wgControlFns {
listener.Control = control.Append(listener.Control, controlFn)
}
}
tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen) tcpDialer4, err := newTCPDialer(dialer4, options.TCPFastOpen)
if err != nil { if err != nil {
return nil, err return nil, err
@ -114,6 +122,7 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
listener, listener,
udpAddr4, udpAddr4,
udpAddr6, udpAddr6,
options.IsWireGuardListener,
}, nil }, nil
} }
@ -146,6 +155,10 @@ func (d *DefaultDialer) ListenPacket(ctx context.Context, destination M.Socksadd
} }
} }
func (d *DefaultDialer) ListenPacketCompat(network, address string) (net.PacketConn, error) {
return trackPacketConn(d.udpListener.ListenPacket(context.Background(), network, address))
}
func trackConn(conn net.Conn, err error) (net.Conn, error) { func trackConn(conn net.Conn, err error) (net.Conn, error) {
if !conntrack.Enabled || err != nil { if !conntrack.Enabled || err != nil {
return conn, err return conn, err

View File

@ -6,15 +6,13 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing-dns" "github.com/sagernet/sing-dns"
"github.com/sagernet/sing/common"
N "github.com/sagernet/sing/common/network" N "github.com/sagernet/sing/common/network"
) )
func MustNew(router adapter.Router, options option.DialerOptions) N.Dialer {
return common.Must1(New(router, options))
}
func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) { func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error) {
if options.IsWireGuardListener {
return NewDefault(router, options)
}
var ( var (
dialer N.Dialer dialer N.Dialer
err error err error

View File

@ -18,11 +18,19 @@ func NewRouter(router adapter.Router) N.Dialer {
} }
func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { func (d *RouterDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) {
return d.router.DefaultOutbound(network).DialContext(ctx, network, destination) dialer, err := d.router.DefaultOutbound(network)
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, network, destination)
} }
func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { func (d *RouterDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {
return d.router.DefaultOutbound(N.NetworkUDP).ListenPacket(ctx, destination) dialer, err := d.router.DefaultOutbound(N.NetworkUDP)
if err != nil {
return nil, err
}
return dialer.ListenPacket(ctx, destination)
} }
func (d *RouterDialer) Upstream() any { func (d *RouterDialer) Upstream() any {

View File

@ -0,0 +1,9 @@
package dialer
import (
"net"
)
type WireGuardListener interface {
ListenPacketCompat(network, address string) (net.PacketConn, error)
}

View 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

View File

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

View File

@ -1,128 +0,0 @@
package json
import (
"bufio"
"io"
)
// kanged from v2ray
type commentFilterState = byte
const (
commentFilterStateContent commentFilterState = iota
commentFilterStateEscape
commentFilterStateDoubleQuote
commentFilterStateDoubleQuoteEscape
commentFilterStateSingleQuote
commentFilterStateSingleQuoteEscape
commentFilterStateComment
commentFilterStateSlash
commentFilterStateMultilineComment
commentFilterStateMultilineCommentStar
)
type CommentFilter struct {
br *bufio.Reader
state commentFilterState
}
func NewCommentFilter(reader io.Reader) io.Reader {
return &CommentFilter{br: bufio.NewReader(reader)}
}
func (v *CommentFilter) Read(b []byte) (int, error) {
p := b[:0]
for len(p) < len(b)-2 {
x, err := v.br.ReadByte()
if err != nil {
if len(p) == 0 {
return 0, err
}
return len(p), nil
}
switch v.state {
case commentFilterStateContent:
switch x {
case '"':
v.state = commentFilterStateDoubleQuote
p = append(p, x)
case '\'':
v.state = commentFilterStateSingleQuote
p = append(p, x)
case '\\':
v.state = commentFilterStateEscape
case '#':
v.state = commentFilterStateComment
case '/':
v.state = commentFilterStateSlash
default:
p = append(p, x)
}
case commentFilterStateEscape:
p = append(p, '\\', x)
v.state = commentFilterStateContent
case commentFilterStateDoubleQuote:
switch x {
case '"':
v.state = commentFilterStateContent
p = append(p, x)
case '\\':
v.state = commentFilterStateDoubleQuoteEscape
default:
p = append(p, x)
}
case commentFilterStateDoubleQuoteEscape:
p = append(p, '\\', x)
v.state = commentFilterStateDoubleQuote
case commentFilterStateSingleQuote:
switch x {
case '\'':
v.state = commentFilterStateContent
p = append(p, x)
case '\\':
v.state = commentFilterStateSingleQuoteEscape
default:
p = append(p, x)
}
case commentFilterStateSingleQuoteEscape:
p = append(p, '\\', x)
v.state = commentFilterStateSingleQuote
case commentFilterStateComment:
if x == '\n' {
v.state = commentFilterStateContent
p = append(p, '\n')
}
case commentFilterStateSlash:
switch x {
case '/':
v.state = commentFilterStateComment
case '*':
v.state = commentFilterStateMultilineComment
default:
p = append(p, '/', x)
}
case commentFilterStateMultilineComment:
switch x {
case '*':
v.state = commentFilterStateMultilineCommentStar
case '\n':
p = append(p, '\n')
}
case commentFilterStateMultilineCommentStar:
switch x {
case '/':
v.state = commentFilterStateContent
case '*':
// Stay
case '\n':
p = append(p, '\n')
default:
v.state = commentFilterStateMultilineComment
}
default:
panic("Unknown state.")
}
}
return len(p), nil
}

View File

@ -1,18 +0,0 @@
package json
import "encoding/json"
var (
Marshal = json.Marshal
Unmarshal = json.Unmarshal
NewEncoder = json.NewEncoder
NewDecoder = json.NewDecoder
)
type (
Encoder = json.Encoder
Decoder = json.Decoder
Token = json.Token
Delim = json.Delim
SyntaxError = json.SyntaxError
)

487
common/srs/binary.go Normal file
View File

@ -0,0 +1,487 @@
package srs
import (
"compress/zlib"
"encoding/binary"
"io"
"net/netip"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/domain"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/rw"
"go4.org/netipx"
)
var MagicBytes = [3]byte{0x53, 0x52, 0x53} // SRS
const (
ruleItemQueryType uint8 = iota
ruleItemNetwork
ruleItemDomain
ruleItemDomainKeyword
ruleItemDomainRegex
ruleItemSourceIPCIDR
ruleItemIPCIDR
ruleItemSourcePort
ruleItemSourcePortRange
ruleItemPort
ruleItemPortRange
ruleItemProcessName
ruleItemProcessPath
ruleItemPackageName
ruleItemWIFISSID
ruleItemWIFIBSSID
ruleItemFinal uint8 = 0xFF
)
func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err error) {
var magicBytes [3]byte
_, err = io.ReadFull(reader, magicBytes[:])
if err != nil {
return
}
if magicBytes != MagicBytes {
err = E.New("invalid sing-box rule set file")
return
}
var version uint8
err = binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return ruleSet, err
}
if version != 1 {
return ruleSet, E.New("unsupported version: ", version)
}
zReader, err := zlib.NewReader(reader)
if err != nil {
return
}
length, err := rw.ReadUVariant(zReader)
if err != nil {
return
}
ruleSet.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ {
ruleSet.Rules[i], err = readRule(zReader, recovery)
if err != nil {
err = E.Cause(err, "read rule[", i, "]")
return
}
}
return
}
func Write(writer io.Writer, ruleSet option.PlainRuleSet) error {
_, err := writer.Write(MagicBytes[:])
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
}
zWriter, err := zlib.NewWriterLevel(writer, zlib.BestCompression)
if err != nil {
return err
}
err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules)))
if err != nil {
return err
}
for _, rule := range ruleSet.Rules {
err = writeRule(zWriter, rule)
if err != nil {
return err
}
}
return zWriter.Close()
}
func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) {
var ruleType uint8
err = binary.Read(reader, binary.BigEndian, &ruleType)
if err != nil {
return
}
switch ruleType {
case 0:
rule.Type = C.RuleTypeDefault
rule.DefaultOptions, err = readDefaultRule(reader, recovery)
case 1:
rule.Type = C.RuleTypeLogical
rule.LogicalOptions, err = readLogicalRule(reader, recovery)
default:
err = E.New("unknown rule type: ", ruleType)
}
return
}
func writeRule(writer io.Writer, rule option.HeadlessRule) error {
switch rule.Type {
case C.RuleTypeDefault:
return writeDefaultRule(writer, rule.DefaultOptions)
case C.RuleTypeLogical:
return writeLogicalRule(writer, rule.LogicalOptions)
default:
panic("unknown rule type: " + rule.Type)
}
}
func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) {
var lastItemType uint8
for {
var itemType uint8
err = binary.Read(reader, binary.BigEndian, &itemType)
if err != nil {
return
}
switch itemType {
case ruleItemQueryType:
var rawQueryType []uint16
rawQueryType, err = readRuleItemUint16(reader)
if err != nil {
return
}
rule.QueryType = common.Map(rawQueryType, func(it uint16) option.DNSQueryType {
return option.DNSQueryType(it)
})
case ruleItemNetwork:
rule.Network, err = readRuleItemString(reader)
case ruleItemDomain:
var matcher *domain.Matcher
matcher, err = domain.ReadMatcher(reader)
if err != nil {
return
}
rule.DomainMatcher = matcher
case ruleItemDomainKeyword:
rule.DomainKeyword, err = readRuleItemString(reader)
case ruleItemDomainRegex:
rule.DomainRegex, err = readRuleItemString(reader)
case ruleItemSourceIPCIDR:
rule.SourceIPSet, err = readIPSet(reader)
if err != nil {
return
}
if recovery {
rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String)
}
case ruleItemIPCIDR:
rule.IPSet, err = readIPSet(reader)
if err != nil {
return
}
if recovery {
rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String)
}
case ruleItemSourcePort:
rule.SourcePort, err = readRuleItemUint16(reader)
case ruleItemSourcePortRange:
rule.SourcePortRange, err = readRuleItemString(reader)
case ruleItemPort:
rule.Port, err = readRuleItemUint16(reader)
case ruleItemPortRange:
rule.PortRange, err = readRuleItemString(reader)
case ruleItemProcessName:
rule.ProcessName, err = readRuleItemString(reader)
case ruleItemProcessPath:
rule.ProcessPath, err = readRuleItemString(reader)
case ruleItemPackageName:
rule.PackageName, err = readRuleItemString(reader)
case ruleItemWIFISSID:
rule.WIFISSID, err = readRuleItemString(reader)
case ruleItemWIFIBSSID:
rule.WIFIBSSID, err = readRuleItemString(reader)
case ruleItemFinal:
err = binary.Read(reader, binary.BigEndian, &rule.Invert)
return
default:
err = E.New("unknown rule item type: ", itemType, ", last type: ", lastItemType)
}
if err != nil {
return
}
lastItemType = itemType
}
}
func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
err := binary.Write(writer, binary.BigEndian, uint8(0))
if err != nil {
return err
}
if len(rule.QueryType) > 0 {
err = writeRuleItemUint16(writer, ruleItemQueryType, common.Map(rule.QueryType, func(it option.DNSQueryType) uint16 {
return uint16(it)
}))
if err != nil {
return err
}
}
if len(rule.Network) > 0 {
err = writeRuleItemString(writer, ruleItemNetwork, rule.Network)
if err != nil {
return err
}
}
if len(rule.Domain) > 0 || len(rule.DomainSuffix) > 0 {
err = binary.Write(writer, binary.BigEndian, ruleItemDomain)
if err != nil {
return err
}
err = domain.NewMatcher(rule.Domain, rule.DomainSuffix).Write(writer)
if err != nil {
return err
}
}
if len(rule.DomainKeyword) > 0 {
err = writeRuleItemString(writer, ruleItemDomainKeyword, rule.DomainKeyword)
if err != nil {
return err
}
}
if len(rule.DomainRegex) > 0 {
err = writeRuleItemString(writer, ruleItemDomainRegex, rule.DomainRegex)
if err != nil {
return err
}
}
if len(rule.SourceIPCIDR) > 0 {
err = writeRuleItemCIDR(writer, ruleItemSourceIPCIDR, rule.SourceIPCIDR)
if err != nil {
return E.Cause(err, "source_ipcidr")
}
}
if len(rule.IPCIDR) > 0 {
err = writeRuleItemCIDR(writer, ruleItemIPCIDR, rule.IPCIDR)
if err != nil {
return E.Cause(err, "ipcidr")
}
}
if len(rule.SourcePort) > 0 {
err = writeRuleItemUint16(writer, ruleItemSourcePort, rule.SourcePort)
if err != nil {
return err
}
}
if len(rule.SourcePortRange) > 0 {
err = writeRuleItemString(writer, ruleItemSourcePortRange, rule.SourcePortRange)
if err != nil {
return err
}
}
if len(rule.Port) > 0 {
err = writeRuleItemUint16(writer, ruleItemPort, rule.Port)
if err != nil {
return err
}
}
if len(rule.PortRange) > 0 {
err = writeRuleItemString(writer, ruleItemPortRange, rule.PortRange)
if err != nil {
return err
}
}
if len(rule.ProcessName) > 0 {
err = writeRuleItemString(writer, ruleItemProcessName, rule.ProcessName)
if err != nil {
return err
}
}
if len(rule.ProcessPath) > 0 {
err = writeRuleItemString(writer, ruleItemProcessPath, rule.ProcessPath)
if err != nil {
return err
}
}
if len(rule.PackageName) > 0 {
err = writeRuleItemString(writer, ruleItemPackageName, rule.PackageName)
if err != nil {
return err
}
}
if len(rule.WIFISSID) > 0 {
err = writeRuleItemString(writer, ruleItemWIFISSID, rule.WIFISSID)
if err != nil {
return err
}
}
if len(rule.WIFIBSSID) > 0 {
err = writeRuleItemString(writer, ruleItemWIFIBSSID, rule.WIFIBSSID)
if err != nil {
return err
}
}
err = binary.Write(writer, binary.BigEndian, ruleItemFinal)
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, rule.Invert)
if err != nil {
return err
}
return nil
}
func readRuleItemString(reader io.Reader) ([]string, error) {
length, err := rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
value := make([]string, length)
for i := uint64(0); i < length; i++ {
value[i], err = rw.ReadVString(reader)
if err != nil {
return nil, err
}
}
return value, nil
}
func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error {
err := binary.Write(writer, binary.BigEndian, itemType)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(value)))
if err != nil {
return err
}
for _, item := range value {
err = rw.WriteVString(writer, item)
if err != nil {
return err
}
}
return nil
}
func readRuleItemUint16(reader io.Reader) ([]uint16, error) {
length, err := rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
value := make([]uint16, length)
for i := uint64(0); i < length; i++ {
err = binary.Read(reader, binary.BigEndian, &value[i])
if err != nil {
return nil, err
}
}
return value, nil
}
func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error {
err := binary.Write(writer, binary.BigEndian, itemType)
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(value)))
if err != nil {
return err
}
for _, item := range value {
err = binary.Write(writer, binary.BigEndian, item)
if err != nil {
return err
}
}
return nil
}
func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
var builder netipx.IPSetBuilder
for i, prefixString := range value {
prefix, err := netip.ParsePrefix(prefixString)
if err == nil {
builder.AddPrefix(prefix)
continue
}
addr, addrErr := netip.ParseAddr(prefixString)
if addrErr == nil {
builder.Add(addr)
continue
}
return E.Cause(err, "parse [", i, "]")
}
ipSet, err := builder.IPSet()
if err != nil {
return err
}
err = binary.Write(writer, binary.BigEndian, itemType)
if err != nil {
return err
}
return writeIPSet(writer, ipSet)
}
func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
var mode uint8
err = binary.Read(reader, binary.BigEndian, &mode)
if err != nil {
return
}
switch mode {
case 0:
logicalRule.Mode = C.LogicalTypeAnd
case 1:
logicalRule.Mode = C.LogicalTypeOr
default:
err = E.New("unknown logical mode: ", mode)
return
}
length, err := rw.ReadUVariant(reader)
if err != nil {
return
}
logicalRule.Rules = make([]option.HeadlessRule, length)
for i := uint64(0); i < length; i++ {
logicalRule.Rules[i], err = readRule(reader, recovery)
if err != nil {
err = E.Cause(err, "read logical rule [", i, "]")
return
}
}
err = binary.Read(reader, binary.BigEndian, &logicalRule.Invert)
if err != nil {
return
}
return
}
func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
}
switch logicalRule.Mode {
case C.LogicalTypeAnd:
err = binary.Write(writer, binary.BigEndian, uint8(0))
case C.LogicalTypeOr:
err = binary.Write(writer, binary.BigEndian, uint8(1))
default:
panic("unknown logical mode: " + logicalRule.Mode)
}
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules)))
if err != nil {
return err
}
for _, rule := range logicalRule.Rules {
err = writeRule(writer, rule)
if err != nil {
return err
}
}
err = binary.Write(writer, binary.BigEndian, logicalRule.Invert)
if err != nil {
return err
}
return nil
}

116
common/srs/ip_set.go Normal file
View File

@ -0,0 +1,116 @@
package srs
import (
"encoding/binary"
"io"
"net/netip"
"unsafe"
"github.com/sagernet/sing/common/rw"
"go4.org/netipx"
)
type myIPSet struct {
rr []myIPRange
}
type myIPRange struct {
from netip.Addr
to netip.Addr
}
func readIPSet(reader io.Reader) (*netipx.IPSet, error) {
var version uint8
err := binary.Read(reader, binary.BigEndian, &version)
if err != nil {
return nil, err
}
var length uint64
err = binary.Read(reader, binary.BigEndian, &length)
if err != nil {
return nil, err
}
mySet := &myIPSet{
rr: make([]myIPRange, length),
}
for i := uint64(0); i < length; i++ {
var (
fromLen uint64
toLen uint64
fromAddr netip.Addr
toAddr netip.Addr
)
fromLen, err = rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
fromBytes := make([]byte, fromLen)
_, err = io.ReadFull(reader, fromBytes)
if err != nil {
return nil, err
}
err = fromAddr.UnmarshalBinary(fromBytes)
if err != nil {
return nil, err
}
toLen, err = rw.ReadUVariant(reader)
if err != nil {
return nil, err
}
toBytes := make([]byte, toLen)
_, err = io.ReadFull(reader, toBytes)
if err != nil {
return nil, err
}
err = toAddr.UnmarshalBinary(toBytes)
if err != nil {
return nil, err
}
mySet.rr[i] = myIPRange{fromAddr, toAddr}
}
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
}
func writeIPSet(writer io.Writer, set *netipx.IPSet) error {
err := binary.Write(writer, binary.BigEndian, uint8(1))
if err != nil {
return err
}
mySet := (*myIPSet)(unsafe.Pointer(set))
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
if err != nil {
return err
}
for _, rr := range mySet.rr {
var (
fromBinary []byte
toBinary []byte
)
fromBinary, err = rr.from.MarshalBinary()
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(fromBinary)))
if err != nil {
return err
}
_, err = writer.Write(fromBinary)
if err != nil {
return err
}
toBinary, err = rr.to.MarshalBinary()
if err != nil {
return err
}
err = rw.WriteUVariant(writer, uint64(len(toBinary)))
if err != nil {
return err
}
_, err = writer.Write(toBinary)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,31 @@
package taskmonitor
import (
"time"
F "github.com/sagernet/sing/common/format"
"github.com/sagernet/sing/common/logger"
)
type Monitor struct {
logger logger.Logger
timeout time.Duration
timer *time.Timer
}
func New(logger logger.Logger, timeout time.Duration) *Monitor {
return &Monitor{
logger: logger,
timeout: timeout,
}
}
func (m *Monitor) Start(taskName ...any) {
m.timer = time.AfterFunc(m.timeout, func() {
m.logger.Warn(F.ToString(taskName...), " take too much time to finish!")
})
}
func (m *Monitor) Finish() {
m.timer.Stop()
}

View File

@ -6,6 +6,7 @@ import (
"os" "os"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@ -42,7 +43,17 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel() defer cancel()
return aTLS.ClientHandshake(ctx, conn, config) tlsConn, err := aTLS.ClientHandshake(ctx, conn, config)
if err != nil {
return nil, err
}
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
if err == nil {
return readWaitConn, nil
} else if err != os.ErrInvalid {
return nil, err
}
return tlsConn, nil
} }
type Dialer struct { type Dialer struct {

View File

@ -7,6 +7,7 @@ import (
"context" "context"
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/ecdh"
"crypto/ed25519" "crypto/ed25519"
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
@ -137,12 +138,21 @@ func (e *RealityClientConfig) ClientHandshake(ctx context.Context, conn net.Conn
hello.SessionId[2] = 1 hello.SessionId[2] = 1
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix())) binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))
copy(hello.SessionId[8:], e.shortID[:]) copy(hello.SessionId[8:], e.shortID[:])
if debug.Enabled { if debug.Enabled {
fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16]) fmt.Printf("REALITY hello.sessionId[:16]: %v\n", hello.SessionId[:16])
} }
publicKey, err := ecdh.X25519().NewPublicKey(e.publicKey)
authKey := uConn.HandshakeState.State13.EcdheParams.SharedKey(e.publicKey) if err != nil {
return nil, err
}
ecdheKey := uConn.HandshakeState.State13.EcdheKey
if ecdheKey == nil {
return nil, E.New("nil ecdhe_key")
}
authKey, err := ecdheKey.ECDH(publicKey)
if err != nil {
return nil, err
}
if authKey == nil { if authKey == nil {
return nil, E.New("nil auth_key") return nil, E.New("nil auth_key")
} }

View File

@ -3,7 +3,9 @@ package tls
import ( import (
"context" "context"
"net" "net"
"os"
"github.com/sagernet/sing-box/common/badtls"
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"
@ -26,5 +28,15 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel() defer cancel()
return aTLS.ServerHandshake(ctx, conn, config) tlsConn, err := aTLS.ServerHandshake(ctx, conn, config)
if err != nil {
return nil, err
}
readWaitConn, err := badtls.NewReadWaitConn(tlsConn)
if err == nil {
return readWaitConn, nil
} else if err != os.ErrInvalid {
return nil, err
}
return tlsConn, nil
} }

View File

@ -219,6 +219,16 @@ func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name { switch name {
case "chrome", "": case "chrome", "":
return utls.HelloChrome_Auto, nil return utls.HelloChrome_Auto, nil
case "chrome_psk":
return utls.HelloChrome_100_PSK, nil
case "chrome_psk_shuffle":
return utls.HelloChrome_112_PSK_Shuf, nil
case "chrome_padding_psk_shuffle":
return utls.HelloChrome_114_Padding_PSK_Shuf, nil
case "chrome_pq":
return utls.HelloChrome_115_PQ, nil
case "chrome_pq_psk":
return utls.HelloChrome_115_PQ_PSK, nil
case "firefox": case "firefox":
return utls.HelloFirefox_Auto, nil return utls.HelloFirefox_Auto, nil
case "edge": case "edge":

View File

@ -9,3 +9,11 @@ const (
LogicalTypeAnd = "and" LogicalTypeAnd = "and"
LogicalTypeOr = "or" LogicalTypeOr = "or"
) )
const (
RuleSetTypeLocal = "local"
RuleSetTypeRemote = "remote"
RuleSetVersion1 = 1
RuleSetFormatSource = "source"
RuleSetFormatBinary = "binary"
)

View File

@ -3,11 +3,15 @@ package constant
import "time" import "time"
const ( const (
TCPTimeout = 5 * time.Second TCPTimeout = 5 * 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
STUNTimeout = 15 * time.Second STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 1 * time.Minute DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
DefaultStartTimeout = 10 * time.Second
DefaultStopTimeout = 5 * time.Second
DefaultStopFatalTimeout = 10 * time.Second
) )

View File

@ -6,12 +6,12 @@ import (
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"github.com/sagernet/sing-box/common/badjson"
"github.com/sagernet/sing-box/common/humanize" "github.com/sagernet/sing-box/common/humanize"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )

View File

@ -2,35 +2,178 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
# ChangeLog #### 1.8.0-beta.9
* Add simple loopback detect
* Fixes and improvements
#### 1.7.5 #### 1.7.5
* Fixes and improvements * Fixes and improvements
#### 1.8.0-alpha.17
* Add GSO support for TUN and WireGuard system interface **1**
* Update uTLS to 1.5.4 **2**
* Update dependencies **3**
* Fixes and improvements
**1**:
See [TUN](/configuration/inbound/tun/) inbound and [WireGuard](/configuration/outbound/wireguard/) outbound.
**2**:
Added some new [fingerprints](/configuration/shared/tls#utls).
Also, starting with this release, uTLS requires at least Go 1.20.
**3**:
Updated `cloudflare-tls`, `gomobile`, `smux`, `tfo-go` and `wireguard-go` to latest, and `gvisor` to `20231204.0`
This may break something, good luck!
#### 1.7.4 #### 1.7.4
* Fixes and improvements * Fixes and improvements
_Due to the long waiting time, this version is no longer waiting for approval _Due to the long waiting time, this version is no longer waiting for approval
by the Apple App Store, so updates to Apple Platforms will be delayed._ by the Apple App Store, so updates to Apple Platforms will be delayed._
#### 1.8.0-alpha.16
* Fixes and improvements
#### 1.8.0-alpha.15
* Some chaotic changes **1**
* Fixes and improvements
**1**:
Designed to optimize memory usage of idle connections, may take effect on the following protocols:
| Protocol | TCP | UDP |
|------------------------------------------------------|------------------|------------------|
| HTTP proxy server | :material-check: | / |
| SOCKS5 | :material-close: | :material-check: |
| Shadowsocks none/AEAD/AEAD2022 | :material-check: | :material-check: |
| Trojan | / | :material-check: |
| TUIC/Hysteria/Hysteria2 | :material-close: | :material-check: |
| Multiplex | :material-close: | :material-check: |
| Plain TLS (Trojan/VLESS without extra sub-protocols) | :material-check: | / |
| Other protocols | :material-close: | :material-close: |
At the same time, everything existing may be broken, please actively report problems with this version.
#### 1.8.0-alpha.13
* Fixes and improvements
#### 1.8.0-alpha.10
* Add `idle_timeout` for URLTest outbound **1**
* Fixes and improvements
**1**:
When URLTest is idle for a certain period of time, the scheduled delay test will be paused.
#### 1.7.2 #### 1.7.2
* Fixes and improvements * Fixes and improvements
#### 1.8.0-alpha.8
* Add context to JSON decode error message **1**
* Reject internal fake-ip queries **2**
* Fixes and improvements
**1**:
JSON parse errors will now include the current key path.
Only takes effect when compiled with Go 1.21+.
**2**:
All internal DNS queries now skip DNS rules with `server` type `fakeip`,
and the default DNS server can no longer be `fakeip`.
This change is intended to break incorrect usage and essentially requires no action.
#### 1.8.0-alpha.7
* Fixes and improvements
#### 1.7.1 #### 1.7.1
* Fixes and improvements * Fixes and improvements
#### 1.8.0-alpha.6
* Fix rule-set matching logic **1**
* Fixes and improvements
**1**:
Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule sets,
rather than completely following the AND logic.
#### 1.8.0-alpha.5
* Parallel rule-set initialization
* Independent `source_ip_is_private` and `ip_is_private` rules **1**
**1**:
The `private` GeoIP country never existed and was actually implemented inside V2Ray.
Since GeoIP was deprecated, we made this rule independent, see [Migration](/migration/#migrate-geoip-to-rule-sets).
#### 1.8.0-alpha.1
* Migrate cache file from Clash API to independent options **1**
* Introducing [Rule Set](/configuration/rule-set/) **2**
* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**
* Allow nested logical rules **4**
**1**:
See [Cache File](/configuration/experimental/cache-file/) and
[Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-options).
**2**:
Rule set is independent collections of rules that can be compiled into binaries to improve performance.
Compared to legacy GeoIP and Geosite resources,
it can include more types of rules, load faster,
use less memory, and update automatically.
See [Route#rule_set](/configuration/route/#rule_set),
[Route Rule](/configuration/route/rule/),
[DNS Rule](/configuration/dns/rule/),
[Rule Set](/configuration/rule-set/),
[Source Format](/configuration/rule-set/source-format/) and
[Headless Rule](/configuration/rule-set/headless-rule/).
For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and
[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets).
**3**:
New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets.
**4**:
Logical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.
#### 1.7.0 #### 1.7.0
* Fixes and improvements * Fixes and improvements
Important changes since 1.6: Important changes since 1.6:
* Add [exclude route support](/configuration/inbound/tun) for TUN inbound * Add [exclude route support](/configuration/inbound/tun/) for TUN inbound
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen) **1** * Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen/) **1**
* Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **2** * Add [HTTPUpgrade V2Ray transport](/configuration/shared/v2ray-transport#HTTPUpgrade) support **2**
* Migrate multiplex and UoT server to inbound **3** * Migrate multiplex and UoT server to inbound **3**
* Add TCP Brutal support for multiplex **4** * Add TCP Brutal support for multiplex **4**
@ -55,12 +198,13 @@ The new HTTPUpgrade transport has better performance than WebSocket and is bette
**3**: **3**:
Starting in 1.7.0, multiplexing support is no longer enabled by default Starting in 1.7.0, multiplexing support is no longer enabled by default
and needs to be turned on explicitly in inbound options. and needs to be turned on explicitly in inbound
options.
**4** **4**
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server, Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,
see [TCP Brutal](/configuration/shared/tcp-brutal) for details. see [TCP Brutal](/configuration/shared/tcp-brutal/) for details.
**5**: **5**:
@ -149,7 +293,7 @@ Only supported in graphical clients on Android and iOS.
#### 1.6.1 #### 1.6.1
* Our [Android client](/installation/clients/sfa) is now available in the Google Play Store ▶️ * Our [Android client](/installation/clients/sfa/) is now available in the Google Play Store ▶️
* Fixes and improvements * Fixes and improvements
#### 1.7.0-alpha.6 #### 1.7.0-alpha.6
@ -169,7 +313,7 @@ options.
**2** **2**
Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server, Hysteria Brutal Congestion Control Algorithm in TCP. A kernel module needs to be installed on the Linux server,
see [TCP Brutal](/configuration/shared/tcp-brutal) for details. see [TCP Brutal](/configuration/shared/tcp-brutal/) for details.
#### 1.7.0-alpha.3 #### 1.7.0-alpha.3
@ -188,13 +332,13 @@ The new HTTPUpgrade transport has better performance than WebSocket and is bette
Important changes since 1.5: Important changes since 1.5:
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎 * Our [Apple tvOS client](/installation/clients/sft/) is now available in the App Store 🍎
* Update BBR congestion control for TUIC and Hysteria2 **1** * Update BBR congestion control for TUIC and Hysteria2 **1**
* Update brutal congestion control for Hysteria2 * Update brutal congestion control for Hysteria2
* Add `brutal_debug` option for Hysteria2 * Add `brutal_debug` option for Hysteria2
* Update legacy Hysteria protocol **2** * Update legacy Hysteria protocol **2**
* Add TLS self sign key pair generate command * Add TLS self sign key pair generate command
* Remove [Deprecated Features](/deprecated) by agreement * Remove [Deprecated Features](/deprecated/) by agreement
**1**: **1**:
@ -212,8 +356,8 @@ the old protocol (Hysteria 1) have been updated to be consistent with Hysteria 2
#### 1.7.0-alpha.1 #### 1.7.0-alpha.1
* Add [exclude route support](/configuration/inbound/tun) for TUN inbound * Add [exclude route support](/configuration/inbound/tun/) for TUN inbound
* Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen) **1** * Add `udp_disable_domain_unmapping` [inbound listen option](/configuration/shared/listen/) **1**
* Fixes and improvements * Fixes and improvements
**1**: **1**:
@ -237,7 +381,8 @@ When `auto_route` is enabled and `strict_route` is disabled, the device can now
**2**: **2**:
Built using Go 1.20, the last version that will run on Built using Go 1.20, the last version that will run on
Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave. Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High
Sierra, 10.14 Mojave.
#### 1.6.0-rc.4 #### 1.6.0-rc.4
@ -251,7 +396,8 @@ Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave
**1**: **1**:
Built using Go 1.20, the last version that will run on Built using Go 1.20, the last version that will run on
Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High Sierra, 10.14 Mojave. Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High
Sierra, 10.14 Mojave.
#### 1.6.0-beta.4 #### 1.6.0-beta.4
@ -331,7 +477,7 @@ introduce new issues.
#### 1.5.2 #### 1.5.2
* Our [Apple tvOS client](/installation/clients/sft) is now available in the App Store 🍎 * Our [Apple tvOS client](/installation/clients/sft/) is now available in the App Store 🍎
* Fixes and improvements * Fixes and improvements
#### 1.6.0-alpha.3 #### 1.6.0-alpha.3
@ -351,7 +497,7 @@ introduce new issues.
* Update BBR congestion control for TUIC and Hysteria2 **1** * Update BBR congestion control for TUIC and Hysteria2 **1**
* Update quic-go to v0.39.0 * Update quic-go to v0.39.0
* Update gVisor to 20230814.0 * Update gVisor to 20230814.0
* Remove [Deprecated Features](/deprecated) by agreement * Remove [Deprecated Features](/deprecated/) by agreement
* Fixes and improvements * Fixes and improvements
**1**: **1**:
@ -365,7 +511,7 @@ This update is intended to address the multi-send defects of the old implementat
Important changes since 1.4: Important changes since 1.4:
* Add TLS [ECH server](/configuration/shared/tls) support * Add TLS [ECH server](/configuration/shared/tls/) support
* Improve TLS TCH client configuration * Improve TLS TCH client configuration
* Add TLS ECH key pair generator **1** * Add TLS ECH key pair generator **1**
* Add TLS ECH support for QUIC based protocols **2** * Add TLS ECH support for QUIC based protocols **2**
@ -374,7 +520,7 @@ Important changes since 1.4:
* Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **4** * Add `interrupt_exist_connections` option for `Selector` and `URLTest` outbounds **4**
* Add DNS01 challenge support for ACME TLS certificate issuer **5** * Add DNS01 challenge support for ACME TLS certificate issuer **5**
* Add `merge` command **6** * Add `merge` command **6**
* Mark [Deprecated Features](/deprecated) * Mark [Deprecated Features](/deprecated/)
**1**: **1**:
@ -386,7 +532,7 @@ All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria[/2]`
**3**: **3**:
See [Hysteria2 inbound](/configuration/inbound/hysteria2) and [Hysteria2 outbound](/configuration/outbound/hysteria2) See [Hysteria2 inbound](/configuration/inbound/hysteria2/) and [Hysteria2 outbound](/configuration/outbound/hysteria2/)
For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network) For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)
@ -399,7 +545,7 @@ Only inbound connections are affected by this setting, internal connections will
**5**: **5**:
Only `Alibaba Cloud DNS` and `Cloudflare` are supported, see [ACME Fields](/configuration/shared/tls#acme-fields) Only `Alibaba Cloud DNS` and `Cloudflare` are supported, see [ACME Fields](/configuration/shared/tls#acme-fields)
and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge). and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/).
**6**: **6**:
@ -481,7 +627,7 @@ Global Flags:
Only `Alibaba Cloud DNS` and `Cloudflare` are supported, Only `Alibaba Cloud DNS` and `Cloudflare` are supported,
see [ACME Fields](/configuration/shared/tls#acme-fields) see [ACME Fields](/configuration/shared/tls#acme-fields)
and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge). and [DNS01 Challenge Fields](/configuration/shared/dns01_challenge/).
#### 1.5.0-beta.10 #### 1.5.0-beta.10
@ -510,7 +656,7 @@ Only inbound connections are affected by this setting, internal connections will
* Fix compatibility issues with official Hysteria2 server and client * Fix compatibility issues with official Hysteria2 server and client
* Fixes and improvements * Fixes and improvements
* Mark [deprecated features](/deprecated) * Mark [deprecated features](/deprecated/)
#### 1.5.0-beta.3 #### 1.5.0-beta.3
@ -529,13 +675,13 @@ Hysteria2 server and client when using `fastOpen=false` or UDP MTU >= 1200.
**1**: **1**:
See [Hysteria2 inbound](/configuration/inbound/hysteria2) and [Hysteria2 outbound](/configuration/outbound/hysteria2) See [Hysteria2 inbound](/configuration/inbound/hysteria2/) and [Hysteria2 outbound](/configuration/outbound/hysteria2/)
For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network) For protocol description, please refer to [https://v2.hysteria.network](https://v2.hysteria.network)
#### 1.5.0-beta.1 #### 1.5.0-beta.1
* Add TLS [ECH server](/configuration/shared/tls) support * Add TLS [ECH server](/configuration/shared/tls/) support
* Improve TLS TCH client configuration * Improve TLS TCH client configuration
* Add TLS ECH key pair generator **1** * Add TLS ECH key pair generator **1**
* Add TLS ECH support for QUIC based protocols **2** * Add TLS ECH support for QUIC based protocols **2**
@ -568,12 +714,12 @@ Important changes since 1.3:
*1*: *1*:
See [TUIC inbound](/configuration/inbound/tuic) See [TUIC inbound](/configuration/inbound/tuic/)
and [TUIC outbound](/configuration/outbound/tuic) and [TUIC outbound](/configuration/outbound/tuic/)
**2**: **2**:
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
another program compatible with the protocol as a server. another program compatible with the protocol as a server.
@ -604,7 +750,7 @@ Requires sing-box to be compiled with Go 1.21.
**1**: **1**:
This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp), designed to provide a QUIC This is the TUIC port of the [UDP over TCP protocol](/configuration/shared/udp-over-tcp/), designed to provide a QUIC
stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or stream based UDP relay mode that TUIC does not provide. Since it is an add-on protocol, you will need to use sing-box or
another program compatible with the protocol as a server. another program compatible with the protocol as a server.
@ -642,8 +788,8 @@ Requires sing-box to be compiled with Go 1.21.
*1*: *1*:
See [TUIC inbound](/configuration/inbound/tuic) See [TUIC inbound](/configuration/inbound/tuic/)
and [TUIC outbound](/configuration/outbound/tuic) and [TUIC outbound](/configuration/outbound/tuic/)
#### 1.3.6 #### 1.3.6
@ -652,7 +798,7 @@ and [TUIC outbound](/configuration/outbound/tuic)
#### 1.3.5 #### 1.3.5
* Fixes and improvements * Fixes and improvements
* Introducing our [Apple tvOS](/installation/clients/sft) client applications **1** * Introducing our [Apple tvOS](/installation/clients/sft/) client applications **1**
* Add per app proxy and app installed/updated trigger support for Android client * Add per app proxy and app installed/updated trigger support for Android client
* Add profile sharing support for Android/iOS/macOS clients * Add profile sharing support for Android/iOS/macOS clients
@ -679,7 +825,8 @@ downloaded through TestFlight.
#### 1.3.1-beta.3 #### 1.3.1-beta.3
* Introducing our [new iOS](/installation/clients/sfi) and [macOS](/installation/clients/sfm) client applications **1** * Introducing our [new iOS](/installation/clients/sfi/) and [macOS](/installation/clients/sfm/) client applications **1
**
* Fixes and improvements * Fixes and improvements
**1**: **1**:
@ -700,7 +847,7 @@ The old testflight link and app are no longer valid.
Important changes since 1.2: Important changes since 1.2:
* Add [FakeIP](/configuration/dns/fakeip) support **1** * Add [FakeIP](/configuration/dns/fakeip/) support **1**
* Improve multiplex **2** * Improve multiplex **2**
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support * Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
* Add `rewrite_ttl` DNS rule action * Add `rewrite_ttl` DNS rule action
@ -727,11 +874,11 @@ Important changes since 1.2:
*1*: *1*:
See [FAQ](/faq/fakeip) for more information. See [FAQ](/faq/fakeip/) for more information.
*2*: *2*:
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex). Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex/).
#### 1.3-rc2 #### 1.3-rc2
@ -793,7 +940,7 @@ Improved performance and reduced memory usage.
*1*: *1*:
Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex). Added new `h2mux` multiplex protocol and `padding` multiplex option, see [Multiplex](/configuration/shared/multiplex/).
#### 1.2.6 #### 1.2.6
@ -845,25 +992,25 @@ This is an incompatible update for XUDP in VLESS if vision flow is enabled.
#### 1.3-beta1 #### 1.3-beta1
* Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support * Add [DNS reverse mapping](/configuration/dns#reverse_mapping) support
* Add [L3 routing](/configuration/route/ip-rule) support **1** * Add [L3 routing](/configuration/route/ip-rule/) support **1**
* Add `rewrite_ttl` DNS rule action * Add `rewrite_ttl` DNS rule action
* Add [FakeIP](/configuration/dns/fakeip) support **2** * Add [FakeIP](/configuration/dns/fakeip/) support **2**
* Add `store_fakeip` Clash API option * Add `store_fakeip` Clash API option
* Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound * Add multi-peer support for [WireGuard](/configuration/outbound/wireguard#peers) outbound
* Add loopback detect * Add loopback detect
*1*: *1*:
It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct) or block connections It can currently be used to [route connections directly to WireGuard](/examples/wireguard-direct/) or block connections
at the IP layer. at the IP layer.
*2*: *2*:
See [FAQ](/faq/fakeip) for more information. See [FAQ](/faq/fakeip/) for more information.
#### 1.2.3 #### 1.2.3
* Introducing our [new Android client application](/installation/clients/sfa) * Introducing our [new Android client application](/installation/clients/sfa/)
* Improve UDP domain destination NAT * Improve UDP domain destination NAT
* Update reality protocol * Update reality protocol
* Fix TTL calculation for DNS response * Fix TTL calculation for DNS response
@ -892,16 +1039,16 @@ to `domain` rule.
Important changes since 1.1: Important changes since 1.1:
* Introducing our [new iOS client application](/installation/clients/sfi) * Introducing our [new iOS client application](/installation/clients/sfi/)
* Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp) * Introducing [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp/)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound * Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md) * Add [ShadowTLS protocol v3](https://github.com/ihciah/shadow-tls/blob/master/docs/protocol-v3-en.md)
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support * Add [VLESS server](/configuration/inbound/vless/) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support * Add [reality TLS](/configuration/shared/tls/) support
* Add [NTP service](/configuration/ntp) * Add [NTP service](/configuration/ntp/)
* Add [DHCP DNS server](/configuration/dns/server) support * Add [DHCP DNS server](/configuration/dns/server/) support
* Add SSH [host key validation](/configuration/outbound/ssh) support * Add SSH [host key validation](/configuration/outbound/ssh/) support
* Add [query_type](/configuration/dns/rule) DNS rule item * Add [query_type](/configuration/dns/rule/) DNS rule item
* Add fallback support for v2ray transport * Add fallback support for v2ray transport
* Add custom TLS server support for http based v2ray transports * Add custom TLS server support for http based v2ray transports
* Add health check support for http-based v2ray transports * Add health check support for http-based v2ray transports
@ -932,7 +1079,7 @@ name.
#### 1.2-beta9 #### 1.2-beta9
* Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp) * Introducing the [UDP over TCP protocol version 2](/configuration/shared/udp-over-tcp/)
* Add health check support for http-based v2ray transports * Add health check support for http-based v2ray transports
* Remove length limit on short_id for reality TLS config * Remove length limit on short_id for reality TLS config
* Fix bugs and update dependencies * Fix bugs and update dependencies
@ -949,7 +1096,7 @@ name.
#### 1.2-beta6 #### 1.2-beta6
* Introducing our [new iOS client application](/installation/clients/sfi) * Introducing our [new iOS client application](/installation/clients/sfi/)
* Add [platform options](/configuration/inbound/tun#platform) for tun inbound * Add [platform options](/configuration/inbound/tun#platform) for tun inbound
* Add custom TLS server support for http based v2ray transports * Add custom TLS server support for http based v2ray transports
* Add generate commands * Add generate commands
@ -962,8 +1109,8 @@ name.
#### 1.2-beta5 #### 1.2-beta5
* Add [VLESS server](/configuration/inbound/vless) and [vision](/configuration/outbound/vless#flow) support * Add [VLESS server](/configuration/inbound/vless/) and [vision](/configuration/outbound/vless#flow) support
* Add [reality TLS](/configuration/shared/tls) support * Add [reality TLS](/configuration/shared/tls/) support
* Fix match private address * Fix match private address
#### 1.1.6 #### 1.1.6
@ -978,7 +1125,7 @@ name.
#### 1.2-beta4 #### 1.2-beta4
* Add [NTP service](/configuration/ntp) * Add [NTP service](/configuration/ntp/)
* Add Add multiple server names and multi-user support for shadowtls * Add Add multiple server names and multi-user support for shadowtls
* Add strict mode support for shadowtls v3 * Add strict mode support for shadowtls v3
* Add uTLS support for shadowtls v3 * Add uTLS support for shadowtls v3
@ -998,9 +1145,9 @@ name.
#### 1.2-beta1 #### 1.2-beta1
* Add [DHCP DNS server](/configuration/dns/server) support * Add [DHCP DNS server](/configuration/dns/server/) support
* Add SSH [host key validation](/configuration/outbound/ssh) support * Add SSH [host key validation](/configuration/outbound/ssh/) support
* Add [query_type](/configuration/dns/rule) DNS rule item * Add [query_type](/configuration/dns/rule/) DNS rule item
* Add v2ray [user stats](/configuration/experimental#statsusers) api * Add v2ray [user stats](/configuration/experimental#statsusers) api
* Add new clash DNS query api * Add new clash DNS query api
* Improve vmess request * Improve vmess request
@ -1229,7 +1376,7 @@ and [ShadowTLS outbound](/configuration/outbound/shadowtls#version)
#### 1.1-beta6 #### 1.1-beta6
* Add [URLTest outbound](/configuration/outbound/urltest) * Add [URLTest outbound](/configuration/outbound/urltest/)
* Fix bugs in 1.1-beta5 * Fix bugs in 1.1-beta5
#### 1.1-beta5 #### 1.1-beta5
@ -1261,8 +1408,8 @@ The default tun stack is changed to system.
#### 1.1-beta4 #### 1.1-beta4
* Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin) * Add internal simple-obfs and v2ray-plugin [Shadowsocks plugins](/configuration/outbound/shadowsocks#plugin)
* Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr) * Add [ShadowsocksR outbound](/configuration/outbound/shadowsocksr/)
* Add [VLESS outbound and XUDP](/configuration/outbound/vless) * Add [VLESS outbound and XUDP](/configuration/outbound/vless/)
* Skip wait for hysteria tcp handshake response * Skip wait for hysteria tcp handshake response
* Fix socks4 client * Fix socks4 client
* Fix hysteria inbound * Fix hysteria inbound
@ -1289,7 +1436,7 @@ The default tun stack is changed to system.
*1*: *1*:
Switching modes using the Clash API, and `store-selected` are now supported, Switching modes using the Clash API, and `store-selected` are now supported,
see [Experimental](/configuration/experimental). see [Experimental](/configuration/experimental/).
*2*: *2*:
@ -1370,15 +1517,15 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
* Fix write trojan udp * Fix write trojan udp
* Fix DNS routing * Fix DNS routing
* Add attribute support for geosite * Add attribute support for geosite
* Update documentation for [Dial Fields](/configuration/shared/dial) * Update documentation for [Dial Fields](/configuration/shared/dial/)
#### 1.0-beta3 #### 1.0-beta3
* Add [chained inbound](/configuration/shared/listen#detour) support * Add [chained inbound](/configuration/shared/listen#detour) support
* Add process_path rule item * Add process_path rule item
* Add macOS redirect support * Add macOS redirect support
* Add ShadowTLS [Inbound](/configuration/inbound/shadowtls), [Outbound](/configuration/outbound/shadowtls) * Add ShadowTLS [Inbound](/configuration/inbound/shadowtls/), [Outbound](/configuration/outbound/shadowtls/)
and [Examples](/examples/shadowtls) and [Examples](/examples/shadowtls/)
* Fix search android package in non-owner users * Fix search android package in non-owner users
* Fix socksaddr type condition * Fix socksaddr type condition
* Fix smux session status * Fix smux session status
@ -1422,7 +1569,7 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
##### 2022/08/23 ##### 2022/08/23
* Add [V2Ray Transport](/configuration/shared/v2ray-transport) support for VMess and Trojan * Add [V2Ray Transport](/configuration/shared/v2ray-transport/) support for VMess and Trojan
* Allow plain http request in Naive inbound (It can now be used with nginx) * Allow plain http request in Naive inbound (It can now be used with nginx)
* Add proxy protocol support * Add proxy protocol support
* Free memory after start * Free memory after start
@ -1431,13 +1578,13 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
##### 2022/08/22 ##### 2022/08/22
* Add strategy setting for each [DNS server](/configuration/dns/server) * Add strategy setting for each [DNS server](/configuration/dns/server/)
* Add bind address to outbound options * Add bind address to outbound options
##### 2022/08/21 ##### 2022/08/21
* Add [Tor outbound](/configuration/outbound/tor) * Add [Tor outbound](/configuration/outbound/tor/)
* Add [SSH outbound](/configuration/outbound/ssh) * Add [SSH outbound](/configuration/outbound/ssh/)
##### 2022/08/20 ##### 2022/08/20
@ -1451,8 +1598,8 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
##### 2022/08/19 ##### 2022/08/19
* Add Hysteria [Inbound](/configuration/inbound/hysteria) and [Outbund](/configuration/outbound/hysteria) * Add Hysteria [Inbound](/configuration/inbound/hysteria/) and [Outbund](/configuration/outbound/hysteria/)
* Add [ACME TLS certificate issuer](/configuration/shared/tls) * Add [ACME TLS certificate issuer](/configuration/shared/tls/)
* Allow read config from stdin (-c stdin) * Allow read config from stdin (-c stdin)
* Update gVisor to 20220815.0 * Update gVisor to 20220815.0
@ -1470,11 +1617,11 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
##### 2022/08/16 ##### 2022/08/16
* Add ip_version (route/dns) rule item * Add ip_version (route/dns) rule item
* Add [WireGuard](/configuration/outbound/wireguard) outbound * Add [WireGuard](/configuration/outbound/wireguard/) outbound
##### 2022/08/15 ##### 2022/08/15
* Add uid, android user and package rules support in [Tun](/configuration/inbound/tun) routing. * Add uid, android user and package rules support in [Tun](/configuration/inbound/tun/) routing.
##### 2022/08/13 ##### 2022/08/13
@ -1483,15 +1630,15 @@ and [Listen Fields](/configuration/shared/listen#udp_fragment).
##### 2022/08/12 ##### 2022/08/12
* Performance improvements * Performance improvements
* Add UoT option for [SOCKS](/configuration/outbound/socks) outbound * Add UoT option for [SOCKS](/configuration/outbound/socks/) outbound
##### 2022/08/11 ##### 2022/08/11
* Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks) outbound, UoT support for all inbounds * Add UoT option for [Shadowsocks](/configuration/outbound/shadowsocks/) outbound, UoT support for all inbounds
##### 2022/08/10 ##### 2022/08/10
* Add full-featured [Naive](/configuration/inbound/naive) inbound * Add full-featured [Naive](/configuration/inbound/naive/) inbound
* Fix default dns server option [#9] by iKirby * Fix default dns server option [#9] by iKirby
##### 2022/08/09 ##### 2022/08/09

View File

@ -18,6 +18,7 @@ SFA provides an unprivileged TUN implementation through Android VpnService.
| `inet4_address` | :material-check: | / | | `inet4_address` | :material-check: | / |
| `inet6_address` | :material-check: | / | | `inet6_address` | :material-check: | / |
| `mtu` | :material-check: | / | | `mtu` | :material-check: | / |
| `gso` | :material-close: | No permission |
| `auto_route` | :material-check: | / | | `auto_route` | :material-check: | / |
| `strict_route` | :material-close: | Not implemented | | `strict_route` | :material-close: | Not implemented |
| `inet4_route_address` | :material-check: | / | | `inet4_route_address` | :material-check: | / |

View File

@ -14,28 +14,29 @@ SFI/SFM/SFT allows you to run sing-box through NetworkExtension with Application
SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension. SFI/SFM/SFT provides an unprivileged TUN implementation through NetworkExtension.
| TUN inbound option | Available | Note | | TUN inbound option | Available | Note |
|-------------------------------|-----------|-------------------| |-------------------------------|-------------------|-------------------|
| `interface_name` | ✖️ | Managed by Darwin | | `interface_name` | :material-close: | Managed by Darwin |
| `inet4_address` | ✔️ | / | | `inet4_address` | :material-check: | / |
| `inet6_address` | ✔️ | / | | `inet6_address` | :material-check: | / |
| `mtu` | ✔️ | / | | `mtu` | :material-check: | / |
| `auto_route` | ✔️ | / | | `gso` | :material-close: | Not implemented |
| `strict_route` | ✖️ | Not implemented | | `auto_route` | :material-check: | / |
| `inet4_route_address` | ✔️ | / | | `strict_route` | :material-close: | Not implemented |
| `inet6_route_address` | ✔️ | / | | `inet4_route_address` | :material-check: | / |
| `inet4_route_exclude_address` | ✔️ | / | | `inet6_route_address` | :material-check: | / |
| `inet6_route_exclude_address` | ✔️ | / | | `inet4_route_exclude_address` | :material-check: | / |
| `endpoint_independent_nat` | ✔️ | / | | `inet6_route_exclude_address` | :material-check: | / |
| `stack` | ✔️ | / | | `endpoint_independent_nat` | :material-check: | / |
| `include_interface` | ✖️ | Not implemented | | `stack` | :material-check: | / |
| `exclude_interface` | ✖️ | Not implemented | | `include_interface` | :material-close: | Not implemented |
| `include_uid` | ✖️ | Not implemented | | `exclude_interface` | :material-close: | Not implemented |
| `exclude_uid` | ✖️ | Not implemented | | `include_uid` | :material-close: | Not implemented |
| `include_android_user` | ✖️ | Not implemented | | `exclude_uid` | :material-close: | Not implemented |
| `include_package` | ✖️ | Not implemented | | `include_android_user` | :material-close: | Not implemented |
| `exclude_package` | ✖️ | Not implemented | | `include_package` | :material-close: | Not implemented |
| `platform` | ✔️ | / | | `exclude_package` | :material-close: | Not implemented |
| `platform` | :material-check: | / |
| Route/DNS rule option | Available | Note | | Route/DNS rule option | Available | Note |
|-----------------------|------------------|-----------------------| |-----------------------|------------------|-----------------------|

View File

@ -4,8 +4,8 @@ Maintained by Project S to provide a unified experience and platform-specific fu
| Platform | Client | | Platform | Client |
|---------------------------------------|-----------------------------------------| |---------------------------------------|-----------------------------------------|
| :material-android: Android | [sing-box for Android](./android) | | :material-android: Android | [sing-box for Android](./android/) |
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple) | | :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
| :material-laptop: Desktop | Working in progress | | :material-laptop: Desktop | Working in progress |
Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core Some third-party projects that claim to use sing-box or use sing-box as a selling point are not listed here. The core

View File

@ -4,8 +4,8 @@
| 平台 | 客户端 | | 平台 | 客户端 |
|---------------------------------------|-----------------------------------------| |---------------------------------------|-----------------------------------------|
| :material-android: Android | [sing-box for Android](./android) | | :material-android: Android | [sing-box for Android](./android/) |
| :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple) | | :material-apple: iOS/macOS/Apple tvOS | [sing-box for Apple platforms](./apple/) |
| :material-laptop: Desktop | 施工中 | | :material-laptop: Desktop | 施工中 |
此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业 此处没有列出一些声称使用或以 sing-box 为卖点的第三方项目。此类项目维护者的动机是获得更多用户,即使它们提供友好的商业

View File

@ -23,9 +23,9 @@
| Key | Format | | Key | Format |
|----------|--------------------------------| |----------|--------------------------------|
| `server` | List of [DNS Server](./server) | | `server` | List of [DNS Server](./server/) |
| `rules` | List of [DNS Rule](./rule) | | `rules` | List of [DNS Rule](./rule/) |
| `fakeip` | [FakeIP](./fakeip) | | `fakeip` | [FakeIP](./fakeip/) |
#### final #### final
@ -62,4 +62,4 @@ problematic in environments such as macOS, where DNS is proxied and cached by th
#### fakeip #### fakeip
[FakeIP](./fakeip) settings. [FakeIP](./fakeip/) settings.

View File

@ -23,8 +23,8 @@
| 键 | 格式 | | 键 | 格式 |
|----------|------------------------| |----------|------------------------|
| `server` | 一组 [DNS 服务器](./server) | | `server` | 一组 [DNS 服务器](./server/) |
| `rules` | 一组 [DNS 规则](./rule) | | `rules` | 一组 [DNS 规则](./rule/) |
#### final #### final
@ -60,4 +60,4 @@
#### fakeip #### fakeip
[FakeIP](./fakeip) 设置。 [FakeIP](./fakeip/) 设置。

View File

@ -1,3 +1,14 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [rule_set](#rule_set)
:material-plus: [source_ip_is_private](#source_ip_is_private)
:material-delete-clock: [geoip](#geoip)
:material-delete-clock: [geosite](#geosite)
### Structure ### Structure
```json ```json
@ -46,6 +57,7 @@
"10.0.0.0/24", "10.0.0.0/24",
"192.168.0.1" "192.168.0.1"
], ],
"source_ip_is_private": false,
"source_port": [ "source_port": [
12345 12345
], ],
@ -85,6 +97,10 @@
"wifi_bssid": [ "wifi_bssid": [
"00:00:00:00:00:00" "00:00:00:00:00:00"
], ],
"rule_set": [
"geoip-cn",
"geosite-cn"
],
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
@ -118,13 +134,15 @@
The default rule uses the following matching logic: The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) && (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) && (`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) && (`source_geoip` || `source_ip_cidr` `source_ip_is_private`) &&
(`source_port` || `source_port_range`) && (`source_port` || `source_port_range`) &&
`other fields` `other fields`
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
#### inbound #### inbound
Tags of [Inbound](/configuration/inbound). Tags of [Inbound](/configuration/inbound/).
#### ip_version #### ip_version
@ -166,15 +184,29 @@ Match domain using regular expression.
#### geosite #### geosite
!!! failure "Deprecated in sing-box 1.8.0"
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-sets).
Match geosite. Match geosite.
#### source_geoip #### source_geoip
!!! failure "Deprecated in sing-box 1.8.0"
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-sets).
Match source geoip. Match source geoip.
#### source_ip_cidr #### source_ip_cidr
Match source ip cidr. Match source IP CIDR.
#### source_ip_is_private
!!! question "Since sing-box 1.8.0"
Match non-public source IP.
#### source_port #### source_port
@ -250,6 +282,12 @@ Match WiFi SSID.
Match WiFi BSSID. Match WiFi BSSID.
#### rule_set
!!! question "Since sing-box 1.8.0"
Match [Rule Set](/configuration/route/#rule_set).
#### invert #### invert
Invert match result. Invert match result.
@ -286,4 +324,4 @@ Rewrite TTL in DNS responses.
#### rules #### rules
Included default rules. Included rules.

View File

@ -1,3 +1,14 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [rule_set](#rule_set)
:material-plus: [source_ip_is_private](#source_ip_is_private)
:material-delete-clock: [geoip](#geoip)
:material-delete-clock: [geosite](#geosite)
### 结构 ### 结构
```json ```json
@ -45,6 +56,7 @@
"source_ip_cidr": [ "source_ip_cidr": [
"10.0.0.0/24" "10.0.0.0/24"
], ],
"source_ip_is_private": false,
"source_port": [ "source_port": [
12345 12345
], ],
@ -84,6 +96,10 @@
"wifi_bssid": [ "wifi_bssid": [
"00:00:00:00:00:00" "00:00:00:00:00:00"
], ],
"rule_set": [
"geoip-cn",
"geosite-cn"
],
"invert": false, "invert": false,
"outbound": [ "outbound": [
"direct" "direct"
@ -115,13 +131,15 @@
默认规则使用以下匹配逻辑: 默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) && (`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) && (`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr`) && (`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
(`source_port` || `source_port_range`) && (`source_port` || `source_port_range`) &&
`other fields` `other fields`
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
#### inbound #### inbound
[入站](/zh/configuration/inbound) 标签. [入站](/zh/configuration/inbound/) 标签.
#### ip_version #### ip_version
@ -163,16 +181,30 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### geosite #### geosite
匹配 GeoSite。 !!! failure "已在 sing-box 1.8.0 废弃"
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
匹配 Geosite。
#### source_geoip #### source_geoip
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
匹配源 GeoIP。 匹配源 GeoIP。
#### source_ip_cidr #### source_ip_cidr
匹配源 IP CIDR。 匹配源 IP CIDR。
#### source_ip_is_private
!!! question "自 sing-box 1.8.0 起"
匹配非公开源 IP。
#### source_port #### source_port
匹配源端口。 匹配源端口。
@ -245,6 +277,12 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
匹配 WiFi BSSID。 匹配 WiFi BSSID。
#### rule_set
!!! question "自 sing-box 1.8.0 起"
匹配[规则集](/zh/configuration/route/#rule_set)。
#### invert #### invert
反选匹配结果。 反选匹配结果。
@ -281,4 +319,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
#### rules #### rules
包括的默认规则。 包括的规则。

View File

@ -41,24 +41,16 @@ The address of the dns server.
| `HTTP3` | `h3://8.8.8.8/dns-query` | | `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` | | `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto` or `dhcp://en0` | | `DHCP` | `dhcp://auto` or `dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` | | [FakeIP](/configuration/dns/fakeip/) | `fakeip` |
!!! warning "" !!! warning ""
To ensure that system DNS is in effect, rather than Go's built-in default resolver, enable CGO at compile time. To ensure that Android system DNS is in effect, rather than Go's built-in default resolver, enable CGO at compile time.
!!! warning ""
QUIC and HTTP3 transport is not included by default, see [Installation](./#installation).
!!! info "" !!! info ""
the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option. the RCode transport is often used to block queries. Use with rules and the `disable_cache` rule option.
!!! warning ""
DHCP transport is not included by default, see [Installation](./#installation).
| RCode | Description | | RCode | Description |
|-------------------|-----------------------| |-------------------|-----------------------|
| `success` | `No error` | | `success` | `No error` |

View File

@ -41,24 +41,16 @@ DNS 服务器的地址。
| `HTTP3` | `h3://8.8.8.8/dns-query` | | `HTTP3` | `h3://8.8.8.8/dns-query` |
| `RCode` | `rcode://refused` | | `RCode` | `rcode://refused` |
| `DHCP` | `dhcp://auto``dhcp://en0` | | `DHCP` | `dhcp://auto``dhcp://en0` |
| [FakeIP](/configuration/dns/fakeip) | `fakeip` | | [FakeIP](/configuration/dns/fakeip/) | `fakeip` |
!!! warning "" !!! warning ""
为了确保系统 DNS 生效,而不是 Go 的内置默认解析器,请在编译时启用 CGO。 为了确保 Android 系统 DNS 生效,而不是 Go 的内置默认解析器,请在编译时启用 CGO。
!!! warning ""
默认安装不包含 QUIC 和 HTTP3 传输层,请参阅 [安装](/zh/#_2)。
!!! info "" !!! info ""
RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。 RCode 传输层传输层常用于屏蔽请求. 与 DNS 规则和 `disable_cache` 规则选项一起使用。
!!! warning ""
默认安装不包含 DHCP 传输层,请参阅 [安装](/zh/#_2)。
| RCode | 描述 | | RCode | 描述 |
|-------------------|----------| |-------------------|----------|
| `success` | `无错误` | | `success` | `无错误` |

View File

@ -0,0 +1,34 @@
---
icon: material/new-box
---
!!! question "Since sing-box 1.8.0"
### Structure
```json
{
"enabled": true,
"path": "",
"cache_id": "",
"store_fakeip": false
}
```
### Fields
#### enabled
Enable cache file.
#### path
Path to the cache file.
`cache.db` will be used if empty.
#### cache_id
Identifier in cache file.
If not empty, configuration specified data will use a separate store keyed by it.

View File

@ -0,0 +1,32 @@
---
icon: material/new-box
---
!!! question "自 sing-box 1.8.0 起"
### 结构
```json
{
"enabled": true,
"path": "",
"cache_id": "",
"store_fakeip": false
}
```
### 字段
#### enabled
启用缓存文件。
#### path
缓存文件路径,默认使用`cache.db`
#### cache_id
缓存文件中的标识符。
如果不为空,配置特定的数据将使用由其键控的单独存储。

View File

@ -0,0 +1,114 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0"
:material-delete-alert: [store_mode](#store_mode)
:material-delete-alert: [store_selected](#store_selected)
:material-delete-alert: [store_fakeip](#store_fakeip)
:material-delete-alert: [cache_file](#cache_file)
:material-delete-alert: [cache_id](#cache_id)
### Structure
```json
{
"external_controller": "127.0.0.1:9090",
"external_ui": "",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "",
// Deprecated
"store_mode": false,
"store_selected": false,
"store_fakeip": false,
"cache_file": "",
"cache_id": ""
}
```
### Fields
#### external_controller
RESTful web API listening address. Clash API will be disabled if empty.
#### external_ui
A relative path to the configuration directory or an absolute path to a
directory in which you put some static web resource. sing-box will then
serve it at `http://{{external-controller}}/ui`.
#### external_ui_download_url
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
#### external_ui_download_detour
The tag of the outbound to download the external UI.
Default outbound will be used if empty.
#### secret
Secret for the RESTful API (optional)
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
#### default_mode
Default mode in clash, `Rule` will be used if empty.
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
#### store_mode
!!! failure "Deprecated in sing-box 1.8.0"
`store_mode` is deprecated in Clash API and enabled by default if `cache_file.enabled`.
Store Clash mode in cache file.
#### store_selected
!!! failure "Deprecated in sing-box 1.8.0"
`store_selected` is deprecated in Clash API and enabled by default if `cache_file.enabled`.
!!! note ""
The tag must be set for target outbounds.
Store selected outbound for the `Selector` outbound in cache file.
#### store_fakeip
!!! failure "Deprecated in sing-box 1.8.0"
`store_selected` is deprecated in Clash API and migrated to `cache_file.store_fakeip`.
Store fakeip in cache file.
#### cache_file
!!! failure "Deprecated in sing-box 1.8.0"
`cache_file` is deprecated in Clash API and migrated to `cache_file.enabled` and `cache_file.path`.
Cache file path, `cache.db` will be used if empty.
#### cache_id
!!! failure "Deprecated in sing-box 1.8.0"
`cache_id` is deprecated in Clash API and migrated to `cache_file.cache_id`.
Identifier in cache file.
If not empty, configuration specified data will use a separate store keyed by it.

View File

@ -0,0 +1,112 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改"
:material-delete-alert: [store_mode](#store_mode)
:material-delete-alert: [store_selected](#store_selected)
:material-delete-alert: [store_fakeip](#store_fakeip)
:material-delete-alert: [cache_file](#cache_file)
:material-delete-alert: [cache_id](#cache_id)
### 结构
```json
{
"external_controller": "127.0.0.1:9090",
"external_ui": "",
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "",
// Deprecated
"store_mode": false,
"store_selected": false,
"store_fakeip": false,
"cache_file": "",
"cache_id": ""
}
```
### Fields
#### external_controller
RESTful web API 监听地址。如果为空,则禁用 Clash API。
#### external_ui
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
#### external_ui_download_url
静态网页资源的 ZIP 下载 URL如果指定的 `external_ui` 目录为空,将使用。
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`
#### external_ui_download_detour
用于下载静态网页资源的出站的标签。
如果为空,将使用默认出站。
#### secret
RESTful API 的密钥(可选)
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
#### default_mode
Clash 中的默认模式,默认使用 `Rule`
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
#### store_mode
!!! failure "已在 sing-box 1.8.0 废弃"
`store_mode` 已在 Clash API 中废弃,且默认启用当 `cache_file.enabled`
将 Clash 模式存储在缓存文件中。
#### store_selected
!!! failure "已在 sing-box 1.8.0 废弃"
`store_selected` 已在 Clash API 中废弃,且默认启用当 `cache_file.enabled`
!!! note ""
必须为目标出站设置标签。
`Selector` 中出站的选定的目标出站存储在缓存文件中。
#### store_fakeip
!!! failure "已在 sing-box 1.8.0 废弃"
`store_selected` 已在 Clash API 中废弃,且已迁移到 `cache_file.store_fakeip`
将 fakeip 存储在缓存文件中。
#### cache_file
!!! failure "已在 sing-box 1.8.0 废弃"
`cache_file` 已在 Clash API 中废弃,且已迁移到 `cache_file.enabled``cache_file.path`
缓存文件路径,默认使用`cache.db`
#### cache_id
!!! failure "已在 sing-box 1.8.0 废弃"
`cache_id` 已在 Clash API 中废弃,且已迁移到 `cache_file.cache_id`
缓存 ID。
如果不为空,配置特定的数据将使用由其键控的单独存储。

View File

@ -1,139 +1,30 @@
---
icon: material/alert-decagram
---
# Experimental # Experimental
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [cache_file](#cache_file)
:material-alert-decagram: [clash_api](#clash_api)
### Structure ### Structure
```json ```json
{ {
"experimental": { "experimental": {
"clash_api": { "cache_file": {},
"external_controller": "127.0.0.1:9090", "clash_api": {},
"external_ui": "", "v2ray_api": {}
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "",
"store_mode": false,
"store_selected": false,
"store_fakeip": false,
"cache_file": "",
"cache_id": ""
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
}
} }
} }
``` ```
!!! note "" ### Fields
Traffic statistics and connection management can degrade performance. | Key | Format |
|--------------|----------------------------|
### Clash API Fields | `cache_file` | [Cache File](./cache-file/) |
| `clash_api` | [Clash API](./clash-api/) |
!!! quote "" | `v2ray_api` | [V2Ray API](./v2ray-api/) |
Clash API is not included by default, see [Installation](./#installation).
#### external_controller
RESTful web API listening address. Clash API will be disabled if empty.
#### external_ui
A relative path to the configuration directory or an absolute path to a
directory in which you put some static web resource. sing-box will then
serve it at `http://{{external-controller}}/ui`.
#### external_ui_download_url
ZIP download URL for the external UI, will be used if the specified `external_ui` directory is empty.
`https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip` will be used if empty.
#### external_ui_download_detour
The tag of the outbound to download the external UI.
Default outbound will be used if empty.
#### secret
Secret for the RESTful API (optional)
Authenticate by spedifying HTTP header `Authorization: Bearer ${secret}`
ALWAYS set a secret if RESTful API is listening on 0.0.0.0
#### default_mode
Default mode in clash, `Rule` will be used if empty.
This setting has no direct effect, but can be used in routing and DNS rules via the `clash_mode` rule item.
#### store_mode
Store Clash mode in cache file.
#### store_selected
!!! note ""
The tag must be set for target outbounds.
Store selected outbound for the `Selector` outbound in cache file.
#### store_fakeip
Store fakeip in cache file.
#### cache_file
Cache file path, `cache.db` will be used if empty.
#### cache_id
Cache ID.
If not empty, `store_selected` will use a separate store keyed by it.
### V2Ray API Fields
!!! quote ""
V2Ray API is not included by default, see [Installation](./#installation).
#### listen
gRPC API listening address. V2Ray API will be disabled if empty.
#### stats
Traffic statistics service settings.
#### stats.enabled
Enable statistics service.
#### stats.inbounds
Inbound list to count traffic.
#### stats.outbounds
Outbound list to count traffic.
#### stats.users
User list to count traffic.

View File

@ -1,137 +1,30 @@
---
icon: material/alert-decagram
---
# 实验性 # 实验性
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [cache_file](#cache_file)
:material-alert-decagram: [clash_api](#clash_api)
### 结构 ### 结构
```json ```json
{ {
"experimental": { "experimental": {
"clash_api": { "cache_file": {},
"external_controller": "127.0.0.1:9090", "clash_api": {},
"external_ui": "", "v2ray_api": {}
"external_ui_download_url": "",
"external_ui_download_detour": "",
"secret": "",
"default_mode": "",
"store_mode": false,
"store_selected": false,
"store_fakeip": false,
"cache_file": "",
"cache_id": ""
},
"v2ray_api": {
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
}
} }
} }
``` ```
!!! note "" ### 字段
流量统计和连接管理会降低性能。 | 键 | 格式 |
|--------------|--------------------------|
### Clash API 字段 | `cache_file` | [缓存文件](./cache-file/) |
| `clash_api` | [Clash API](./clash-api/) |
!!! quote "" | `v2ray_api` | [V2Ray API](./v2ray-api/) |
默认安装不包含 Clash API参阅 [安装](/zh/#_2)。
#### external_controller
RESTful web API 监听地址。如果为空,则禁用 Clash API。
#### external_ui
到静态网页资源目录的相对路径或绝对路径。sing-box 会在 `http://{{external-controller}}/ui` 下提供它。
#### external_ui_download_url
静态网页资源的 ZIP 下载 URL如果指定的 `external_ui` 目录为空,将使用。
默认使用 `https://github.com/MetaCubeX/Yacd-meta/archive/gh-pages.zip`
#### external_ui_download_detour
用于下载静态网页资源的出站的标签。
如果为空,将使用默认出站。
#### secret
RESTful API 的密钥(可选)
通过指定 HTTP 标头 `Authorization: Bearer ${secret}` 进行身份验证
如果 RESTful API 正在监听 0.0.0.0,请始终设置一个密钥。
#### default_mode
Clash 中的默认模式,默认使用 `Rule`
此设置没有直接影响,但可以通过 `clash_mode` 规则项在路由和 DNS 规则中使用。
#### store_mode
将 Clash 模式存储在缓存文件中。
#### store_selected
!!! note ""
必须为目标出站设置标签。
`Selector` 中出站的选定的目标出站存储在缓存文件中。
#### store_fakeip
将 fakeip 存储在缓存文件中。
#### cache_file
缓存文件路径,默认使用`cache.db`
#### cache_id
缓存 ID。
如果不为空,`store_selected` 将会使用以此为键的独立存储。
### V2Ray API 字段
!!! quote ""
默认安装不包含 V2Ray API参阅 [安装](/zh/#_2)。
#### listen
gRPC API 监听地址。如果为空,则禁用 V2Ray API。
#### stats
流量统计服务设置。
#### stats.enabled
启用统计服务。
#### stats.inbounds
统计流量的入站列表。
#### stats.outbounds
统计流量的出站列表。
#### stats.users
统计流量的用户列表。

View File

@ -0,0 +1,50 @@
!!! quote ""
V2Ray API is not included by default, see [Installation](/installation/build-from-source/#build-tags).
### Structure
```json
{
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
}
```
### Fields
#### listen
gRPC API listening address. V2Ray API will be disabled if empty.
#### stats
Traffic statistics service settings.
#### stats.enabled
Enable statistics service.
#### stats.inbounds
Inbound list to count traffic.
#### stats.outbounds
Outbound list to count traffic.
#### stats.users
User list to count traffic.

View File

@ -0,0 +1,50 @@
!!! quote ""
默认安装不包含 V2Ray API参阅 [安装](/zh/installation/build-from-source/#_5)。
### 结构
```json
{
"listen": "127.0.0.1:8080",
"stats": {
"enabled": true,
"inbounds": [
"socks-in"
],
"outbounds": [
"proxy",
"direct"
],
"users": [
"sekai"
]
}
}
```
### 字段
#### listen
gRPC API 监听地址。如果为空,则禁用 V2Ray API。
#### stats
流量统计服务设置。
#### stats.enabled
启用统计服务。
#### stats.inbounds
统计流量的入站列表。
#### stats.outbounds
统计流量的出站列表。
#### stats.users
统计流量的用户列表。

View File

@ -17,7 +17,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -20,7 +20,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -29,13 +29,9 @@
} }
``` ```
!!! warning ""
QUIC, which is required by hysteria is not included by default, see [Installation](./#installation).
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -29,10 +29,6 @@
} }
``` ```
!!! warning ""
默认安装不包含被 Hysteria 依赖的 QUIC参阅 [安装](/zh/#_2)。
### 监听字段 ### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。 参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@ -26,10 +26,6 @@
} }
``` ```
!!! warning ""
QUIC, which is required by Hysteria2 is not included by default, see [Installation](./#installation).
!!! warning "Difference from official Hysteria2" !!! warning "Difference from official Hysteria2"
The official program supports an authentication method called **userpass**, The official program supports an authentication method called **userpass**,
@ -39,7 +35,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -26,10 +26,6 @@
} }
``` ```
!!! warning ""
默认安装不包含被 Hysteria2 依赖的 QUIC参阅 [安装](/zh/#_2)。
!!! warning "与官方 Hysteria2 的区别" !!! warning "与官方 Hysteria2 的区别"
官方程序支持一种名为 **userpass** 的验证方式, 官方程序支持一种名为 **userpass** 的验证方式,

View File

@ -17,22 +17,22 @@
| Type | Format | Injectable | | Type | Format | Injectable |
|---------------|------------------------------|------------| |---------------|------------------------------|------------|
| `direct` | [Direct](./direct) | X | | `direct` | [Direct](./direct/) | X |
| `mixed` | [Mixed](./mixed) | TCP | | `mixed` | [Mixed](./mixed/) | TCP |
| `socks` | [SOCKS](./socks) | TCP | | `socks` | [SOCKS](./socks/) | TCP |
| `http` | [HTTP](./http) | TCP | | `http` | [HTTP](./http/) | TCP |
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP | | `shadowsocks` | [Shadowsocks](./shadowsocks/) | TCP |
| `vmess` | [VMess](./vmess) | TCP | | `vmess` | [VMess](./vmess/) | TCP |
| `trojan` | [Trojan](./trojan) | TCP | | `trojan` | [Trojan](./trojan/) | TCP |
| `naive` | [Naive](./naive) | X | | `naive` | [Naive](./naive/) | X |
| `hysteria` | [Hysteria](./hysteria) | X | | `hysteria` | [Hysteria](./hysteria/) | X |
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP | | `shadowtls` | [ShadowTLS](./shadowtls/) | TCP |
| `tuic` | [TUIC](./tuic) | X | | `tuic` | [TUIC](./tuic/) | X |
| `hysteria2` | [Hysteria2](./hysteria2) | X | | `hysteria2` | [Hysteria2](./hysteria2/) | X |
| `vless` | [VLESS](./vless) | TCP | | `vless` | [VLESS](./vless/) | TCP |
| `tun` | [Tun](./tun) | X | | `tun` | [Tun](./tun/) | X |
| `redirect` | [Redirect](./redirect) | X | | `redirect` | [Redirect](./redirect/) | X |
| `tproxy` | [TProxy](./tproxy) | X | | `tproxy` | [TProxy](./tproxy/) | X |
#### tag #### tag

View File

@ -17,22 +17,22 @@
| 类型 | 格式 | 注入支持 | | 类型 | 格式 | 注入支持 |
|---------------|------------------------------|------| |---------------|------------------------------|------|
| `direct` | [Direct](./direct) | X | | `direct` | [Direct](./direct/) | X |
| `mixed` | [Mixed](./mixed) | TCP | | `mixed` | [Mixed](./mixed/) | TCP |
| `socks` | [SOCKS](./socks) | TCP | | `socks` | [SOCKS](./socks/) | TCP |
| `http` | [HTTP](./http) | TCP | | `http` | [HTTP](./http/) | TCP |
| `shadowsocks` | [Shadowsocks](./shadowsocks) | TCP | | `shadowsocks` | [Shadowsocks](./shadowsocks/) | TCP |
| `vmess` | [VMess](./vmess) | TCP | | `vmess` | [VMess](./vmess/) | TCP |
| `trojan` | [Trojan](./trojan) | TCP | | `trojan` | [Trojan](./trojan/) | TCP |
| `naive` | [Naive](./naive) | X | | `naive` | [Naive](./naive/) | X |
| `hysteria` | [Hysteria](./hysteria) | X | | `hysteria` | [Hysteria](./hysteria/) | X |
| `shadowtls` | [ShadowTLS](./shadowtls) | TCP | | `shadowtls` | [ShadowTLS](./shadowtls/) | TCP |
| `tuic` | [TUIC](./tuic) | X | | `tuic` | [TUIC](./tuic/) | X |
| `hysteria2` | [Hysteria2](./hysteria2) | X | | `hysteria2` | [Hysteria2](./hysteria2/) | X |
| `vless` | [VLESS](./vless) | TCP | | `vless` | [VLESS](./vless/) | TCP |
| `tun` | [Tun](./tun) | X | | `tun` | [Tun](./tun/) | X |
| `redirect` | [Redirect](./redirect) | X | | `redirect` | [Redirect](./redirect/) | X |
| `tproxy` | [TProxy](./tproxy) | X | | `tproxy` | [TProxy](./tproxy/) | X |
#### tag #### tag

View File

@ -21,7 +21,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -18,13 +18,9 @@
} }
``` ```
!!! warning ""
HTTP3 transport is not included by default, see [Installation](./#installation).
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -18,10 +18,6 @@
} }
``` ```
!!! warning ""
默认安装不包含 HTTP3 传输层, 参阅 [安装](/zh/#_2)。
### 监听字段 ### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。 参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@ -15,4 +15,4 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.

View File

@ -50,7 +50,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -50,7 +50,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### 字段 ### 字段

View File

@ -35,7 +35,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields
@ -66,11 +66,11 @@ Only available in the ShadowTLS protocol 3.
==Required== ==Required==
Handshake server address and [Dial options](/configuration/shared/dial). Handshake server address and [Dial options](/configuration/shared/dial/).
#### handshake_for_server_name #### handshake_for_server_name
Handshake server address and [Dial options](/configuration/shared/dial) for specific server name. Handshake server address and [Dial options](/configuration/shared/dial/) for specific server name.
Only available in the ShadowTLS protocol 2/3. Only available in the ShadowTLS protocol 2/3.

View File

@ -20,7 +20,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -17,7 +17,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -31,7 +31,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields
@ -65,4 +65,4 @@ See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).

View File

@ -67,4 +67,4 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### transport #### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。 V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。

View File

@ -22,13 +22,9 @@
} }
``` ```
!!! warning ""
QUIC, which is required by TUIC is not included by default, see [Installation](./#installation).
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields

View File

@ -22,10 +22,6 @@
} }
``` ```
!!! warning ""
默认安装不包含被 TUI 依赖的 QUIC参阅 [安装](/zh/#_2)。
### 监听字段 ### 监听字段
参阅 [监听字段](/zh/configuration/shared/listen/)。 参阅 [监听字段](/zh/configuration/shared/listen/)。

View File

@ -1,3 +1,12 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [gso](#gso)
:material-alert-decagram: [stack](#stack)
!!! quote "" !!! quote ""
Only supported on Linux, Windows and macOS. Only supported on Linux, Windows and macOS.
@ -12,6 +21,7 @@
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126", "inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000, "mtu": 9000,
"gso": false,
"auto_route": true, "auto_route": true,
"strict_route": true, "strict_route": true,
"inet4_route_address": [ "inet4_route_address": [
@ -98,6 +108,16 @@ IPv6 prefix for the tun interface.
The maximum transmission unit. The maximum transmission unit.
#### gso
!!! question "Since sing-box 1.8.0"
!!! quote ""
Only supported on Linux.
Enable generic segmentation offload.
#### auto_route #### auto_route
Set the default route to the Tun. Set the default route to the Tun.
@ -160,18 +180,19 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
#### stack #### stack
!!! quote "Changes in sing-box 1.8.0"
:material-delete-alert: The legacy LWIP stack has been deprecated and removed.
TCP/IP stack. TCP/IP stack.
| Stack | Description | Status | | Stack | Description |
|--------|----------------------------------------------------------------------------------|-------------------| |----------|-------------------------------------------------------------------------------------------------------|
| system | Sometimes better performance | recommended | | `system` | Perform L3 to L4 translation using the system network stack |
| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended | | `gvisor` | Perform L3 to L4 translation using [gVisor](https://github.com/google/gvisor)'s virtual network stack |
| mixed | Mixed `system` TCP stack and `gVisor` UDP stack | recommended | | `mixed` | Mixed `system` TCP stack and `gvisor` UDP stack |
| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
!!! warning "" Defaults to the `mixed` stack if the gVisor build tag is enabled, otherwise defaults to the `system` stack.
gVisor and LWIP stacks is not included by default, see [Installation](./#installation).
#### include_interface #### include_interface
@ -217,10 +238,10 @@ Exclude users in route, but in range.
Limit android users in route. Limit android users in route.
| Common user | ID | | Common user | ID |
|--------------|-----| |--------------|----|
| Main | 0 | | Main | 0 |
| Work Profile | 10 | | Work Profile | 10 |
#### include_package #### include_package
@ -240,4 +261,4 @@ System HTTP proxy settings.
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.

View File

@ -1,3 +1,12 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [gso](#gso)
:material-alert-decagram: [stack](#stack)
!!! quote "" !!! quote ""
仅支持 Linux、Windows 和 macOS。 仅支持 Linux、Windows 和 macOS。
@ -12,6 +21,7 @@
"inet4_address": "172.19.0.1/30", "inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126", "inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000, "mtu": 9000,
"gso": false,
"auto_route": true, "auto_route": true,
"strict_route": true, "strict_route": true,
"inet4_route_address": [ "inet4_route_address": [
@ -98,6 +108,16 @@ tun 接口的 IPv6 前缀。
最大传输单元。 最大传输单元。
#### gso
!!! question "自 sing-box 1.8.0 起"
!!! quote ""
仅支持 Linux。
启用通用分段卸载。
#### auto_route #### auto_route
设置到 Tun 的默认路由。 设置到 Tun 的默认路由。
@ -157,17 +177,19 @@ UDP NAT 过期时间,以秒为单位,默认为 3005 分钟)。
#### stack #### stack
!!! quote "sing-box 1.8.0 中的更改"
:material-delete-alert: 旧的 LWIP 栈已被弃用并移除。
TCP/IP 栈。 TCP/IP 栈。
| 栈 | 描述 | 状态 | | 栈 | 描述 |
|-------------|--------------------------------------------------------------------------|-------| |--------|------------------------------------------------------------------|
| system (默认) | 有时性能更好 | 推荐 | | system | 基于系统网络栈执行 L3 到 L4 转换 |
| gVisor | 兼容性较好,基于 [google/gvisor](https://github.com/google/gvisor) | 推荐 | | gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
| LWIP | 基于 [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | 上游已存档 | | mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
!!! warning "" 默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
默认安装不包含 gVisor 和 LWIP 栈,请参阅 [安装](/zh/#_2)。
#### include_interface #### include_interface
@ -214,8 +236,8 @@ TCP/IP 栈。
限制被路由的 Android 用户。 限制被路由的 Android 用户。
| 常用用户 | ID | | 常用用户 | ID |
|--|-----| |------|----|
| 您 | 0 | | 您 | 0 |
| 工作资料 | 10 | | 工作资料 | 10 |
#### include_package #### include_package

View File

@ -22,7 +22,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields
@ -56,4 +56,4 @@ See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).

View File

@ -56,4 +56,4 @@ TLS 配置, 参阅 [TLS](/zh/configuration/shared/tls/#inbound)。
#### transport #### transport
V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport)。 V2Ray 传输配置,参阅 [V2Ray 传输层](/zh/configuration/shared/v2ray-transport/)。

View File

@ -22,7 +22,7 @@
### Listen Fields ### Listen Fields
See [Listen Fields](/configuration/shared/listen) for details. See [Listen Fields](/configuration/shared/listen/) for details.
### Fields ### Fields
@ -51,4 +51,4 @@ See [Multiplex](/configuration/shared/multiplex#inbound) for details.
#### transport #### transport
V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport). V2Ray Transport configuration, see [V2Ray Transport](/configuration/shared/v2ray-transport/).

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