Compare commits

..

9 Commits

Author SHA1 Message Date
世界
039c7e5bcb documentation: Bump version 2023-11-30 16:29:52 +08:00
世界
218567be46 Independent source_ip_is_private and ip_is_private rules 2023-11-30 16:29:52 +08:00
世界
889f426939 Make rule-set initialization parallel 2023-11-30 16:29:52 +08:00
世界
cb28aba697 documentation: Update rule-set example 2023-11-30 11:05:46 +08:00
世界
bbc1d12015 documentation: Bump version 2023-11-30 10:31:18 +08:00
世界
249e501754 documentation: Add rule set 2023-11-30 10:30:58 +08:00
世界
4e4c0820d5 Add rule set 2023-11-30 10:30:58 +08:00
世界
33881ebd8c Allow nested logical rules 2023-11-30 10:30:57 +08:00
世界
4d6b7ff054 Migrate to independent cache file 2023-11-30 10:30:57 +08:00
193 changed files with 2699 additions and 3597 deletions

View File

@@ -44,7 +44,13 @@ body:
attributes:
label: Version
description: If you are using the original command line program, please provide the output of the `sing-box version` command.
render: shell
value: |-
<details>
```console
# Replace this line with the output
```
</details>
- type: textarea
attributes:
label: Description
@@ -64,4 +70,10 @@ body:
If you encounter a crash with the graphical client, please provide crash logs.
For Apple platform clients, please check `Settings - View Service Log` for crash logs.
For the Android client, please check the `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` file for crash logs.
render: shell
value: |-
<details>
```console
# Replace this line with logs
```
</details>

View File

@@ -44,7 +44,13 @@ body:
attributes:
label: 版本
description: 如果您使用原始命令行程序,请提供 `sing-box version` 命令的输出。
render: shell
value: |-
<details>
```console
# 使用输出内容覆盖此行
```
</details>
- type: textarea
attributes:
label: 描述
@@ -64,18 +70,10 @@ body:
如果您遭遇图形界面应用程序崩溃,请提供崩溃日志。
对于 Apple 平台图形客户端程序,请检查 `Settings - View Service Log` 以导出崩溃日志。
对于 Android 图形客户端程序,请检查 `/sdcard/Android/data/io.nekohasekai.sfa/files/stderr.log` 文件以导出崩溃日志。
render: shell
- type: checkboxes
attributes:
label: 完整性要求
description: 我保证我提供了完整的可以在本地重现该问题的服务器、客户端配置文件与流程,而不是一个脱敏的复杂客户端配置文件,否则该 issue 将被关闭。
options:
- label: 我保证
required: true
- type: checkboxes
attributes:
label: 负责性要求
description: 我保证我阅读了文档,了解所有我编写的配置文件项的含义,而不是大量堆砌看似有用的选项或默认值,否则该 issue 将被关闭。
options:
- label: 我保证
required: true
value: |-
<details>
```console
# 使用日志内容覆盖此行
```
</details>

View File

@@ -30,7 +30,7 @@ jobs:
run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Add cache to Go proxy
@@ -54,7 +54,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.18.10
- name: Cache go module
@@ -74,7 +74,7 @@ jobs:
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.20.7
- name: Cache go module
@@ -209,7 +209,7 @@ jobs:
run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: Build

View File

@@ -30,7 +30,7 @@ jobs:
run: |
echo go_version=$(curl -s https://raw.githubusercontent.com/actions/go-versions/main/versions-manifest.json | grep -oE '"version": "[0-9]{1}.[0-9]{1,}(.[0-9]{1,})?"' | head -1 | cut -d':' -f2 | sed 's/ //g; s/"//g') >> $GITHUB_OUTPUT
- name: Setup Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: ${{ steps.version.outputs.go_version }}
- name: golangci-lint

View File

@@ -8,7 +8,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v8
with:
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 5 days'
days-before-stale: 60

View File

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

View File

@@ -109,7 +109,7 @@ type OutboundGroup interface {
type URLTestGroup interface {
OutboundGroup
URLTest(ctx context.Context) (map[string]uint16, error)
URLTest(ctx context.Context, url string) (map[string]uint16, error)
}
func OutboundTag(detour Outbound) string {

View File

@@ -46,24 +46,12 @@ type InboundContext struct {
SourceGeoIPCode string
GeoIPCode string
ProcessInfo *process.Info
QueryType uint16
FakeIP bool
IPCIDRMatchSource bool
// rule cache
// dns cache
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
QueryType uint16
}
type inboundContextKey struct{}

View File

@@ -17,7 +17,6 @@ import (
type Router interface {
Service
PostStarter
Outbounds() []Outbound
Outbound(tag string) (Outbound, bool)
@@ -87,17 +86,10 @@ type DNSRule interface {
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()

66
box.go
View File

@@ -9,8 +9,6 @@ import (
"time"
"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/cachefile"
"github.com/sagernet/sing-box/experimental/libbox/platform"
@@ -157,7 +155,7 @@ func New(options Options) (*Box, error) {
preServices2 := make(map[string]adapter.Service)
postServices := make(map[string]adapter.Service)
if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
cacheFile := cachefile.NewCacheFile(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
preServices1["cache file"] = cacheFile
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
}
@@ -232,34 +230,25 @@ func (s *Box) Start() error {
}
func (s *Box) preStart() error {
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 {
monitor.Start("pre-start ", serviceName)
s.logger.Trace("pre-start ", serviceName)
err := preService.PreStart()
monitor.Finish()
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
return E.Cause(err, "pre-starting ", serviceName)
}
}
}
for serviceName, service := range s.preServices2 {
if preService, isPreService := service.(adapter.PreStarter); isPreService {
monitor.Start("pre-start ", serviceName)
s.logger.Trace("pre-start ", serviceName)
err := preService.PreStart()
monitor.Finish()
if err != nil {
return E.Cause(err, "pre-start ", serviceName)
return E.Cause(err, "pre-starting ", serviceName)
}
}
}
err = s.startOutbounds()
err := s.startOutbounds()
if err != nil {
return err
}
@@ -272,12 +261,14 @@ func (s *Box) start() error {
return err
}
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 {
s.logger.Trace("starting ", serviceName)
err = service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
@@ -290,34 +281,33 @@ func (s *Box) start() error {
} else {
tag = in.Tag()
}
s.logger.Trace("initializing inbound/", in.Type(), "[", tag, "]")
err = in.Start()
if err != nil {
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
}
}
return s.postStart()
return nil
}
func (s *Box) postStart() error {
for serviceName, service := range s.postServices {
s.logger.Trace("starting ", service)
err := service.Start()
if err != nil {
return E.Cause(err, "start ", serviceName)
}
}
for _, outbound := range s.outbounds {
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
err := lateOutbound.PostStart()
for serviceName, service := range s.outbounds {
if lateService, isLateService := service.(adapter.PostStarter); isLateService {
s.logger.Trace("post-starting ", service)
err := lateService.PostStart()
if err != nil {
return E.Cause(err, "post-start outbound/", outbound.Tag())
return E.Cause(err, "post-start ", serviceName)
}
}
}
err := s.router.PostStart()
if err != nil {
return E.Cause(err, "post-start router")
}
return s.router.PostStart()
return nil
}
func (s *Box) Close() error {
@@ -327,53 +317,47 @@ func (s *Box) Close() error {
default:
close(s.done)
}
monitor := taskmonitor.New(s.logger, C.DefaultStopTimeout)
var errors error
for serviceName, service := range s.postServices {
monitor.Start("close ", serviceName)
s.logger.Trace("closing ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
monitor.Finish()
}
for i, in := range s.inbounds {
monitor.Start("close inbound/", in.Type(), "[", i, "]")
s.logger.Trace("closing inbound/", in.Type(), "[", i, "]")
errors = E.Append(errors, in.Close(), func(err error) error {
return E.Cause(err, "close inbound/", in.Type(), "[", i, "]")
})
monitor.Finish()
}
for i, out := range s.outbounds {
monitor.Start("close outbound/", out.Type(), "[", i, "]")
s.logger.Trace("closing outbound/", out.Type(), "[", i, "]")
errors = E.Append(errors, common.Close(out), func(err error) error {
return E.Cause(err, "close outbound/", out.Type(), "[", i, "]")
})
monitor.Finish()
}
monitor.Start("close router")
s.logger.Trace("closing router")
if err := common.Close(s.router); err != nil {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close router")
})
}
monitor.Finish()
for serviceName, service := range s.preServices1 {
monitor.Start("close ", serviceName)
s.logger.Trace("closing ", serviceName)
errors = E.Append(errors, service.Close(), func(err error) error {
return E.Cause(err, "close ", serviceName)
})
monitor.Finish()
}
for serviceName, service := range s.preServices2 {
monitor.Start("close ", serviceName)
s.logger.Trace("closing ", 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 {
errors = E.Append(errors, err, func(err error) error {
return E.Cause(err, "close logger")
return E.Cause(err, "close log factory")
})
}
return errors

View File

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

View File

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

View File

@@ -5,10 +5,9 @@ import (
"os"
"path/filepath"
"github.com/sagernet/sing-box/common/json"
"github.com/sagernet/sing-box/log"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra"
)
@@ -38,10 +37,6 @@ func format() error {
return err
}
for _, optionsEntry := range optionsList {
optionsEntry.options, err = badjson.Omitempty(optionsEntry.options)
if err != nil {
return err
}
buffer := new(bytes.Buffer)
encoder := json.NewEncoder(buffer)
encoder.SetIndent("", " ")

View File

@@ -6,11 +6,11 @@ import (
"os"
"strings"
"github.com/sagernet/sing-box/common/json"
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"

View File

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

View File

@@ -5,10 +5,10 @@ import (
"os"
"strings"
"github.com/sagernet/sing-box/common/json"
"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"
)

View File

@@ -6,10 +6,10 @@ import (
"os"
"path/filepath"
"github.com/sagernet/sing-box/common/json"
"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"
)

View File

@@ -13,12 +13,10 @@ import (
"time"
"github.com/sagernet/sing-box"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/common/badjsonmerge"
"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/sagernet/sing/common/json/badjson"
"github.com/spf13/cobra"
)
@@ -57,7 +55,8 @@ func readConfigAt(path string) (*OptionsEntry, error) {
if err != nil {
return nil, E.Cause(err, "read config at ", path)
}
options, err := json.UnmarshalExtended[option.Options](configContent)
var options option.Options
err = options.UnmarshalJSON(configContent)
if err != nil {
return nil, E.Cause(err, "decode config at ", path)
}
@@ -107,18 +106,13 @@ func readConfigAndMerge() (option.Options, error) {
if len(optionsList) == 1 {
return optionsList[0].options, nil
}
var mergedMessage json.RawMessage
var mergedOptions option.Options
for _, options := range optionsList {
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage)
mergedOptions, err = badjsonmerge.MergeOptions(options.options, mergedOptions)
if err != nil {
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
}
@@ -199,7 +193,7 @@ func run() error {
}
func closeMonitor(ctx context.Context) {
time.Sleep(C.DefaultStopFatalTimeout)
time.Sleep(3 * time.Second)
select {
case <-ctx.Done():
return

View File

@@ -1,7 +1,6 @@
package main
import (
"context"
"os"
"time"
@@ -38,7 +37,7 @@ func main() {
func preRun(cmd *cobra.Command, args []string) {
if disableColor {
log.SetStdLogger(log.NewDefaultFactory(context.Background(), log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, "", nil, false).Logger())
log.SetStdLogger(log.NewFactory(log.Formatter{BaseTime: time.Now(), DisableColors: true}, os.Stderr, nil).Logger())
}
if workingDir != "" {
_, err := os.Stat(workingDir)

46
common/badjson/array.go Normal file
View File

@@ -0,0 +1,46 @@
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
}

54
common/badjson/json.go Normal file
View File

@@ -0,0 +1,54 @@
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
}

79
common/badjson/object.go Normal file
View File

@@ -0,0 +1,79 @@
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

@@ -0,0 +1,80 @@
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

@@ -0,0 +1,59 @@
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)
}

233
common/badtls/badtls.go Normal file
View File

@@ -0,0 +1,233 @@
//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

@@ -0,0 +1,14 @@
//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
}

22
common/badtls/link.go Normal file
View File

@@ -0,0 +1,22 @@
//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

View File

@@ -1,115 +0,0 @@
//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

@@ -1,13 +0,0 @@
//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
import "github.com/sagernet/sing/common/json"
import "github.com/sagernet/sing-box/common/json"
func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())

View File

@@ -6,6 +6,7 @@ import (
"sync"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing/common/bufio/deadline"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -44,7 +45,14 @@ func (d *DetourDialer) DialContext(ctx context.Context, network string, destinat
if err != nil {
return nil, err
}
return dialer.DialContext(ctx, network, destination)
conn, err := dialer.DialContext(ctx, network, destination)
if err != nil {
return nil, err
}
if deadline.NeedAdditionalReadDeadline(conn) {
conn = deadline.NewConn(conn)
}
return conn, nil
}
func (d *DetourDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) {

128
common/json/comment.go Normal file
View File

@@ -0,0 +1,128 @@
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
}

18
common/json/std.go Normal file
View File

@@ -0,0 +1,18 @@
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
)

View File

@@ -109,10 +109,8 @@ func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err er
}
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)

View File

@@ -1,31 +0,0 @@
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,7 +6,6 @@ import (
"os"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/badtls"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
M "github.com/sagernet/sing/common/metadata"
@@ -43,17 +42,7 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {
ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout)
defer cancel()
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
return aTLS.ClientHandshake(ctx, conn, config)
}
type Dialer struct {

View File

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

View File

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

View File

@@ -219,16 +219,6 @@ func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
switch name {
case "chrome", "":
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":
return utls.HelloFirefox_Auto, nil
case "edge":

View File

@@ -3,15 +3,11 @@ package constant
import "time"
const (
TCPTimeout = 5 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute
DefaultStartTimeout = 10 * time.Second
DefaultStopTimeout = 5 * time.Second
DefaultStopFatalTimeout = 10 * time.Second
TCPTimeout = 5 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
QUICTimeout = 30 * time.Second
STUNTimeout = 15 * time.Second
UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 1 * time.Minute
)

View File

@@ -6,12 +6,12 @@ import (
"runtime"
"runtime/debug"
"github.com/sagernet/sing-box/common/badjson"
"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/option"
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"
)

View File

@@ -2,135 +2,16 @@
icon: material/alert-decagram
---
#### 1.8.0-beta.4
# ChangeLog
* Fixes and improvements
#### 1.8.0-beta.2
* Fix GSO support
* Fixes and improvements
#### 1.7.5
* 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
* Fixes and improvements
_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._
#### 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
* 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
* 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
#### 1.8.0-alpha.2
* 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.
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
@@ -201,14 +82,11 @@ The new HTTPUpgrade transport has better performance than WebSocket and is bette
**3**:
Starting in 1.7.0, multiplexing support is no longer enabled by default
and needs to be turned on explicitly in inbound
options.
Starting in 1.7.0, multiplexing support is no longer enabled by default and needs to be turned on explicitly in inbound options.
**4**
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.
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.
**5**:
@@ -384,8 +262,7 @@ When `auto_route` is enabled and `strict_route` is disabled, the device can now
**2**:
Built using Go 1.20, the last version that will run on
Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High
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.
#### 1.6.0-rc.4
@@ -399,8 +276,7 @@ Sierra, 10.14 Mojave.
**1**:
Built using Go 1.20, the last version that will run on
Windows 7, 8, Server 2008, Server 2012 and macOS 10.13 High
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.
#### 1.6.0-beta.4

View File

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

View File

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

View File

@@ -134,12 +134,10 @@ icon: material/alert-decagram
The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr` `source_ip_is_private`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
#### inbound
Tags of [Inbound](/configuration/inbound).
@@ -186,7 +184,7 @@ Match domain using regular expression.
!!! 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).
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-set).
Match geosite.
@@ -194,7 +192,7 @@ Match geosite.
!!! 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).
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-set).
Match source geoip.

View File

@@ -131,12 +131,10 @@ icon: material/alert-decagram
默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
#### inbound
[入站](/zh/configuration/inbound) 标签.
@@ -183,7 +181,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! failure "已在 sing-box 1.8.0 废弃"
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geosite-to-rule-set)。
匹配 Geosite。
@@ -191,7 +189,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
GeoIp 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geoip-to-rule-set)。
匹配源 GeoIP。

View File

@@ -45,12 +45,20 @@ The address of the dns server.
!!! warning ""
To ensure that Android system DNS is in effect, rather than Go's built-in default resolver, enable CGO at compile time.
To ensure that 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 ""
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 |
|-------------------|-----------------------|
| `success` | `No error` |

View File

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

View File

@@ -1,32 +0,0 @@
---
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

@@ -10,6 +10,11 @@ icon: material/alert-decagram
:material-delete-alert: [cache_file](#cache_file)
:material-delete-alert: [cache_id](#cache_id)
!!! quote ""
Clash API is not included by default, see [Installation](./#installation).
### Structure
```json
@@ -43,6 +48,8 @@ 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.
@@ -111,4 +118,4 @@ Cache file path, `cache.db` will be used if empty.
Identifier in cache file.
If not empty, configuration specified data will use a separate store keyed by it.
If not empty, configuration specified data will use a separate store keyed by it.

View File

@@ -1,112 +0,0 @@
---
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,30 +0,0 @@
---
icon: material/alert-decagram
---
# 实验性
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [cache_file](#cache_file)
:material-alert-decagram: [clash_api](#clash_api)
### 结构
```json
{
"experimental": {
"cache_file": {},
"clash_api": {},
"v2ray_api": {}
}
}
```
### 字段
| 键 | 格式 |
|--------------|--------------------------|
| `cache_file` | [缓存文件](./cache-file) |
| `clash_api` | [Clash API](./clash-api) |
| `v2ray_api` | [V2Ray API](./v2ray-api) |

View File

@@ -1,8 +1,8 @@
### Structure
!!! quote ""
V2Ray API is not included by default, see [Installation](/installation/build-from-source/#build-tags).
### Structure
V2Ray API is not included by default, see [Installation](./#installation).
```json
{

View File

@@ -1,50 +0,0 @@
!!! 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

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

View File

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

View File

@@ -26,6 +26,10 @@
}
```
!!! warning ""
QUIC, which is required by Hysteria2 is not included by default, see [Installation](./#installation).
!!! warning "Difference from official Hysteria2"
The official program supports an authentication method called **userpass**,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [gso](#gso)
:material-plus: [gso_max_size](#gso_max_size)
:material-alert-decagram: [stack](#stack)
!!! quote ""
Only supported on Linux, Windows and macOS.
@@ -22,8 +12,6 @@ icon: material/alert-decagram
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126",
"mtu": 9000,
"gso": false,
"gso_max_size": 65536,
"auto_route": true,
"strict_route": true,
"inet4_route_address": [
@@ -110,28 +98,6 @@ IPv6 prefix for the tun interface.
The maximum transmission unit.
#### gso
!!! question "Since sing-box 1.8.0"
!!! quote ""
Only supported on Linux.
Enable generic segmentation offload.
#### gso_max_size
!!! question "Since sing-box 1.8.0"
!!! quote ""
Only supported on Linux.
Maximum GSO packet size.
`65536` is used by default.
#### auto_route
Set the default route to the Tun.
@@ -194,19 +160,18 @@ UDP NAT expiration time in seconds, default is 300 (5 minutes).
#### stack
!!! quote "Changes in sing-box 1.8.0"
:material-delete-alert: The legacy LWIP stack has been deprecated and removed.
TCP/IP stack.
| Stack | Description |
|----------|-------------------------------------------------------------------------------------------------------|
| `system` | Perform L3 to L4 translation using the system network stack |
| `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 |
| Stack | Description | Status |
|--------|----------------------------------------------------------------------------------|-------------------|
| system | Sometimes better performance | recommended |
| gVisor | Better compatibility, based on [google/gvisor](https://github.com/google/gvisor) | recommended |
| mixed | Mixed `system` TCP stack and `gVisor` UDP stack | recommended |
| LWIP | Based on [eycorsican/go-tun2socks](https://github.com/eycorsican/go-tun2socks) | upstream archived |
Defaults to the `mixed` stack if the gVisor build tag is enabled, otherwise defaults to the `system` stack.
!!! warning ""
gVisor and LWIP stacks is not included by default, see [Installation](./#installation).
#### include_interface
@@ -252,10 +217,10 @@ Exclude users in route, but in range.
Limit android users in route.
| Common user | ID |
|--------------|----|
| Main | 0 |
| Work Profile | 10 |
| Common user | ID |
|--------------|-----|
| Main | 0 |
| Work Profile | 10 |
#### include_package

View File

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

View File

@@ -24,6 +24,10 @@
}
```
!!! warning ""
QUIC, which is required by hysteria is not included by default, see [Installation](./#installation).
### Fields
#### server

View File

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

View File

@@ -22,6 +22,10 @@
}
```
!!! warning ""
QUIC, which is required by Hysteria2 is not included by default, see [Installation](./#installation).
!!! warning "Difference from official Hysteria2"
The official Hysteria2 supports an authentication method called **userpass**,

View File

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

View File

@@ -26,6 +26,7 @@
| `trojan` | [Trojan](./trojan) |
| `wireguard` | [Wireguard](./wireguard) |
| `hysteria` | [Hysteria](./hysteria) |
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `shadowtls` | [ShadowTLS](./shadowtls) |
| `tuic` | [TUIC](./tuic) |

View File

@@ -26,6 +26,7 @@
| `trojan` | [Trojan](./trojan) |
| `wireguard` | [Wireguard](./wireguard) |
| `hysteria` | [Hysteria](./hysteria) |
| `shadowsocksr` | [ShadowsocksR](./shadowsocksr) |
| `vless` | [VLESS](./vless) |
| `shadowtls` | [ShadowTLS](./shadowtls) |
| `tuic` | [TUIC](./tuic) |

View File

@@ -0,0 +1,106 @@
### Structure
```json
{
"type": "shadowsocksr",
"tag": "ssr-out",
"server": "127.0.0.1",
"server_port": 1080,
"method": "aes-128-cfb",
"password": "8JCsPssfgS8tiRwiMlhARg==",
"obfs": "plain",
"obfs_param": "",
"protocol": "origin",
"protocol_param": "",
"network": "udp",
... // Dial Fields
}
```
!!! warning ""
The ShadowsocksR protocol is obsolete and unmaintained. This outbound is provided for compatibility only.
!!! warning ""
ShadowsocksR is not included by default, see [Installation](./#installation).
### Fields
#### server
==Required==
The server address.
#### server_port
==Required==
The server port.
#### method
==Required==
Encryption methods:
* `aes-128-ctr`
* `aes-192-ctr`
* `aes-256-ctr`
* `aes-128-cfb`
* `aes-192-cfb`
* `aes-256-cfb`
* `rc4-md5`
* `chacha20-ietf`
* `xchacha20`
#### password
==Required==
The shadowsocks password.
#### obfs
The ShadowsocksR obfuscate.
* plain
* http_simple
* http_post
* random_head
* tls1.2_ticket_auth
#### obfs_param
The ShadowsocksR obfuscate parameter.
#### protocol
The ShadowsocksR protocol.
* origin
* verify_sha1
* auth_sha1_v4
* auth_aes128_md5
* auth_aes128_sha1
* auth_chain_a
* auth_chain_b
#### protocol_param
The ShadowsocksR protocol parameter.
#### network
Enabled network
One of `tcp` `udp`.
Both is enabled by default.
### Dial Fields
See [Dial Fields](/configuration/shared/dial) for details.

View File

@@ -0,0 +1,106 @@
### 结构
```json
{
"type": "shadowsocksr",
"tag": "ssr-out",
"server": "127.0.0.1",
"server_port": 1080,
"method": "aes-128-cfb",
"password": "8JCsPssfgS8tiRwiMlhARg==",
"obfs": "plain",
"obfs_param": "",
"protocol": "origin",
"protocol_param": "",
"network": "udp",
... // 拨号字段
}
```
!!! warning ""
ShadowsocksR 协议已过时且无人维护。 提供此出站仅出于兼容性目的。
!!! warning ""
默认安装不包含被 ShadowsocksR参阅 [安装](/zh/#_2)。
### 字段
#### server
==必填==
服务器地址。
#### server_port
==必填==
服务器端口。
#### method
==必填==
加密方法:
* `aes-128-ctr`
* `aes-192-ctr`
* `aes-256-ctr`
* `aes-128-cfb`
* `aes-192-cfb`
* `aes-256-cfb`
* `rc4-md5`
* `chacha20-ietf`
* `xchacha20`
#### password
==必填==
Shadowsocks 密码。
#### obfs
ShadowsocksR 混淆。
* plain
* http_simple
* http_post
* random_head
* tls1.2_ticket_auth
#### obfs_param
ShadowsocksR 混淆参数。
#### protocol
ShadowsocksR 协议。
* origin
* verify_sha1
* auth_sha1_v4
* auth_aes128_md5
* auth_aes128_sha1
* auth_chain_a
* auth_chain_b
#### protocol_param
ShadowsocksR 协议参数。
#### network
启用的网络协议
`tcp``udp`
默认所有。
### 拨号字段
参阅 [拨号字段](/zh/configuration/shared/dial/)。

View File

@@ -18,7 +18,7 @@
!!! info ""
Embedded Tor is not included by default, see [Installation](/installation/build-from-source/#build-tags).
Embedded tor is not included by default, see [Installation](./#installation).
### Fields

View File

@@ -18,7 +18,7 @@
!!! info ""
默认安装不包含嵌入式 Tor, 参阅 [安装](/zh/installation/build-from-source/#_5)。
默认安装不包含嵌入式 Tor, 参阅 [安装](/zh/#_2)。
### 字段

View File

@@ -21,6 +21,10 @@
}
```
!!! warning ""
QUIC, which is required by TUIC is not included by default, see [Installation](./#installation).
### Fields
#### server

View File

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

View File

@@ -10,10 +10,9 @@
"proxy-b",
"proxy-c"
],
"url": "",
"interval": "",
"tolerance": 0,
"idle_timeout": "",
"url": "https://www.gstatic.com/generate_204",
"interval": "1m",
"tolerance": 50,
"interrupt_exist_connections": false
}
```
@@ -32,16 +31,12 @@ The URL to test. `https://www.gstatic.com/generate_204` will be used if empty.
#### interval
The test interval. `3m` will be used if empty.
The test interval. `1m` will be used if empty.
#### tolerance
The test tolerance in milliseconds. `50` will be used if empty.
#### idle_timeout
The idle timeout. `30m` will be used if empty.
#### interrupt_exist_connections
Interrupt existing connections when the selected outbound has changed.

View File

@@ -10,10 +10,9 @@
"proxy-b",
"proxy-c"
],
"url": "",
"interval": "",
"url": "https://www.gstatic.com/generate_204",
"interval": "1m",
"tolerance": 50,
"idle_timeout": "",
"interrupt_exist_connections": false
}
```
@@ -32,16 +31,12 @@
#### interval
测试间隔。 默认使用 `3m`
测试间隔。 默认使用 `1m`
#### tolerance
以毫秒为单位的测试容差。 默认使用 `50`
#### idle_timeout
空闲超时。默认使用 `30m`
#### interrupt_exist_connections
当选定的出站发生更改时,中断现有连接。

View File

@@ -1,12 +1,3 @@
---
icon: material/new-box
---
!!! quote "Changes in sing-box 1.8.0"
:material-plus: [gso](#gso)
:material-plus: [gso_max_size](#gso_max_size)
### Structure
```json
@@ -17,8 +8,6 @@ icon: material/new-box
"server": "127.0.0.1",
"server_port": 1080,
"system_interface": false,
"gso": false,
"gso_max_size": 65536,
"interface_name": "wg0",
"local_address": [
"10.0.0.2/32"
@@ -47,6 +36,14 @@ icon: material/new-box
}
```
!!! warning ""
WireGuard is not included by default, see [Installation](./#installation).
!!! warning ""
gVisor, which is required by the unprivileged WireGuard is not included by default, see [Installation](./#installation).
### Fields
#### server
@@ -63,37 +60,15 @@ The server port.
#### system_interface
Use system interface.
Use system tun support.
Requires privilege and cannot conflict with exists system interfaces.
Requires privilege and cannot conflict with system interfaces.
Forced if gVisor not included in the build.
#### interface_name
Custom interface name for system interface.
#### gso
!!! question "Since sing-box 1.8.0"
!!! quote ""
Only supported on Linux.
Enable generic segmentation offload for system interface.
#### gso_max_size
!!! question "Since sing-box 1.8.0"
!!! quote ""
Only supported on Linux.
Maximum GSO packet size.
`65536` is used by default.
Custom device name when `system_interface` enabled.
#### local_address

View File

@@ -1,12 +1,3 @@
---
icon: material/new-box
---
!!! quote "sing-box 1.8.0 中的更改"
:material-plus: [gso](#gso)
:material-plus: [gso_max_size](#gso_max_size)
### 结构
```json
@@ -17,8 +8,6 @@ icon: material/new-box
"server": "127.0.0.1",
"server_port": 1080,
"system_interface": false,
"gso": false,
"gso_max_size": 65536,
"interface_name": "wg0",
"local_address": [
"10.0.0.2/32"
@@ -35,6 +24,14 @@ icon: material/new-box
}
```
!!! warning ""
默认安装不包含 WireGuard, 参阅 [安装](/zh/#_2)。
!!! warning ""
默认安装不包含被非特权 WireGuard 需要的 gVisor, 参阅 [安装](/zh/#_2)。
### 字段
#### server
@@ -51,37 +48,15 @@ icon: material/new-box
#### system_interface
使用系统设备
使用系统 tun 支持
需要特权且不能与已有系统接口冲突。
需要特权且不能与系统接口冲突。
如果 gVisor 未包含在构建中,则强制执行。
#### interface_name
为系统接口自定义设备名称。
#### gso
!!! question "自 sing-box 1.8.0 起"
!!! quote ""
仅支持 Linux。
为系统接口启用通用分段卸载。
#### gso_max_size
!!! question "自 sing-box 1.8.0 起"
!!! quote ""
仅支持 Linux。
通用分段卸载包的最大大小。
默认使用 `65536`
启用 `system_interface` 时的自定义设备名称。
#### local_address

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! 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).
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-set).
### Structure

View File

@@ -1,41 +0,0 @@
---
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
### 结构
```json
{
"route": {
"geoip": {
"path": "",
"download_url": "",
"download_detour": ""
}
}
}
```
### 字段
#### path
指定 GeoIP 资源的路径。
默认 `geoip.db`
#### download_url
指定 GeoIP 资源的下载链接。
默认为 `https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db`
#### download_detour
用于下载 GeoIP 资源的出站的标签。
如果为空,将使用默认出站。

View File

@@ -4,7 +4,7 @@ icon: material/delete-clock
!!! 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).
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-set).
### Structure

View File

@@ -1,41 +0,0 @@
---
icon: material/delete-clock
---
!!! failure "已在 sing-box 1.8.0 废弃"
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
### 结构
```json
{
"route": {
"geosite": {
"path": "",
"download_url": "",
"download_detour": ""
}
}
}
```
### 字段
#### path
指定 GeoSite 资源的路径。
默认 `geosite.db`
#### download_url
指定 GeoSite 资源的下载链接。
默认为 `https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db`
#### download_detour
用于下载 GeoSite 资源的出站的标签。
如果为空,将使用默认出站。

View File

@@ -134,14 +134,12 @@ icon: material/alert-decagram
!!! note ""
The default rule uses the following matching logic:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr` || `ip_is_private`) &&
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
#### inbound
Tags of [Inbound](/configuration/inbound).
@@ -184,7 +182,7 @@ Match domain using regular expression.
!!! 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).
Geosite is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geosite-to-rule-set).
Match geosite.
@@ -192,7 +190,7 @@ Match geosite.
!!! 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).
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-set).
Match source geoip.
@@ -200,7 +198,7 @@ Match 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).
GeoIP is deprecated and may be removed in the future, check [Migration](/migration/#migrate-geoip-to-rule-set).
Match geoip.

View File

@@ -132,14 +132,12 @@ icon: material/alert-decagram
!!! note ""
默认规则使用以下匹配逻辑:
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr` || `ip_is_private`) &&
(`domain` || `domain_suffix` || `domain_keyword` || `domain_regex` || `geosite` || `geoip` || `ip_cidr`) &&
(`port` || `port_range`) &&
(`source_geoip` || `source_ip_cidr` || `source_ip_is_private`) &&
(`source_geoip` || `source_ip_cidr`) &&
(`source_port` || `source_port_range`) &&
`other fields`
另外,引用的规则集可视为被合并,而不是作为一个单独的规则子项。
#### inbound
[入站](/zh/configuration/inbound) 标签。
@@ -182,7 +180,7 @@ icon: material/alert-decagram
!!! failure "已在 sing-box 1.8.0 废弃"
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geosite)。
Geosite 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geosite-to-rule-set)。
匹配 Geosite。
@@ -190,7 +188,7 @@ icon: material/alert-decagram
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
GeoIp 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geoip-to-rule-set)。
匹配源 GeoIP。
@@ -198,7 +196,7 @@ icon: material/alert-decagram
!!! failure "已在 sing-box 1.8.0 废弃"
GeoIP 已废弃且可能在不久的将来移除,参阅 [迁移指南](/zh/migration/#geoip)。
GeoIp 已废弃且可能在不久的将来移除,参阅 [迁移指南](/migration/#migrate-geoip-to-rule-set)。
匹配 GeoIP。

View File

@@ -1,12 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "Changes in sing-box 1.8.0"
:material-alert-decagram: [utls](#utls)
### Inbound
```json
@@ -208,6 +199,10 @@ The path to the server private key, in PEM format.
==Client only==
!!! warning ""
uTLS is not included by default, see [Installation](./#installation).
!!! note ""
uTLS is poorly maintained and the effect may be unproven, use at your own risk.
@@ -216,20 +211,7 @@ uTLS is a fork of "crypto/tls", which provides ClientHello fingerprinting resist
Available fingerprint values:
!!! question "Since sing-box 1.8.0"
:material-plus: chrome_psk
:material-plus: chrome_psk_shuffle
:material-plus: chrome_padding_psk_shuffle
:material-plus: chrome_pq
:material-plus: chrome_pq_psk
* chrome
* chrome_psk
* chrome_psk_shuffle
* chrome_padding_psk_shuffle
* chrome_pq
* chrome_pq_psk
* firefox
* edge
* safari
@@ -244,6 +226,10 @@ Chrome fingerprint will be used if empty.
### ECH Fields
!!! warning ""
ECH is not included by default, see [Installation](./#installation).
ECH (Encrypted Client Hello) is a TLS extension that allows a client to encrypt the first part of its ClientHello
message.
@@ -292,6 +278,10 @@ If empty, load from DNS will be attempted.
### ACME Fields
!!! warning ""
ACME is not included by default, see [Installation](./#installation).
#### domain
List of domain.
@@ -367,6 +357,14 @@ See [DNS01 Challenge Fields](/configuration/shared/dns01_challenge) for details.
### Reality Fields
!!! warning ""
reality server is not included by default, see [Installation](./#installation).
!!! warning ""
uTLS, which is required by reality client is not included by default, see [Installation](./#installation).
#### handshake
==Server only==

View File

@@ -1,11 +1,3 @@
---
icon: material/alert-decagram
---
!!! quote "sing-box 1.8.0 中的更改"
:material-alert-decagram: [utls](#utls)
### 入站
```json
@@ -201,6 +193,10 @@ TLS 版本值:
==仅客户端==
!!! warning ""
默认安装不包含 uTLS, 参阅 [安装](/zh/#_2)。
!!! note ""
uTLS 维护不善且其效果可能未经证实,使用风险自负。
@@ -209,20 +205,7 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
可用的指纹值:
!!! question "自 sing-box 1.8.0 起"
:material-plus: chrome_psk
:material-plus: chrome_psk_shuffle
:material-plus: chrome_padding_psk_shuffle
:material-plus: chrome_pq
:material-plus: chrome_pq_psk
* chrome
* chrome_psk
* chrome_psk_shuffle
* chrome_padding_psk_shuffle
* chrome_pq
* chrome_pq_psk
* firefox
* edge
* safari
@@ -237,9 +220,14 @@ uTLS 是 "crypto/tls" 的一个分支,它提供了 ClientHello 指纹识别阻
## ECH 字段
!!! warning ""
默认安装不包含 ECH, 参阅 [安装](/zh/#_2)。
ECH (Encrypted Client Hello) 是一个 TLS 扩展,它允许客户端加密其 ClientHello 的第一部分
信息。
ECH 配置和密钥可以通过 `sing-box generate ech-keypair [--pq-signature-schemes-enabled]` 生成。
#### pq_signature_schemes_enabled
@@ -285,6 +273,10 @@ ECH PEM 配置路径
### ACME 字段
!!! warning ""
默认安装不包含 ACME参阅 [安装](/zh/#_2)。
#### domain
一组域名。
@@ -356,6 +348,14 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。
### Reality 字段
!!! warning ""
默认安装不包含 reality 服务器,参阅 [安装](/zh/#_2)。
!!! warning ""
默认安装不包含被 reality 客户端需要的 uTLS, 参阅 [安装](/zh/#_2)。
#### handshake
==仅服务器==

View File

@@ -129,6 +129,10 @@ It needs to be consistent with the server.
}
```
!!! warning ""
QUIC is not included by default, see [Installation](./#installation).
!!! warning "Difference from v2ray-core"
No additional encryption support:
@@ -138,7 +142,7 @@ It needs to be consistent with the server.
!!! note ""
standard gRPC has good compatibility but poor performance and is not included by default, see [Installation](/installation/build-from-source/#build-tags).
standard gRPC has good compatibility but poor performance and is not included by default, see [Installation](./#installation).
```json
{

View File

@@ -128,6 +128,10 @@ HTTP 请求的额外标头。
}
```
!!! warning ""
默认安装不包含 QUIC, 参阅 [安装](/zh/#_2)。
!!! warning "与 v2ray-core 的区别"
没有额外的加密支持:
@@ -137,7 +141,7 @@ HTTP 请求的额外标头。
!!! note ""
默认安装不包含标准 gRPC (兼容性好,但性能较差), 参阅 [安装](/zh/installation/build-from-source/#_5)。
默认安装不包含标准 gRPC (兼容性好,但性能较差), 参阅 [安装](/zh/#_2)。
```json
{

View File

@@ -4,37 +4,7 @@ icon: material/delete-alert
# Deprecated Feature List
## 1.8.0
#### Cache file and related features in Clash API
`cache_file` and related features in Clash API is migrated to independent `cache_file` options,
check [Migration](/migration/#migrate-cache-file-from-clash-api-to-independent-options).
#### GeoIP
GeoIP is deprecated and may be removed in the future.
The maxmind GeoIP National Database, as an IP classification database,
is not entirely suitable for traffic bypassing,
and all existing implementations suffer from high memory usage and difficult management.
sing-box 1.8.0 introduces [Rule Set](/configuration/rule_set), which can completely replace GeoIP,
check [Migration](/migration/#migrate-geoip-to-rule-sets).
#### Geosite
Geosite is deprecated and may be removed in the future.
Geosite, the `domain-list-community` project maintained by V2Ray as an early traffic bypassing solution,
suffers from a number of problems, including lack of maintenance, inaccurate rules, and difficult management.
sing-box 1.8.0 introduces [Rule Set](/configuration/rule_set), which can completely replace Geosite,
check [Migration](/migration/#migrate-geosite-to-rule-sets).
Geosite即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,存在着大量问题,包括缺少维护、规则不准确、管理困难。
## 1.6.0
### 1.6.0
The following features will be marked deprecated in 1.5.0 and removed entirely in 1.6.0.

View File

@@ -4,34 +4,7 @@ icon: material/delete-alert
# 废弃功能列表
## 1.8.0
#### Clash API 中的 Cache file 及相关功能
Clash API 中的 `cache_file` 及相关功能已废弃且已迁移到独立的 `cache_file` 设置,
参阅 [迁移指南](/zh/migration/#clash-api)。
#### GeoIP
GeoIP 已废弃且可能在不久的将来移除。
maxmind GeoIP 国家数据库作为 IP 分类数据库,不完全适合流量绕过,
且现有的实现均存在内存使用大与管理困难的问题。
sing-box 1.8.0 引入了[规则集](/configuration/rule_set)
可以完全替代 GeoIP 参阅 [迁移指南](/zh/migration/#geoip)。
#### Geosite
Geosite 已废弃且可能在不久的将来移除。
Geosite即由 V2Ray 维护的 domain-list-community 项目,作为早期流量绕过解决方案,
存在着包括缺少维护、规则不准确和管理困难内的大量问题。
sing-box 1.8.0 引入了[规则集](/configuration/rule_set)
可以完全替代 Geosite参阅 [迁移指南](/zh/migration/#geosite)。
## 1.6.0
### 1.6.0
下列功能已在 1.5.0 中标记为已弃用,并在 1.6.0 中完全删除。

View File

@@ -13,17 +13,7 @@ Before sing-box 1.4.0:
Since sing-box 1.4.0:
* Go 1.18.5 - ~
* Go 1.20.0 - ~ with tag `with_quic` enabled
Since sing-box 1.5.0:
* Go 1.18.5 - ~
* Go 1.20.0 - ~ with tag `with_quic` or `with_ech` enabled
Since sing-box 1.8.0:
* Go 1.18.5 - ~
* Go 1.20.0 - ~ with tag `with_quic`, `with_ech`, or `with_utls` enabled
* Go 1.20.0 - ~ if `with_quic` tag enabled
You can download and install Go from: https://go.dev/doc/install, latest version is recommended.
@@ -33,7 +23,7 @@ You can download and install Go from: https://go.dev/doc/install, latest version
make
```
Or build and install binary to `$GOBIN`:
Or build and install binary to `GOBIN`:
```bash
make install
@@ -55,17 +45,19 @@ go build -tags "tag_a tag_b" ./cmd/sing-box
| Build Tag | Enabled by default | Description |
|------------------------------------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server), [Naive inbound](/configuration/inbound/naive), [Hysteria Inbound](/configuration/inbound/hysteria), [Hysteria Outbound](/configuration/outbound/hysteria) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
| `with_grpc` | :material-close: | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server). |
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard). |
| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls). |
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls). |
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor). |
| `with_quic` | | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server), [Naive inbound](/configuration/inbound/naive), [Hysteria Inbound](/configuration/inbound/hysteria), [Hysteria Outbound](/configuration/outbound/hysteria) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
| `with_grpc` | ✖️ | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
| `with_dhcp` | | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server). |
| `with_wireguard` | | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard). |
| `with_ech` | | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
| `with_utls` | | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
| `with_reality_server` | | Build with reality TLS server support, see [TLS](/configuration/shared/tls). |
| `with_acme` | | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls). |
| `with_clash_api` | | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_v2ray_api` | ✖️ | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | ✖️ | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor). |
| `with_lwip` (CGO required) | ✖️ | Build with LWIP Tun stack support, see [Tun inbound](/configuration/inbound/tun#stack). |
It is not recommended to change the default build tag list unless you really know what you are adding.

View File

@@ -1,71 +0,0 @@
---
icon: material/file-code
---
# 从源代码构建
## :material-graph: 要求
sing-box 1.4.0 前:
* Go 1.18.5 - 1.20.x
从 sing-box 1.4.0:
* Go 1.18.5 - ~
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic`
从 sing-box 1.5.0:
* Go 1.18.5 - ~
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic``with_ech`
从 sing-box 1.8.0:
* Go 1.18.5 - ~
* Go 1.20.0 - ~ 如果启用构建标记 `with_quic``with_ech``with_utls`
您可以从 https://go.dev/doc/install 下载并安装 Go推荐使用最新版本。
## :material-fast-forward: 快速开始
```bash
make
```
或者构建二进制文件并将其安装到 `$GOBIN`
```bash
make install
```
## :material-cog: 自定义构建
```bash
TAGS="tag_a tag_b" make
```
or
```bash
go build -tags "tag_a tag_b" ./cmd/sing-box
```
## :material-folder-settings: 构建标记
| 构建标记 | 默认启动 | 说明 |
|------------------------------------|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `with_quic` | :material-check: | Build with QUIC support, see [QUIC and HTTP3 DNS transports](/configuration/dns/server), [Naive inbound](/configuration/inbound/naive), [Hysteria Inbound](/configuration/inbound/hysteria), [Hysteria Outbound](/configuration/outbound/hysteria) and [V2Ray Transport#QUIC](/configuration/shared/v2ray-transport#quic). |
| `with_grpc` | :material-close: | Build with standard gRPC support, see [V2Ray Transport#gRPC](/configuration/shared/v2ray-transport#grpc). |
| `with_dhcp` | :material-check: | Build with DHCP support, see [DHCP DNS transport](/configuration/dns/server). |
| `with_wireguard` | :material-check: | Build with WireGuard support, see [WireGuard outbound](/configuration/outbound/wireguard). |
| `with_ech` | :material-check: | Build with TLS ECH extension support for TLS outbound, see [TLS](/configuration/shared/tls#ech). |
| `with_utls` | :material-check: | Build with [uTLS](https://github.com/refraction-networking/utls) support for TLS outbound, see [TLS](/configuration/shared/tls#utls). |
| `with_reality_server` | :material-check: | Build with reality TLS server support, see [TLS](/configuration/shared/tls). |
| `with_acme` | :material-check: | Build with ACME TLS certificate issuer support, see [TLS](/configuration/shared/tls). |
| `with_clash_api` | :material-check: | Build with Clash API support, see [Experimental](/configuration/experimental#clash-api-fields). |
| `with_v2ray_api` | :material-close: | Build with V2Ray API support, see [Experimental](/configuration/experimental#v2ray-api-fields). |
| `with_gvisor` | :material-check: | Build with gVisor support, see [Tun inbound](/configuration/inbound/tun#stack) and [WireGuard outbound](/configuration/outbound/wireguard#system_interface). |
| `with_embedded_tor` (CGO required) | :material-close: | Build with embedded Tor support, see [Tor outbound](/configuration/outbound/tor). |
除非您确实知道您正在启用什么,否则不建议更改默认构建标签列表。

View File

@@ -1,31 +0,0 @@
---
icon: material/docker
---
# Docker
## :material-console: 命令
```bash
docker run -d \
-v /etc/sing-box:/etc/sing-box/ \
--name=sing-box \
--restart=always \
ghcr.io/sagernet/sing-box \
-D /var/lib/sing-box \
-C /etc/sing-box/ run
```
## :material-box-shadow: Compose
```yaml
version: "3.8"
services:
sing-box:
image: ghcr.io/sagernet/sing-box
container_name: sing-box
restart: always
volumes:
- /etc/sing-box:/etc/sing-box/
command: -D /var/lib/sing-box -C /etc/sing-box/ run
```

View File

@@ -1,90 +0,0 @@
---
icon: material/package
---
# 包管理器
## :material-download-box: 手动安装
=== ":material-debian: Debian / DEB"
```bash
bash <(curl -fsSL https://sing-box.app/deb-install.sh)
```
=== ":material-redhat: Redhat / RPM"
```bash
bash <(curl -fsSL https://sing-box.app/rpm-install.sh)
```
=== ":simple-archlinux: Archlinux / PKG"
```bash
bash <(curl -fsSL https://sing-box.app/arch-install.sh)
```
## :material-book-lock-open: 托管安装
=== ":material-linux: Linux"
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|----------|--------------------|---------------------|------------------------------|------------------|
| AUR | (Linux) Arch Linux | [sing-box][aur] ᴬᵁᴿ | `? -S sing-box` | :material-check: |
| nixpkgs | (Linux) NixOS | [sing-box][nixpkgs] | `nix-env -iA nixos.sing-box` | :material-check: |
| Homebrew | macOS / Linux | [sing-box][brew] | `brew install sing-box` | :material-check: |
| Alpine | (Linux) Alpine | [sing-box][alpine] | `apk add sing-box` | :material-alert: |
=== ":material-apple: macOS"
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|----------|---------------|------------------|-------------------------|------------------|
| Homebrew | macOS / Linux | [sing-box][brew] | `brew install sing-box` | :material-check: |
=== ":material-microsoft-windows: Windows"
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|------------|---------|--------------------|---------------------------|------------------|
| Scoop | Windows | [sing-box][scoop] | `scoop install sing-box` | :material-check: |
| Chocolatey | Windows | [sing-box][choco] | `choco install sing-box` | :material-check: |
| winget | Windows | [sing-box][winget] | `winget install sing-box` | :material-alert: |
=== ":material-android: Android"
| 类型 | 平台 | 链接 | 命令 | 活跃维护 |
|--------|---------|--------------------|--------------------|------------------|
| Termux | Android | [sing-box][termux] | `pkg add sing-box` | :material-check: |
## :material-book-multiple: 服务管理
对于带有 [systemd][systemd] 的 Linux 系统,通常安装已经包含 sing-box 服务,
您可以使用以下命令管理服务:
| 行动 | 命令 |
|------|-----------------------------------------------|
| 启用 | `sudo systemctl enable sing-box` |
| 禁用 | `sudo systemctl disable sing-box` |
| 启动 | `sudo systemctl start sing-box` |
| 停止 | `sudo systemctl stop sing-box` |
| 强行停止 | `sudo systemctl kill sing-box` |
| 重新启动 | `sudo systemctl restart sing-box` |
| 查看日志 | `sudo journalctl -u sing-box --output cat -e` |
| 实时日志 | `sudo journalctl -u sing-box --output cat -f` |
[alpine]: https://pkgs.alpinelinux.org/packages?name=sing-box
[aur]: https://aur.archlinux.org/packages/sing-box
[nixpkgs]: https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/tools/networking/sing-box/default.nix
[termux]: https://github.com/termux/termux-packages/tree/master/packages/sing-box
[brew]: https://formulae.brew.sh/formula/sing-box
[choco]: https://chocolatey.org/packages/sing-box
[scoop]: https://github.com/ScoopInstaller/Main/blob/master/bucket/sing-box.json
[winget]: https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/SagerNet/sing-box
[systemd]: https://systemd.io/

View File

@@ -35,6 +35,10 @@ but only AEAD 2022 ciphers TCP with multiplexing is recommended.
## :material-server: Server Example
!!! info ""
Password of cipher `2022-blake3-aes-128-gcm` can be generated by command `sing-box generate rand 16 --base64`
=== ":material-account: Single-user"
```json

View File

@@ -531,7 +531,7 @@ flowchart TB
"outbound": "dns"
},
{
"ip_is_private": true,
"geoip": "private",
"outbound": "direct"
},
{

View File

@@ -2,6 +2,8 @@
icon: material/arrange-bring-forward
---
# Migration
## 1.8.0
!!! warning "Unstable"
@@ -10,7 +12,7 @@ icon: material/arrange-bring-forward
### :material-close-box: Migrate cache file from Clash API to independent options
!!! info "References"
!!! info "Reference"
[Clash API](/configuration/experimental/clash-api) /
[Cache File](/configuration/experimental/cache-file)
@@ -48,7 +50,7 @@ icon: material/arrange-bring-forward
### :material-checkbox-intermediate: Migrate GeoIP to rule sets
!!! info "References"
!!! info "Reference"
[GeoIP](/configuration/route/geoip) /
[Route](/configuration/route) /
@@ -133,7 +135,7 @@ icon: material/arrange-bring-forward
### :material-checkbox-intermediate: Migrate Geosite to rule sets
!!! info "References"
!!! info "Reference"
[Geosite](/configuration/route/geosite) /
[Route](/configuration/route) /

View File

@@ -1,193 +0,0 @@
---
icon: material/arrange-bring-forward
---
## 1.8.0
!!! warning "不稳定的"
该版本仍在开发中,迁移指南可能将在未来更改。
### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项
!!! info "参考"
[Clash API](/zh/configuration/experimental/clash-api) /
[Cache File](/zh/configuration/experimental/cache-file)
=== ":material-card-remove: 弃用的"
```json
{
"experimental": {
"clash_api": {
"cache_file": "cache.db", // 默认值
"cahce_id": "my_profile2",
"store_mode": true,
"store_selected": true,
"store_fakeip": true
}
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"experimental" : {
"cache_file": {
"enabled": true,
"path": "cache.db", // 默认值
"cache_id": "my_profile2",
"store_fakeip": true
}
}
}
```
### :material-checkbox-intermediate: 迁移 GeoIP 到规则集
!!! info "参考"
[GeoIP](/zh/configuration/route/geoip) /
[路由](/zh/configuration/route) /
[路由规则](/zh/configuration/route/rule) /
[DNS 规则](/zh/configuration/dns/rule) /
[规则集](/zh/configuration/rule-set)
!!! tip
`sing-box geoip` 命令可以帮助您将自定义 GeoIP 转换为规则集。
=== ":material-card-remove: 弃用的"
```json
{
"route": {
"rules": [
{
"geoip": "private",
"outbound": "direct"
},
{
"geoip": "cn",
"outbound": "direct"
},
{
"source_geoip": "cn",
"outbound": "block"
}
],
"geoip": {
"download_detour": "proxy"
}
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"route": {
"rules": [
{
"ip_is_private": true,
"outbound": "direct"
},
{
"rule_set": "geoip-cn",
"outbound": "direct"
},
{
"rule_set": "geoip-us",
"rule_set_ipcidr_match_source": true,
"outbound": "block"
}
],
"rule_set": [
{
"tag": "geoip-cn",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs",
"download_detour": "proxy"
},
{
"tag": "geoip-us",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-us.srs",
"download_detour": "proxy"
}
]
},
"experimental": {
"cache_file": {
"enabled": true // required to save Rule Set cache
}
}
}
```
### :material-checkbox-intermediate: 迁移 Geosite 到规则集
!!! info "参考"
[Geosite](/zh/configuration/route/geosite) /
[路由](/zh/configuration/route) /
[路由规则](/zh/configuration/route/rule) /
[DNS 规则](/zh/configuration/dns/rule) /
[规则集](/zh/configuration/rule-set)
!!! tip
`sing-box geosite` 命令可以帮助您将自定义 Geosite 转换为规则集。
=== ":material-card-remove: 弃用的"
```json
{
"route": {
"rules": [
{
"geosite": "cn",
"outbound": "direct"
}
],
"geosite": {
"download_detour": "proxy"
}
}
}
```
=== ":material-card-multiple: 新的"
```json
{
"route": {
"rules": [
{
"rule_set": "geosite-cn",
"outbound": "direct"
}
],
"rule_set": [
{
"tag": "geosite-cn",
"type": "remote",
"format": "binary",
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-cn.srs",
"download_detour": "proxy"
}
]
},
"experimental": {
"cache_file": {
"enabled": true // required to save Rule Set cache
}
}
}
```

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