mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-14 04:38:28 +10:00
Compare commits
86 Commits
v1.9.0-rc.
...
v1.10.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06ee404918 | ||
|
|
687e631f3d | ||
|
|
a276121013 | ||
|
|
285336261b | ||
|
|
8a11417dd5 | ||
|
|
53e5aa64b4 | ||
|
|
97ef3ca0f3 | ||
|
|
ba2eb6e49f | ||
|
|
df331ba0c3 | ||
|
|
3f67845d4f | ||
|
|
dedeba0e6d | ||
|
|
4f870150fc | ||
|
|
85aaa112c4 | ||
|
|
19f6c6abfd | ||
|
|
3677e7ab9e | ||
|
|
2d2e174a6e | ||
|
|
81e9eda357 | ||
|
|
7cba3da108 | ||
|
|
82d06b43e7 | ||
|
|
a7ac91f573 | ||
|
|
0540a95a43 | ||
|
|
94707dfcdd | ||
|
|
8a17043502 | ||
|
|
b0aaa86806 | ||
|
|
8a2d3fbb28 | ||
|
|
4652019608 | ||
|
|
06fa5abf63 | ||
|
|
996fbbf0c3 | ||
|
|
142ff1b455 | ||
|
|
74d662f7a3 | ||
|
|
085f603377 | ||
|
|
460fae83dc | ||
|
|
bb9bd9bff6 | ||
|
|
c2354ebf25 | ||
|
|
c1f4755c4e | ||
|
|
0ca5909b06 | ||
|
|
e77a8114c5 | ||
|
|
f1393235ff | ||
|
|
bdba2365de | ||
|
|
ce0da5b557 | ||
|
|
3853201412 | ||
|
|
7003ef40a3 | ||
|
|
59ec92228c | ||
|
|
0eeb2da323 | ||
|
|
977b0fac02 | ||
|
|
51964801ff | ||
|
|
e08c052fc9 | ||
|
|
53927d8bbd | ||
|
|
968b9bc217 | ||
|
|
69dc87aa6d | ||
|
|
4193df375f | ||
|
|
5ff7006326 | ||
|
|
a89107ea9d | ||
|
|
9ffdbba2ed | ||
|
|
65c71049ea | ||
|
|
7d4e6a7f4e | ||
|
|
d612620c5d | ||
|
|
8a9a77a438 | ||
|
|
a2098c18e1 | ||
|
|
cf2181dd3a | ||
|
|
5899e95ff1 | ||
|
|
d7160c19cf | ||
|
|
da9e22b4e6 | ||
|
|
0e120f8a44 | ||
|
|
d918863ac5 | ||
|
|
2ae192305c | ||
|
|
71d1879bd6 | ||
|
|
917514e09f | ||
|
|
5327aeaea4 | ||
|
|
93ae3f7a1e | ||
|
|
f24a2aed7d | ||
|
|
0517ceef76 | ||
|
|
830ea46932 | ||
|
|
cd0fcd5ddc | ||
|
|
003176f069 | ||
|
|
71d92518c1 | ||
|
|
b5dcd6bf59 | ||
|
|
11c7b4a866 | ||
|
|
ee14135298 | ||
|
|
cbcf005f37 | ||
|
|
daee0b154e | ||
|
|
d530c724c0 | ||
|
|
7f698c1104 | ||
|
|
7a4a44c6d2 | ||
|
|
44277e5dd2 | ||
|
|
1f470c69c4 |
28
.github/workflows/debug.yml
vendored
28
.github/workflows/debug.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -33,32 +33,12 @@ jobs:
|
||||
- name: Run Test
|
||||
run: |
|
||||
go test -v ./...
|
||||
build_go118:
|
||||
name: Debug build (Go 1.18)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ~1.18
|
||||
- name: Cache go module
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/go/pkg/mod
|
||||
key: go118-${{ hashFiles('**/go.sum') }}
|
||||
- name: Run Test
|
||||
run: make ci_build_go118
|
||||
build_go120:
|
||||
name: Debug build (Go 1.20)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -78,7 +58,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -208,7 +188,7 @@ jobs:
|
||||
TAGS: with_clash_api,with_quic
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
|
||||
33
.github/workflows/docker.yml
vendored
33
.github/workflows/docker.yml
vendored
@@ -4,13 +4,35 @@ on:
|
||||
release:
|
||||
types:
|
||||
- released
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "The tag version you want to build"
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get commit to build
|
||||
id: ref
|
||||
run: |-
|
||||
if [[ -z "${{ github.event.inputs.tag }}" ]]; then
|
||||
ref="${{ github.ref_name }}"
|
||||
else
|
||||
ref="${{ github.event.inputs.tag }}"
|
||||
fi
|
||||
echo "ref=$ref"
|
||||
echo "ref=$ref" >> $GITHUB_OUTPUT
|
||||
if [[ $ref == *"-"* ]]; then
|
||||
latest=latest-beta
|
||||
else
|
||||
latest=latest
|
||||
fi
|
||||
echo "latest=$latest"
|
||||
echo "latest=$latest" >> $GITHUB_OUTPUT
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
ref: ${{ steps.ref.outputs.ref }}
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Setup QEMU for Docker Buildx
|
||||
@@ -27,13 +49,14 @@ jobs:
|
||||
with:
|
||||
images: ghcr.io/sagernet/sing-box
|
||||
- name: Build and release Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
platforms: linux/386,linux/amd64,linux/arm64,linux/s390x
|
||||
context: .
|
||||
target: dist
|
||||
build-args: |
|
||||
BUILDKIT_CONTEXT_KEEP_GIT_DIR=1
|
||||
tags: |
|
||||
ghcr.io/sagernet/sing-box:latest
|
||||
ghcr.io/sagernet/sing-box:${{ github.ref_name }}
|
||||
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.latest }}
|
||||
ghcr.io/sagernet/sing-box:${{ steps.ref.outputs.ref }}
|
||||
push: true
|
||||
|
||||
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
go-version: ^1.22
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v5
|
||||
uses: golangci/golangci-lint-action@v6
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=30m
|
||||
|
||||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Setup Go
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
EOF
|
||||
echo "HOME=$HOME" >> "$GITHUB_ENV"
|
||||
- name: Publish release
|
||||
uses: goreleaser/goreleaser-action@v5
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser-pro
|
||||
version: latest
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -14,3 +14,5 @@
|
||||
/*.xcframework/
|
||||
.DS_Store
|
||||
/config.d/
|
||||
/venv/
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ nfpms:
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
builds:
|
||||
- main
|
||||
vendor: sagernet
|
||||
homepage: https://sing-box.sagernet.org/
|
||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||
description: The universal proxy platform.
|
||||
|
||||
@@ -113,7 +113,6 @@ nfpms:
|
||||
file_name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ with .Arm }}v{{ . }}{{ end }}{{ with .Mips }}_{{ . }}{{ end }}{{ if not (eq .Amd64 "v1") }}{{ .Amd64 }}{{ end }}'
|
||||
builds:
|
||||
- main
|
||||
vendor: sagernet
|
||||
homepage: https://sing-box.sagernet.org/
|
||||
maintainer: nekohasekai <contact-git@sekai.icu>
|
||||
description: The universal proxy platform.
|
||||
|
||||
17
Makefile
17
Makefile
@@ -1,7 +1,6 @@
|
||||
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_utls
|
||||
TAGS_GO120 = with_gvisor,with_dhcp,with_wireguard,with_reality_server,with_clash_api,with_quic,with_utls
|
||||
TAGS_GO121 = with_ech
|
||||
TAGS ?= $(TAGS_GO118),$(TAGS_GO120),$(TAGS_GO121)
|
||||
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_ech,with_utls,with_reality_server
|
||||
@@ -20,13 +19,9 @@ PREFIX ?= $(shell go env GOPATH)
|
||||
build:
|
||||
go build $(MAIN_PARAMS) $(MAIN)
|
||||
|
||||
ci_build_go118:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
go build $(PARAMS) -tags "$(TAGS_GO118)" $(MAIN)
|
||||
|
||||
ci_build_go120:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
go build $(PARAMS) -tags "$(TAGS_GO118),$(TAGS_GO120)" $(MAIN)
|
||||
go build $(PARAMS) -tags "$(TAGS_GO120)" $(MAIN)
|
||||
|
||||
ci_build:
|
||||
go build $(PARAMS) $(MAIN)
|
||||
@@ -197,13 +192,15 @@ lib_install:
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.3
|
||||
|
||||
docs:
|
||||
mkdocs serve
|
||||
venv/bin/mkdocs serve
|
||||
|
||||
publish_docs:
|
||||
mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||
venv/bin/mkdocs gh-deploy -m "Update" --force --ignore-version --no-history
|
||||
|
||||
docs_install:
|
||||
pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||
python -m venv venv
|
||||
source ./venv/bin/activate && pip install --force-reinstall mkdocs-material=="9.*" mkdocs-static-i18n=="1.2.*"
|
||||
|
||||
clean:
|
||||
rm -rf bin dist sing-box
|
||||
rm -f $(shell go env GOPATH)/sing-box
|
||||
|
||||
@@ -4,13 +4,13 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/common/urltest"
|
||||
"github.com/sagernet/sing-dns"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
type ClashServer interface {
|
||||
@@ -30,6 +30,9 @@ type CacheFile interface {
|
||||
StoreFakeIP() bool
|
||||
FakeIPStorage
|
||||
|
||||
StoreRDRC() bool
|
||||
dns.RDRCStore
|
||||
|
||||
LoadMode() string
|
||||
StoreMode(mode string) error
|
||||
LoadSelected(group string) string
|
||||
@@ -52,16 +55,15 @@ func (s *SavedRuleSet) MarshalBinary() ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rw.WriteUVariant(&buffer, uint64(len(s.Content)))
|
||||
err = varbin.Write(&buffer, binary.BigEndian, s.Content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buffer.Write(s.Content)
|
||||
err = binary.Write(&buffer, binary.BigEndian, s.LastUpdated.Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = rw.WriteVString(&buffer, s.LastEtag)
|
||||
err = varbin.Write(&buffer, binary.BigEndian, s.LastEtag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,12 +77,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contentLen, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Content = make([]byte, contentLen)
|
||||
_, err = io.ReadFull(reader, s.Content)
|
||||
err = varbin.Read(reader, binary.BigEndian, &s.Content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,7 +87,7 @@ func (s *SavedRuleSet) UnmarshalBinary(data []byte) error {
|
||||
return err
|
||||
}
|
||||
s.LastUpdated = time.Unix(lastUpdated, 0)
|
||||
s.LastEtag, err = rw.ReadVString(reader)
|
||||
err = varbin.Read(reader, binary.BigEndian, &s.LastEtag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -51,19 +51,25 @@ type InboundContext struct {
|
||||
|
||||
// rule cache
|
||||
|
||||
IPCIDRMatchSource bool
|
||||
SourceAddressMatch bool
|
||||
SourcePortMatch bool
|
||||
DestinationAddressMatch bool
|
||||
DestinationPortMatch bool
|
||||
IPCIDRMatchSource bool
|
||||
IPCIDRAcceptEmpty bool
|
||||
|
||||
SourceAddressMatch bool
|
||||
SourcePortMatch bool
|
||||
DestinationAddressMatch bool
|
||||
DestinationPortMatch bool
|
||||
DidMatch bool
|
||||
IgnoreDestinationIPCIDRMatch bool
|
||||
}
|
||||
|
||||
func (c *InboundContext) ResetRuleCache() {
|
||||
c.IPCIDRMatchSource = false
|
||||
c.IPCIDRAcceptEmpty = false
|
||||
c.SourceAddressMatch = false
|
||||
c.SourcePortMatch = false
|
||||
c.DestinationAddressMatch = false
|
||||
c.DestinationPortMatch = false
|
||||
c.DidMatch = false
|
||||
}
|
||||
|
||||
type inboundContextKey struct{}
|
||||
|
||||
@@ -10,15 +10,18 @@ import (
|
||||
"github.com/sagernet/sing-tun"
|
||||
"github.com/sagernet/sing/common/control"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
"github.com/sagernet/sing/service"
|
||||
|
||||
mdns "github.com/miekg/dns"
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
type Router interface {
|
||||
Service
|
||||
PreStarter
|
||||
PostStarter
|
||||
Cleanup() error
|
||||
|
||||
Outbounds() []Outbound
|
||||
Outbound(tag string) (Outbound, bool)
|
||||
@@ -45,7 +48,9 @@ type Router interface {
|
||||
DefaultInterface() string
|
||||
AutoDetectInterface() bool
|
||||
AutoDetectInterfaceFunc() control.Func
|
||||
DefaultMark() int
|
||||
DefaultMark() uint32
|
||||
RegisterAutoRedirectOutputMark(mark uint32) error
|
||||
AutoRedirectOutputMark() uint32
|
||||
NetworkMonitor() tun.NetworkUpdateMonitor
|
||||
InterfaceMonitor() tun.DefaultInterfaceMonitor
|
||||
PackageManager() tun.PackageManager
|
||||
@@ -71,6 +76,7 @@ func RouterFromContext(ctx context.Context) Router {
|
||||
|
||||
type HeadlessRule interface {
|
||||
Match(metadata *InboundContext) bool
|
||||
String() string
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
@@ -79,26 +85,38 @@ type Rule interface {
|
||||
Type() string
|
||||
UpdateGeosite() error
|
||||
Outbound() string
|
||||
String() string
|
||||
}
|
||||
|
||||
type DNSRule interface {
|
||||
Rule
|
||||
DisableCache() bool
|
||||
RewriteTTL() *uint32
|
||||
ClientSubnet() *netip.Prefix
|
||||
WithAddressLimit() bool
|
||||
MatchAddressLimit(metadata *InboundContext) bool
|
||||
}
|
||||
|
||||
type RuleSet interface {
|
||||
Name() string
|
||||
StartContext(ctx context.Context, startContext RuleSetStartContext) error
|
||||
PostStart() error
|
||||
Metadata() RuleSetMetadata
|
||||
ExtractIPSet() []*netipx.IPSet
|
||||
IncRef()
|
||||
DecRef()
|
||||
Cleanup()
|
||||
RegisterCallback(callback RuleSetUpdateCallback) *list.Element[RuleSetUpdateCallback]
|
||||
UnregisterCallback(element *list.Element[RuleSetUpdateCallback])
|
||||
Close() error
|
||||
HeadlessRule
|
||||
}
|
||||
|
||||
type RuleSetUpdateCallback func(it RuleSet)
|
||||
|
||||
type RuleSetMetadata struct {
|
||||
ContainsProcessRule bool
|
||||
ContainsWIFIRule bool
|
||||
ContainsIPCIDRRule bool
|
||||
}
|
||||
|
||||
type RuleSetStartContext interface {
|
||||
|
||||
35
box.go
35
box.go
@@ -111,6 +111,7 @@ func New(options Options) (*Box, error) {
|
||||
ctx,
|
||||
router,
|
||||
logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")),
|
||||
tag,
|
||||
inboundOptions,
|
||||
options.PlatformInterface,
|
||||
)
|
||||
@@ -203,7 +204,7 @@ func (s *Box) PreStart() error {
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
log.Error(E.Cause(err, "origin error"))
|
||||
println(err.Error())
|
||||
debug.PrintStack()
|
||||
panic("panic on early close: " + fmt.Sprint(v))
|
||||
}
|
||||
@@ -222,9 +223,9 @@ func (s *Box) Start() error {
|
||||
defer func() {
|
||||
v := recover()
|
||||
if v != nil {
|
||||
log.Error(E.Cause(err, "origin error"))
|
||||
println(err.Error())
|
||||
debug.PrintStack()
|
||||
panic("panic on early close: " + fmt.Sprint(v))
|
||||
println("panic on early start: " + fmt.Sprint(v))
|
||||
}
|
||||
}()
|
||||
s.Close()
|
||||
@@ -302,7 +303,11 @@ func (s *Box) start() error {
|
||||
return E.Cause(err, "initialize inbound/", in.Type(), "[", tag, "]")
|
||||
}
|
||||
}
|
||||
return s.postStart()
|
||||
err = s.postStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.router.Cleanup()
|
||||
}
|
||||
|
||||
func (s *Box) postStart() error {
|
||||
@@ -312,16 +317,28 @@ func (s *Box) postStart() error {
|
||||
return E.Cause(err, "start ", serviceName)
|
||||
}
|
||||
}
|
||||
for _, outbound := range s.outbounds {
|
||||
if lateOutbound, isLateOutbound := outbound.(adapter.PostStarter); isLateOutbound {
|
||||
// TODO: reorganize ALL start order
|
||||
for _, out := range s.outbounds {
|
||||
if lateOutbound, isLateOutbound := out.(adapter.PostStarter); isLateOutbound {
|
||||
err := lateOutbound.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start outbound/", outbound.Tag())
|
||||
return E.Cause(err, "post-start outbound/", out.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.router.PostStart()
|
||||
err := s.router.PostStart()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, in := range s.inbounds {
|
||||
if lateInbound, isLateInbound := in.(adapter.PostStarter); isLateInbound {
|
||||
err = lateInbound.PostStart()
|
||||
if err != nil {
|
||||
return E.Cause(err, "post-start inbound/", in.Tag())
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Box) Close() error {
|
||||
|
||||
@@ -45,7 +45,9 @@ func (s *Box) startOutbounds() error {
|
||||
}
|
||||
started[outboundTag] = true
|
||||
canContinue = true
|
||||
if starter, isStarter := outboundToStart.(common.Starter); isStarter {
|
||||
if starter, isStarter := outboundToStart.(interface {
|
||||
Start() error
|
||||
}); isStarter {
|
||||
monitor.Start("initialize outbound/", outboundToStart.Type(), "[", outboundTag, "]")
|
||||
err := starter.Start()
|
||||
monitor.Finish()
|
||||
|
||||
Submodule clients/android updated: c0ce4cc97d...00e3a80875
Submodule clients/apple updated: 17eec08615...0cbe335cbb
@@ -93,7 +93,7 @@ func buildAndroid() {
|
||||
|
||||
const name = "libbox.aar"
|
||||
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
|
||||
if rw.FileExists(copyPath) {
|
||||
if rw.IsDir(copyPath) {
|
||||
copyPath, _ = filepath.Abs(copyPath)
|
||||
err = rw.CopyFile(name, filepath.Join(copyPath, name))
|
||||
if err != nil {
|
||||
@@ -134,7 +134,7 @@ func buildiOS() {
|
||||
}
|
||||
|
||||
copyPath := filepath.Join("..", "sing-box-for-apple")
|
||||
if rw.FileExists(copyPath) {
|
||||
if rw.IsDir(copyPath) {
|
||||
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
|
||||
targetDir, _ = filepath.Abs(targetDir)
|
||||
os.RemoveAll(targetDir)
|
||||
|
||||
@@ -30,7 +30,7 @@ func FindSDK() {
|
||||
}
|
||||
for _, path := range searchPath {
|
||||
path = os.ExpandEnv(path)
|
||||
if rw.FileExists(filepath.Join(path, "licenses", "android-sdk-license")) {
|
||||
if rw.IsFile(filepath.Join(path, "licenses", "android-sdk-license")) {
|
||||
androidSDKPath = path
|
||||
break
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func FindSDK() {
|
||||
func findNDK() bool {
|
||||
const fixedVersion = "26.2.11394342"
|
||||
const versionFile = "source.properties"
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.FileExists(filepath.Join(fixedPath, versionFile)) {
|
||||
if fixedPath := filepath.Join(androidSDKPath, "ndk", fixedVersion); rw.IsFile(filepath.Join(fixedPath, versionFile)) {
|
||||
androidNDKPath = fixedPath
|
||||
return true
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func findNDK() bool {
|
||||
})
|
||||
for _, versionName := range versionNames {
|
||||
currentNDKPath := filepath.Join(androidSDKPath, "ndk", versionName)
|
||||
if rw.FileExists(filepath.Join(androidSDKPath, versionFile)) {
|
||||
if rw.IsFile(filepath.Join(androidSDKPath, versionFile)) {
|
||||
androidNDKPath = currentNDKPath
|
||||
log.Warn("reproducibility warning: using NDK version " + versionName + " instead of " + fixedVersion)
|
||||
return true
|
||||
@@ -100,11 +100,11 @@ var GoBinPath string
|
||||
func FindMobile() {
|
||||
goBin := filepath.Join(build.Default.GOPATH, "bin")
|
||||
if runtime.GOOS == "windows" {
|
||||
if !rw.FileExists(filepath.Join(goBin, "gobind.exe")) {
|
||||
if !rw.IsFile(filepath.Join(goBin, "gobind.exe")) {
|
||||
log.Fatal("missing gomobile installation")
|
||||
}
|
||||
} else {
|
||||
if !rw.FileExists(filepath.Join(goBin, "gobind")) {
|
||||
if !rw.IsFile(filepath.Join(goBin, "gobind")) {
|
||||
log.Fatal("missing gomobile installation")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,11 @@ func merge(outputPath string) error {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
err = rw.WriteFile(outputPath, buffer.Bytes())
|
||||
err = rw.MkdirParent(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.WriteFile(outputPath, buffer.Bytes(), 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
var commandRuleSet = &cobra.Command{
|
||||
Use: "rule-set",
|
||||
Short: "Manage rule sets",
|
||||
Short: "Manage rule-sets",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@@ -55,10 +55,10 @@ func compileRuleSet(sourcePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet, err := plainRuleSet.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet := plainRuleSet.Upgrade()
|
||||
var outputPath string
|
||||
if flagRuleSetCompileOutput == flagRuleSetCompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".json") {
|
||||
|
||||
83
cmd/sing-box/cmd_rule_set_decompile.go
Normal file
83
cmd/sing-box/cmd_rule_set_decompile.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var flagRuleSetDecompileOutput string
|
||||
|
||||
const flagRuleSetDecompileDefaultOutput = "<file_name>.json"
|
||||
|
||||
var commandRuleSetDecompile = &cobra.Command{
|
||||
Use: "decompile [binary-path]",
|
||||
Short: "Decompile rule-set binary to json",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := decompileRuleSet(args[0])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandRuleSet.AddCommand(commandRuleSetDecompile)
|
||||
commandRuleSetDecompile.Flags().StringVarP(&flagRuleSetDecompileOutput, "output", "o", flagRuleSetDecompileDefaultOutput, "Output file")
|
||||
}
|
||||
|
||||
func decompileRuleSet(sourcePath string) error {
|
||||
var (
|
||||
reader io.Reader
|
||||
err error
|
||||
)
|
||||
if sourcePath == "stdin" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
reader, err = os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
plainRuleSet, err := srs.Read(reader, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ruleSet := option.PlainRuleSetCompat{
|
||||
Version: C.RuleSetVersion1,
|
||||
Options: plainRuleSet,
|
||||
}
|
||||
var outputPath string
|
||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".srs") {
|
||||
outputPath = sourcePath[:len(sourcePath)-4] + ".json"
|
||||
} else {
|
||||
outputPath = sourcePath + ".json"
|
||||
}
|
||||
} else {
|
||||
outputPath = flagRuleSetDecompileOutput
|
||||
}
|
||||
outputFile, err := os.Create(outputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := json.NewEncoder(outputFile)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(ruleSet)
|
||||
if err != nil {
|
||||
outputFile.Close()
|
||||
os.Remove(outputPath)
|
||||
return err
|
||||
}
|
||||
outputFile.Close()
|
||||
return nil
|
||||
}
|
||||
96
cmd/sing-box/cmd_rule_set_match.go
Normal file
96
cmd/sing-box/cmd_rule_set_match.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing-box/route"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var flagRuleSetMatchFormat string
|
||||
|
||||
var commandRuleSetMatch = &cobra.Command{
|
||||
Use: "match <rule-set path> <IP address/domain>",
|
||||
Short: "Check if an IP address or a domain matches the rule-set",
|
||||
Args: cobra.ExactArgs(2),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := ruleSetMatch(args[0], args[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandRuleSetMatch.Flags().StringVarP(&flagRuleSetMatchFormat, "format", "f", "source", "rule-set format")
|
||||
commandRuleSet.AddCommand(commandRuleSetMatch)
|
||||
}
|
||||
|
||||
func ruleSetMatch(sourcePath string, domain string) error {
|
||||
var (
|
||||
reader io.Reader
|
||||
err error
|
||||
)
|
||||
if sourcePath == "stdin" {
|
||||
reader = os.Stdin
|
||||
} else {
|
||||
reader, err = os.Open(sourcePath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
}
|
||||
content, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return E.Cause(err, "read rule-set")
|
||||
}
|
||||
var plainRuleSet option.PlainRuleSet
|
||||
switch flagRuleSetMatchFormat {
|
||||
case C.RuleSetFormatSource:
|
||||
var compat option.PlainRuleSetCompat
|
||||
compat, err = json.UnmarshalExtended[option.PlainRuleSetCompat](content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plainRuleSet, err = compat.Upgrade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case C.RuleSetFormatBinary:
|
||||
plainRuleSet, err = srs.Read(bytes.NewReader(content), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return E.New("unknown rule-set format: ", flagRuleSetMatchFormat)
|
||||
}
|
||||
ipAddress := M.ParseAddr(domain)
|
||||
var metadata adapter.InboundContext
|
||||
if ipAddress.IsValid() {
|
||||
metadata.Destination = M.SocksaddrFrom(ipAddress, 0)
|
||||
} else {
|
||||
metadata.Domain = domain
|
||||
}
|
||||
for i, ruleOptions := range plainRuleSet.Rules {
|
||||
var currentRule adapter.HeadlessRule
|
||||
currentRule, err = route.NewHeadlessRule(nil, ruleOptions)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse rule_set.rules.[", i, "]")
|
||||
}
|
||||
if currentRule.Match(&metadata) {
|
||||
println(F.ToString("match rules.[", i, "]: ", currentRule))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func readConfigAndMerge() (option.Options, error) {
|
||||
}
|
||||
var mergedMessage json.RawMessage
|
||||
for _, options := range optionsList {
|
||||
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage)
|
||||
mergedMessage, err = badjson.MergeJSON(options.options.RawMessage, mergedMessage, false)
|
||||
if err != nil {
|
||||
return option.Options{}, E.Cause(err, "merge config at ", options.path)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -32,7 +34,10 @@ func init() {
|
||||
commandTools.AddCommand(commandFetch)
|
||||
}
|
||||
|
||||
var httpClient *http.Client
|
||||
var (
|
||||
httpClient *http.Client
|
||||
http3Client *http.Client
|
||||
)
|
||||
|
||||
func fetch(args []string) error {
|
||||
instance, err := createPreStartedClient()
|
||||
@@ -53,8 +58,16 @@ func fetch(args []string) error {
|
||||
},
|
||||
}
|
||||
defer httpClient.CloseIdleConnections()
|
||||
if C.WithQUIC {
|
||||
err = initializeHTTP3Client(instance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer http3Client.CloseIdleConnections()
|
||||
}
|
||||
for _, urlString := range args {
|
||||
parsedURL, err := url.Parse(urlString)
|
||||
var parsedURL *url.URL
|
||||
parsedURL, err = url.Parse(urlString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,16 +76,27 @@ func fetch(args []string) error {
|
||||
parsedURL.Scheme = "http"
|
||||
fallthrough
|
||||
case "http", "https":
|
||||
err = fetchHTTP(parsedURL)
|
||||
err = fetchHTTP(httpClient, parsedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "http3":
|
||||
if !C.WithQUIC {
|
||||
return C.ErrQUICNotIncluded
|
||||
}
|
||||
parsedURL.Scheme = "https"
|
||||
err = fetchHTTP(http3Client, parsedURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return E.New("unsupported scheme: ", parsedURL.Scheme)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fetchHTTP(parsedURL *url.URL) error {
|
||||
func fetchHTTP(httpClient *http.Client, parsedURL *url.URL) error {
|
||||
request, err := http.NewRequest("GET", parsedURL.String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
36
cmd/sing-box/cmd_tools_fetch_http3.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build with_quic
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
|
||||
"github.com/sagernet/quic-go"
|
||||
"github.com/sagernet/quic-go/http3"
|
||||
box "github.com/sagernet/sing-box"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
func initializeHTTP3Client(instance *box.Box) error {
|
||||
dialer, err := createDialer(instance, N.NetworkUDP, commandToolsFlagOutbound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http3Client = &http.Client{
|
||||
Transport: &http3.RoundTripper{
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
destination := M.ParseSocksaddr(addr)
|
||||
udpConn, dErr := dialer.DialContext(ctx, N.NetworkUDP, destination)
|
||||
if dErr != nil {
|
||||
return nil, dErr
|
||||
}
|
||||
return quic.DialEarly(ctx, bufio.NewUnbindPacketConn(udpConn), udpConn.RemoteAddr(), tlsCfg, cfg)
|
||||
},
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
18
cmd/sing-box/cmd_tools_fetch_http3_stub.go
Normal file
@@ -0,0 +1,18 @@
|
||||
//go:build !with_quic
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
box "github.com/sagernet/sing-box"
|
||||
)
|
||||
|
||||
func initializeHTTP3Client(instance *box.Box) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
|
||||
func fetchHTTP3(parsedURL *url.URL) error {
|
||||
return os.ErrInvalid
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package badtls
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"reflect"
|
||||
"sync"
|
||||
@@ -18,20 +20,32 @@ import (
|
||||
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
|
||||
tls.Conn
|
||||
halfAccess *sync.Mutex
|
||||
rawInput *bytes.Buffer
|
||||
input *bytes.Reader
|
||||
hand *bytes.Buffer
|
||||
readWaitOptions N.ReadWaitOptions
|
||||
tlsReadRecord func() error
|
||||
tlsHandlePostHandshakeMessage func() error
|
||||
}
|
||||
|
||||
func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||
stdConn, isSTDConn := conn.(*tls.STDConn)
|
||||
if !isSTDConn {
|
||||
var (
|
||||
loaded bool
|
||||
tlsReadRecord func() error
|
||||
tlsHandlePostHandshakeMessage func() error
|
||||
)
|
||||
for _, tlsCreator := range tlsRegistry {
|
||||
loaded, tlsReadRecord, tlsHandlePostHandshakeMessage = tlsCreator(conn)
|
||||
if loaded {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !loaded {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
rawConn := reflect.Indirect(reflect.ValueOf(stdConn))
|
||||
rawConn := reflect.Indirect(reflect.ValueOf(conn))
|
||||
rawHalfConn := rawConn.FieldByName("in")
|
||||
if !rawHalfConn.IsValid() || rawHalfConn.Kind() != reflect.Struct {
|
||||
return nil, E.New("badtls: invalid half conn")
|
||||
@@ -57,11 +71,13 @@ func NewReadWaitConn(conn tls.Conn) (tls.Conn, error) {
|
||||
}
|
||||
hand := (*bytes.Buffer)(unsafe.Pointer(rawHand.UnsafeAddr()))
|
||||
return &ReadWaitConn{
|
||||
STDConn: stdConn,
|
||||
halfAccess: halfAccess,
|
||||
rawInput: rawInput,
|
||||
input: input,
|
||||
hand: hand,
|
||||
Conn: conn,
|
||||
halfAccess: halfAccess,
|
||||
rawInput: rawInput,
|
||||
input: input,
|
||||
hand: hand,
|
||||
tlsReadRecord: tlsReadRecord,
|
||||
tlsHandlePostHandshakeMessage: tlsHandlePostHandshakeMessage,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -71,19 +87,19 @@ func (c *ReadWaitConn) InitializeReadWaiter(options N.ReadWaitOptions) (needCopy
|
||||
}
|
||||
|
||||
func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
err = c.Handshake()
|
||||
err = c.HandshakeContext(context.Background())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.halfAccess.Lock()
|
||||
defer c.halfAccess.Unlock()
|
||||
for c.input.Len() == 0 {
|
||||
err = tlsReadRecord(c.STDConn)
|
||||
err = c.tlsReadRecord()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for c.hand.Len() > 0 {
|
||||
err = tlsHandlePostHandshakeMessage(c.STDConn)
|
||||
err = c.tlsHandlePostHandshakeMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -100,7 +116,7 @@ func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
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)
|
||||
_ = c.tlsReadRecord()
|
||||
// return n, err // will be io.EOF on closeNotify
|
||||
}
|
||||
|
||||
@@ -109,11 +125,27 @@ func (c *ReadWaitConn) WaitReadBuffer() (buffer *buf.Buffer, err error) {
|
||||
}
|
||||
|
||||
func (c *ReadWaitConn) Upstream() any {
|
||||
return c.STDConn
|
||||
return c.Conn
|
||||
}
|
||||
|
||||
//go:linkname tlsReadRecord crypto/tls.(*Conn).readRecord
|
||||
func tlsReadRecord(c *tls.STDConn) error
|
||||
var tlsRegistry []func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error)
|
||||
|
||||
//go:linkname tlsHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||
func tlsHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := conn.(*tls.STDConn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, func() error {
|
||||
return stdTLSReadRecord(tlsConn)
|
||||
}, func() error {
|
||||
return stdTLSHandlePostHandshakeMessage(tlsConn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname stdTLSReadRecord crypto/tls.(*Conn).readRecord
|
||||
func stdTLSReadRecord(c *tls.STDConn) error
|
||||
|
||||
//go:linkname stdTLSHandlePostHandshakeMessage crypto/tls.(*Conn).handlePostHandshakeMessage
|
||||
func stdTLSHandlePostHandshakeMessage(c *tls.STDConn) error
|
||||
|
||||
31
common/badtls/read_wait_ech.go
Normal file
31
common/badtls/read_wait_ech.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build go1.21 && !without_badtls && with_ech
|
||||
|
||||
package badtls
|
||||
|
||||
import (
|
||||
"net"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/cloudflare-tls"
|
||||
"github.com/sagernet/sing/common"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := common.Cast[*tls.Conn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, func() error {
|
||||
return echReadRecord(tlsConn)
|
||||
}, func() error {
|
||||
return echHandlePostHandshakeMessage(tlsConn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname echReadRecord github.com/sagernet/cloudflare-tls.(*Conn).readRecord
|
||||
func echReadRecord(c *tls.Conn) error
|
||||
|
||||
//go:linkname echHandlePostHandshakeMessage github.com/sagernet/cloudflare-tls.(*Conn).handlePostHandshakeMessage
|
||||
func echHandlePostHandshakeMessage(c *tls.Conn) error
|
||||
31
common/badtls/read_wait_utls.go
Normal file
31
common/badtls/read_wait_utls.go
Normal file
@@ -0,0 +1,31 @@
|
||||
//go:build go1.21 && !without_badtls && with_utls
|
||||
|
||||
package badtls
|
||||
|
||||
import (
|
||||
"net"
|
||||
_ "unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/utls"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tlsRegistry = append(tlsRegistry, func(conn net.Conn) (loaded bool, tlsReadRecord func() error, tlsHandlePostHandshakeMessage func() error) {
|
||||
tlsConn, loaded := common.Cast[*tls.UConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, func() error {
|
||||
return utlsReadRecord(tlsConn.Conn)
|
||||
}, func() error {
|
||||
return utlsHandlePostHandshakeMessage(tlsConn.Conn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//go:linkname utlsReadRecord github.com/sagernet/utls.(*Conn).readRecord
|
||||
func utlsReadRecord(c *tls.Conn) error
|
||||
|
||||
//go:linkname utlsHandlePostHandshakeMessage github.com/sagernet/utls.(*Conn).handlePostHandshakeMessage
|
||||
func utlsHandlePostHandshakeMessage(c *tls.Conn) error
|
||||
@@ -32,24 +32,44 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
||||
var dialer net.Dialer
|
||||
var listener net.ListenConfig
|
||||
if options.BindInterface != "" {
|
||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), options.BindInterface, -1)
|
||||
var interfaceFinder control.InterfaceFinder
|
||||
if router != nil {
|
||||
interfaceFinder = router.InterfaceFinder()
|
||||
} else {
|
||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||
}
|
||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.AutoDetectInterface() {
|
||||
} else if router != nil && router.AutoDetectInterface() {
|
||||
bindFunc := router.AutoDetectInterfaceFunc()
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
} else if router.DefaultInterface() != "" {
|
||||
} else if router != nil && router.DefaultInterface() != "" {
|
||||
bindFunc := control.BindToInterface(router.InterfaceFinder(), router.DefaultInterface(), -1)
|
||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||
listener.Control = control.Append(listener.Control, bindFunc)
|
||||
}
|
||||
if options.RoutingMark != 0 {
|
||||
var autoRedirectOutputMark uint32
|
||||
if router != nil {
|
||||
autoRedirectOutputMark = router.AutoRedirectOutputMark()
|
||||
}
|
||||
if autoRedirectOutputMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(autoRedirectOutputMark))
|
||||
}
|
||||
if options.RoutingMark > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(options.RoutingMark))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(options.RoutingMark))
|
||||
} else if router.DefaultMark() != 0 {
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `routing_mark`")
|
||||
}
|
||||
} else if router != nil && router.DefaultMark() > 0 {
|
||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(router.DefaultMark()))
|
||||
listener.Control = control.Append(listener.Control, control.RoutingMark(router.DefaultMark()))
|
||||
if autoRedirectOutputMark > 0 {
|
||||
return nil, E.New("`auto_redirect` with `route_[_exclude]_address_set is conflict with `default_mark`")
|
||||
}
|
||||
}
|
||||
if options.ReuseAddr {
|
||||
listener.Control = control.Append(listener.Control, control.ReuseAddr())
|
||||
@@ -63,6 +83,9 @@ func NewDefault(router adapter.Router, options option.DialerOptions) (*DefaultDi
|
||||
} else {
|
||||
dialer.Timeout = C.TCPTimeout
|
||||
}
|
||||
// TODO: Add an option to customize the keep alive period
|
||||
dialer.KeepAlive = C.TCPKeepAliveInitial
|
||||
dialer.Control = control.Append(dialer.Control, control.SetKeepAlivePeriod(C.TCPKeepAliveInitial, C.TCPKeepAliveInterval))
|
||||
var udpFragment bool
|
||||
if options.UDPFragment != nil {
|
||||
udpFragment = *options.UDPFragment
|
||||
|
||||
@@ -13,6 +13,9 @@ func New(router adapter.Router, options option.DialerOptions) (N.Dialer, error)
|
||||
if options.IsWireGuardListener {
|
||||
return NewDefault(router, options)
|
||||
}
|
||||
if router == nil {
|
||||
return NewDefault(nil, options)
|
||||
}
|
||||
var (
|
||||
dialer N.Dialer
|
||||
err error
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package geosite
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
type Reader struct {
|
||||
@@ -34,45 +38,36 @@ func Open(path string) (*Reader, []string, error) {
|
||||
return reader, codes, nil
|
||||
}
|
||||
|
||||
type geositeMetadata struct {
|
||||
Code string
|
||||
Index uint64
|
||||
Length uint64
|
||||
}
|
||||
|
||||
func (r *Reader) readMetadata() error {
|
||||
version, err := rw.ReadByte(r.reader)
|
||||
reader := bufio.NewReader(r.reader)
|
||||
version, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version != 0 {
|
||||
return E.New("unknown version")
|
||||
}
|
||||
entryLength, err := rw.ReadUVariant(r.reader)
|
||||
metadataEntries, err := varbin.ReadValue[[]geositeMetadata](reader, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys := make([]string, entryLength)
|
||||
domainIndex := make(map[string]int)
|
||||
domainLength := make(map[string]int)
|
||||
for i := 0; i < int(entryLength); i++ {
|
||||
var (
|
||||
code string
|
||||
codeIndex uint64
|
||||
codeLength uint64
|
||||
)
|
||||
code, err = rw.ReadVString(r.reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys[i] = code
|
||||
codeIndex, err = rw.ReadUVariant(r.reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
codeLength, err = rw.ReadUVariant(r.reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domainIndex[code] = int(codeIndex)
|
||||
domainLength[code] = int(codeLength)
|
||||
for _, entry := range metadataEntries {
|
||||
domainIndex[entry.Code] = int(entry.Index)
|
||||
domainLength[entry.Code] = int(entry.Length)
|
||||
}
|
||||
r.domainIndex = domainIndex
|
||||
r.domainLength = domainLength
|
||||
if reader.Buffered() > 0 {
|
||||
return common.Error(r.reader.Seek(int64(-reader.Buffered()), io.SeekCurrent))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -85,27 +80,28 @@ func (r *Reader) Read(code string) ([]Item, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
counter := &rw.ReadCounter{Reader: r.reader}
|
||||
domain := make([]Item, r.domainLength[code])
|
||||
for i := range domain {
|
||||
var (
|
||||
item Item
|
||||
err error
|
||||
)
|
||||
item.Type, err = rw.ReadByte(counter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Value, err = rw.ReadVString(counter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
domain[i] = item
|
||||
counter := &readCounter{Reader: r.reader}
|
||||
domain, err := varbin.ReadValue[[]Item](bufio.NewReader(counter), binary.BigEndian)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = r.reader.Seek(int64(-index)-counter.Count(), io.SeekCurrent)
|
||||
_, err = r.reader.Seek(int64(-index)-counter.count, io.SeekCurrent)
|
||||
return domain, err
|
||||
}
|
||||
|
||||
func (r *Reader) Upstream() any {
|
||||
return r.reader
|
||||
}
|
||||
|
||||
type readCounter struct {
|
||||
io.Reader
|
||||
count int64
|
||||
}
|
||||
|
||||
func (r *readCounter) Read(p []byte) (n int, err error) {
|
||||
n, err = r.Reader.Read(p)
|
||||
if n > 0 {
|
||||
atomic.AddInt64(&r.count, int64(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,13 +2,14 @@ package geosite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"encoding/binary"
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func Write(writer io.Writer, domains map[string][]Item) error {
|
||||
func Write(writer varbin.Writer, domains map[string][]Item) error {
|
||||
keys := make([]string, 0, len(domains))
|
||||
for code := range domains {
|
||||
keys = append(keys, code)
|
||||
@@ -19,40 +20,28 @@ func Write(writer io.Writer, domains map[string][]Item) error {
|
||||
index := make(map[string]int)
|
||||
for _, code := range keys {
|
||||
index[code] = content.Len()
|
||||
for _, domain := range domains[code] {
|
||||
content.WriteByte(domain.Type)
|
||||
err := rw.WriteVString(content, domain.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err := varbin.Write(content, binary.BigEndian, domains[code])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := rw.WriteByte(writer, 0)
|
||||
err := writer.WriteByte(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = rw.WriteUVariant(writer, uint64(len(keys)))
|
||||
err = varbin.Write(writer, binary.BigEndian, common.Map(keys, func(it string) *geositeMetadata {
|
||||
return &geositeMetadata{
|
||||
Code: it,
|
||||
Index: uint64(index[it]),
|
||||
Length: uint64(len(domains[it])),
|
||||
}
|
||||
}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, code := range keys {
|
||||
err = rw.WriteVString(writer, code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(index[code]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(domains[code])))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = writer.Write(content.Bytes())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -223,7 +223,7 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
||||
r1, _, err := syscall.SyscallN(
|
||||
procQueryFullProcessImageNameW.Addr(),
|
||||
uintptr(h),
|
||||
uintptr(1),
|
||||
uintptr(0),
|
||||
uintptr(unsafe.Pointer(&buf[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
|
||||
@@ -16,30 +16,40 @@ import (
|
||||
)
|
||||
|
||||
type LinuxSystemProxy struct {
|
||||
hasGSettings bool
|
||||
hasKWriteConfig5 bool
|
||||
sudoUser string
|
||||
serverAddr M.Socksaddr
|
||||
supportSOCKS bool
|
||||
isEnabled bool
|
||||
hasGSettings bool
|
||||
kWriteConfigCmd string
|
||||
sudoUser string
|
||||
serverAddr M.Socksaddr
|
||||
supportSOCKS bool
|
||||
isEnabled bool
|
||||
}
|
||||
|
||||
func NewSystemProxy(ctx context.Context, serverAddr M.Socksaddr, supportSOCKS bool) (*LinuxSystemProxy, error) {
|
||||
hasGSettings := common.Error(exec.LookPath("gsettings")) == nil
|
||||
hasKWriteConfig5 := common.Error(exec.LookPath("kwriteconfig5")) == nil
|
||||
kWriteConfigCmds := []string{
|
||||
"kwriteconfig5",
|
||||
"kwriteconfig6",
|
||||
}
|
||||
var kWriteConfigCmd string
|
||||
for _, cmd := range kWriteConfigCmds {
|
||||
if common.Error(exec.LookPath(cmd)) == nil {
|
||||
kWriteConfigCmd = cmd
|
||||
break
|
||||
}
|
||||
}
|
||||
var sudoUser string
|
||||
if os.Getuid() == 0 {
|
||||
sudoUser = os.Getenv("SUDO_USER")
|
||||
}
|
||||
if !hasGSettings && !hasKWriteConfig5 {
|
||||
if !hasGSettings && kWriteConfigCmd == "" {
|
||||
return nil, E.New("unsupported desktop environment")
|
||||
}
|
||||
return &LinuxSystemProxy{
|
||||
hasGSettings: hasGSettings,
|
||||
hasKWriteConfig5: hasKWriteConfig5,
|
||||
sudoUser: sudoUser,
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
hasGSettings: hasGSettings,
|
||||
kWriteConfigCmd: kWriteConfigCmd,
|
||||
sudoUser: sudoUser,
|
||||
serverAddr: serverAddr,
|
||||
supportSOCKS: supportSOCKS,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -70,8 +80,8 @@ func (p *LinuxSystemProxy) Enable() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.hasKWriteConfig5 {
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
|
||||
if p.kWriteConfigCmd != "" {
|
||||
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "1")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +93,7 @@ func (p *LinuxSystemProxy) Enable() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
|
||||
err = p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "Authmode", "0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -103,8 +113,8 @@ func (p *LinuxSystemProxy) Disable() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if p.hasKWriteConfig5 {
|
||||
err := p.runAsUser("kwriteconfig5", "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
|
||||
if p.kWriteConfigCmd != "" {
|
||||
err := p.runAsUser(p.kWriteConfigCmd, "--file", "kioslaverc", "--group", "Proxy Settings", "--key", "ProxyType", "0")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -150,7 +160,7 @@ func (p *LinuxSystemProxy) setKDEProxy(proxyTypes ...string) error {
|
||||
proxyUrl = "http://" + p.serverAddr.String()
|
||||
}
|
||||
err := p.runAsUser(
|
||||
"kwriteconfig5",
|
||||
p.kWriteConfigCmd,
|
||||
"--file",
|
||||
"kioslaverc",
|
||||
"--group",
|
||||
|
||||
113
common/sniff/bittorrent.go
Normal file
113
common/sniff/bittorrent.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
const (
|
||||
trackerConnectFlag = iota
|
||||
trackerAnnounceFlag
|
||||
trackerScrapeFlag
|
||||
|
||||
trackerProtocolID = 0x41727101980
|
||||
|
||||
trackerConnectMinSize = 16
|
||||
trackerAnnounceMinSize = 20
|
||||
trackerScrapeMinSize = 8
|
||||
)
|
||||
|
||||
// BitTorrent detects if the stream is a BitTorrent connection.
|
||||
// For the BitTorrent protocol specification, see https://www.bittorrent.org/beps/bep_0003.html
|
||||
func BitTorrent(_ context.Context, reader io.Reader) (*adapter.InboundContext, error) {
|
||||
var first byte
|
||||
err := binary.Read(reader, binary.BigEndian, &first)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if first != 19 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
var protocol [19]byte
|
||||
_, err = reader.Read(protocol[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if string(protocol[:]) != "BitTorrent protocol" {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UTP detects if the packet is a uTP connection packet.
|
||||
// For the uTP protocol specification, see
|
||||
// 1. https://www.bittorrent.org/beps/bep_0029.html
|
||||
// 2. https://github.com/bittorrent/libutp/blob/2b364cbb0650bdab64a5de2abb4518f9f228ec44/utp_internal.cpp#L112
|
||||
func UTP(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
// A valid uTP packet must be at least 20 bytes long.
|
||||
if len(packet) < 20 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
version := packet[0] & 0x0F
|
||||
ty := packet[0] >> 4
|
||||
if version != 1 || ty > 4 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
|
||||
// Validate the extensions
|
||||
extension := packet[1]
|
||||
reader := bytes.NewReader(packet[20:])
|
||||
for extension != 0 {
|
||||
err := binary.Read(reader, binary.BigEndian, &extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var length byte
|
||||
err = binary.Read(reader, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = reader.Seek(int64(length), io.SeekCurrent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UDPTracker detects if the packet is a UDP Tracker Protocol packet.
|
||||
// For the UDP Tracker Protocol specification, see https://www.bittorrent.org/beps/bep_0015.html
|
||||
func UDPTracker(_ context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
switch {
|
||||
case len(packet) >= trackerConnectMinSize &&
|
||||
binary.BigEndian.Uint64(packet[:8]) == trackerProtocolID &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerConnectFlag:
|
||||
fallthrough
|
||||
case len(packet) >= trackerAnnounceMinSize &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerAnnounceFlag:
|
||||
fallthrough
|
||||
case len(packet) >= trackerScrapeMinSize &&
|
||||
binary.BigEndian.Uint32(packet[8:12]) == trackerScrapeFlag:
|
||||
return &adapter.InboundContext{
|
||||
Protocol: C.ProtocolBitTorrent,
|
||||
}, nil
|
||||
default:
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
}
|
||||
81
common/sniff/bittorrent_test.go
Normal file
81
common/sniff/bittorrent_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffBittorrent(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"13426974546f7272656e742070726f746f636f6c0000000000100000e21ea9569b69bab33c97851d0298bdfa89bc90922d5554313631302dea812fcd6a3563e3be40c1d1",
|
||||
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452333030302d653369733079647675763638",
|
||||
"13426974546f7272656e742070726f746f636f6c00000000001000052aa4f5a7e209e54b32803d43670971c4c8caaa052d5452343035302d6f7a316c6e79377931716130",
|
||||
}
|
||||
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
metadata, err := sniff.BitTorrent(context.TODO(), bytes.NewReader(pkt))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffUTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
packets := []string{
|
||||
"010041a282d7ee7b583afb160004000006d8318da776968f92d666f7963f32dae23ba0d2c810d8b8209cc4939f54fde9eeaa521c2c20c9ba7f43f4fb0375f28de06643b5e3ca4685ab7ac76adca99783be72ef05ed59ef4234f5712b75b4c7c0d7bee8fe2ca20ad626ba5bb0ffcc16bf06790896f888048cf72716419a07db1a3dca4550fbcea75b53e97235168a221cf3e553dfbb723961bd719fab038d86e0ecb74747f5a2cd669de1c4b9ad375f3a492d09d98cdfad745435625401315bbba98d35d32086299801377b93495a63a9efddb8d05f5b37a5c5b1c0a25e917f12007bb5e05013ada8aff544fab8cadf61d80ddb0b60f12741e44515a109d144fd53ef845acb4b5ccf0d6fc302d7003d76df3fc3423bb0237301c9e88f900c2d392a8e0fdb36d143cf7527a93fd0a2638b746e72f6699fffcd4fd15348fce780d4caa04382fd9faf1ca0ae377ca805da7536662b84f5ee18dd3ae38fcb095a7543e55f9069ae92c8cf54ae44e97b558d35e2545c66601ed2149cbc32bd6df199a2be7cf0da8b2ff137e0d23e776bc87248425013876d3a3cc31a83b424b752bd0346437f24b532978005d8f5b1b0be1a37a2489c32a18a9ad3118e3f9d30eb299bffae18e1f0677c2a5c185e62519093fe6bc2b7339299ea50a587989f726ca6443a75dd5bb936f6367c6355d80fae53ff529d740b2e5576e3eefdf1fdbfc69c3c8d8ac750512635de63e054bee1d3b689bc1b2bc3d2601e42a00b5c89066d173d4ae7ffedfd2274e5cf6d868fbe640aedb69b8246142f00b32d459974287537ddd5373460dcbc92f5cfdd7a3ed6020822ae922d947893752ca1983d0d32977374c384ac8f5ab566859019b7351526b9f13e932037a55bb052d9deb3b3c23317e0784fdc51a64f2159bfea3b069cf5caf02ee2c3c1a6b6b427bb16165713e8802d95b5c8ed77953690e994bd38c9ae113fedaf6ee7fc2b96c032ceafc2a530ad0422e84546b9c6ad8ef6ea02fa508abddd1805c38a7b42e9b7c971b1b636865ebec06ed754bb404cd6b4e6cc8cb77bd4a0c43410d5cd5ef8fe853a66d49b3b9e06cb141236cdbfdd5761601dc54d1250b86c660e0f898fe62526fdd9acf0eab60a3bbbb2151970461f28f10b31689594bea646c4b03ee197d63bdef4e5a7c22716b3bb9494a83b78ecd81b338b80ac6c09c43485b1b09ba41c74343832c78f0520c1d659ac9eb1502094141e82fb9e5e620970ebc0655514c43c294a7714cbf9a499d277daf089f556398a01589a77494bec8bfb60a108f3813b55368672b88c1af40f6b3c8b513f7c70c3e0efce85228b8b9ec67ba0393f9f7305024d8e2da6a26cf85613d14f249170ce1000089df4c9c260df7f8292aa2ecb5d5bac97656d59aa248caedea2d198e51ce87baece338716d114b458de02d65c9ff808ca5b5b73723b4d1e962d9ac2d98176544dc9984cf8554d07820ef3dd0861cfe57b478328046380de589adad94ee44743ffac73bb7361feca5d56f07cf8ce75080e261282ae30350d7882679b15cab9e7e53ddf93310b33f7390ae5d318bb53f387e6af5d0ef4f947fc9cb8e7e38b52c7f8d772ece6156b38d88796ea19df02c53723b44df7c76315a0de9462f27287e682d2b4cda1a68fe00d7e48c51ee981be44e1ca940fb5190c12655edb4a83c3a4f33e48a015692df4f0b3d61656e362aca657b5ae8c12db5a0db3db1e45135ee918b66918f40e53c4f83e9da0cddfe63f736ae751ab3837a30ae3220d8e8e311487093a7b90c7e7e40dd54ca750e19452f9193aa892aa6a6229ab493dadae988b1724f7898ee69c36d3eb7364c4adbeca811cfe2065873e78c2b6dfdf1595f7a7831c07e03cda82e4f86f76438dfb2b07c13638ce7b509cfa71b88b5102b39a203b423202088e1c2103319cb32c13c1e546ff8612fa194c95a7808ab767c265a1bd5fa0efed5c8ec1701876a00ec8",
|
||||
"01001ecb68176f215d04326300100000dbcf30292d14b54e9ee2d115ee5b8ebc7fad3e882d4fcdd0c14c6b917c11cb4c6a9f410b52a33ae97c2ac77c7a2b122b8955e09af3c5c595f1b2e79ca57cfe44c44e069610773b9bc9ba223d7f6b383e3adddd03fb88a8476028e30979c2ef321ffc97c5c132bcf9ac5b410bbb5ec6cefca3c7209202a14c5ae922b6b157b0a80249d13ffe5b996af0bc8e54ba576d148372494303e7ead0602b05b9c8fc97d48508a028a04d63a1fd28b0edfcd5c51715f63188b53eefede98a76912dca98518551a8856567307a56a702cbfcc115ea0c755b418bc2c7b57721239b82f09fb24328a4b0ce0f109bcb2a64e04b8aadb1f8487585425acdf8fc4ec8ea93cfcec5ac098bb29d42ddef6e46b03f34a5de28316726699b7cb5195c33e5c48abe87d591d63f9991c84c30819d186d6e0e95fd83c8dff07aa669c4430989bcaccfeacb9bcadbdb4d8f1964dbeb9687745656edd30b21c66cc0a1d742a78717d134a19a7f02d285a4973b1a198c00cfdff4676608dc4f3e817e3463c3b4e2c80d3e8d4fbac541a58a2fb7ad6939f607f8144eff6c8b0adc28ee5609ea158987519892fb",
|
||||
"21001ecb6817f2805d044fd700100000dbd03029",
|
||||
"410277ef0b1fb1f60000000000040000c233000000080000000000000000",
|
||||
}
|
||||
|
||||
for _, pkt := range packets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sniff.UTP(context.TODO(), pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSniffUDPTracker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
connectPackets := []string{
|
||||
// connect packets
|
||||
"00000417271019800000000078e90560",
|
||||
"00000417271019800000000022c5d64d",
|
||||
"000004172710198000000000b3863541",
|
||||
|
||||
// announce packets
|
||||
"3d7592ead4b8c9e300000001b871a3820000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
"3d7592ead4b8c9e30000000188deed1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
"3d7592ead4b8c9e300000001ceb948ad0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a3362cdb7020ff920e5aa642c3d4066950dd1f01f4d00000000000000000000000000000000000000000000000000000000000000000000000000000000000002092f616e6e6f756e6365",
|
||||
|
||||
// scrape packets
|
||||
"3d7592ead4b8c9e300000002d2f4bba5a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
|
||||
"3d7592ead4b8c9e300000002441243292aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
|
||||
"3d7592ead4b8c9e300000002b2aa461b1ad1fa9661cf3fe45fb2504ad52ec6c67758e294",
|
||||
}
|
||||
|
||||
for _, pkt := range connectPackets {
|
||||
pkt, err := hex.DecodeString(pkt)
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sniff.UDPTracker(context.TODO(), pkt)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, C.ProtocolBitTorrent, metadata.Protocol)
|
||||
}
|
||||
}
|
||||
31
common/sniff/dtls.go
Normal file
31
common/sniff/dtls.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package sniff
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
)
|
||||
|
||||
func DTLSRecord(ctx context.Context, packet []byte) (*adapter.InboundContext, error) {
|
||||
const fixedHeaderSize = 13
|
||||
if len(packet) < fixedHeaderSize {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
contentType := packet[0]
|
||||
switch contentType {
|
||||
case 20, 21, 22, 23, 25:
|
||||
default:
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
versionMajor := packet[1]
|
||||
if versionMajor != 0xfe {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
versionMinor := packet[2]
|
||||
if versionMinor != 0xff && versionMinor != 0xfd {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
return &adapter.InboundContext{Protocol: C.ProtocolDTLS}, nil
|
||||
}
|
||||
30
common/sniff/dtls_test.go
Normal file
30
common/sniff/dtls_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package sniff_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSniffDTLSClientHello(t *testing.T) {
|
||||
t.Parallel()
|
||||
packet, err := hex.DecodeString("16fefd0000000000000000007e010000720000000000000072fefd668a43523798e064bd806d0c87660de9c611a59bbdfc3892c4e072d94f2cafc40000000cc02bc02fc00ac014c02cc0300100003c000d0010000e0403050306030401050106010807ff01000100000a00080006001d00170018000b00020100000e000900060008000700010000170000")
|
||||
require.NoError(t, err)
|
||||
metadata, err := sniff.DTLSRecord(context.Background(), packet)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, metadata.Protocol, C.ProtocolDTLS)
|
||||
}
|
||||
|
||||
func TestSniffDTLSClientApplicationData(t *testing.T) {
|
||||
t.Parallel()
|
||||
packet, err := hex.DecodeString("17fefd000100000000000100440001000000000001a4f682b77ecadd10f3f3a2f78d90566212366ff8209fd77314f5a49352f9bb9bd12f4daba0b4736ae29e46b9714d3b424b3e6d0234736619b5aa0d3f")
|
||||
require.NoError(t, err)
|
||||
metadata, err := sniff.DTLSRecord(context.Background(), packet)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, metadata.Protocol, C.ProtocolDTLS)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package srs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/zlib"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/domain"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
@@ -38,14 +39,14 @@ const (
|
||||
ruleItemFinal uint8 = 0xFF
|
||||
)
|
||||
|
||||
func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err error) {
|
||||
func Read(reader io.Reader, recover bool) (ruleSet option.PlainRuleSet, err error) {
|
||||
var magicBytes [3]byte
|
||||
_, err = io.ReadFull(reader, magicBytes[:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if magicBytes != MagicBytes {
|
||||
err = E.New("invalid sing-box rule set file")
|
||||
err = E.New("invalid sing-box rule-set file")
|
||||
return
|
||||
}
|
||||
var version uint8
|
||||
@@ -60,13 +61,14 @@ func Read(reader io.Reader, recovery bool) (ruleSet option.PlainRuleSet, err err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
length, err := rw.ReadUVariant(zReader)
|
||||
bReader := bufio.NewReader(zReader)
|
||||
length, err := binary.ReadUvarint(bReader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ruleSet.Rules = make([]option.HeadlessRule, length)
|
||||
for i := uint64(0); i < length; i++ {
|
||||
ruleSet.Rules[i], err = readRule(zReader, recovery)
|
||||
ruleSet.Rules[i], err = readRule(bReader, recover)
|
||||
if err != nil {
|
||||
err = E.Cause(err, "read rule[", i, "]")
|
||||
return
|
||||
@@ -88,20 +90,25 @@ func Write(writer io.Writer, ruleSet option.PlainRuleSet) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(zWriter, uint64(len(ruleSet.Rules)))
|
||||
bWriter := bufio.NewWriter(zWriter)
|
||||
_, err = varbin.WriteUvarint(bWriter, uint64(len(ruleSet.Rules)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rule := range ruleSet.Rules {
|
||||
err = writeRule(zWriter, rule)
|
||||
err = writeRule(bWriter, rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = bWriter.Flush()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return zWriter.Close()
|
||||
}
|
||||
|
||||
func readRule(reader io.Reader, recovery bool) (rule option.HeadlessRule, err error) {
|
||||
func readRule(reader varbin.Reader, recover bool) (rule option.HeadlessRule, err error) {
|
||||
var ruleType uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &ruleType)
|
||||
if err != nil {
|
||||
@@ -110,17 +117,17 @@ 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)
|
||||
rule.DefaultOptions, err = readDefaultRule(reader, recover)
|
||||
case 1:
|
||||
rule.Type = C.RuleTypeLogical
|
||||
rule.LogicalOptions, err = readLogicalRule(reader, recovery)
|
||||
rule.LogicalOptions, err = readLogicalRule(reader, recover)
|
||||
default:
|
||||
err = E.New("unknown rule type: ", ruleType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeRule(writer io.Writer, rule option.HeadlessRule) error {
|
||||
func writeRule(writer varbin.Writer, rule option.HeadlessRule) error {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
return writeDefaultRule(writer, rule.DefaultOptions)
|
||||
@@ -131,7 +138,7 @@ func writeRule(writer io.Writer, rule option.HeadlessRule) error {
|
||||
}
|
||||
}
|
||||
|
||||
func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadlessRule, err error) {
|
||||
func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHeadlessRule, err error) {
|
||||
var lastItemType uint8
|
||||
for {
|
||||
var itemType uint8
|
||||
@@ -158,6 +165,9 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
|
||||
return
|
||||
}
|
||||
rule.DomainMatcher = matcher
|
||||
if recover {
|
||||
rule.Domain, rule.DomainSuffix = matcher.Dump()
|
||||
}
|
||||
case ruleItemDomainKeyword:
|
||||
rule.DomainKeyword, err = readRuleItemString(reader)
|
||||
case ruleItemDomainRegex:
|
||||
@@ -167,7 +177,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if recovery {
|
||||
if recover {
|
||||
rule.SourceIPCIDR = common.Map(rule.SourceIPSet.Prefixes(), netip.Prefix.String)
|
||||
}
|
||||
case ruleItemIPCIDR:
|
||||
@@ -175,7 +185,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if recovery {
|
||||
if recover {
|
||||
rule.IPCIDR = common.Map(rule.IPSet.Prefixes(), netip.Prefix.String)
|
||||
}
|
||||
case ruleItemSourcePort:
|
||||
@@ -209,7 +219,7 @@ func readDefaultRule(reader io.Reader, recovery bool) (rule option.DefaultHeadle
|
||||
}
|
||||
}
|
||||
|
||||
func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
|
||||
func writeDefaultRule(writer varbin.Writer, rule option.DefaultHeadlessRule) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(0))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -327,73 +337,31 @@ func writeDefaultRule(writer io.Writer, rule option.DefaultHeadlessRule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readRuleItemString(reader io.Reader) ([]string, error) {
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := make([]string, length)
|
||||
for i := uint64(0); i < length; i++ {
|
||||
value[i], err = rw.ReadVString(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
func readRuleItemString(reader varbin.Reader) ([]string, error) {
|
||||
return varbin.ReadValue[[]string](reader, binary.BigEndian)
|
||||
}
|
||||
|
||||
func writeRuleItemString(writer io.Writer, itemType uint8, value []string) error {
|
||||
err := binary.Write(writer, binary.BigEndian, itemType)
|
||||
func writeRuleItemString(writer varbin.Writer, itemType uint8, value []string) error {
|
||||
err := writer.WriteByte(itemType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(value)))
|
||||
return varbin.Write(writer, binary.BigEndian, value)
|
||||
}
|
||||
|
||||
func readRuleItemUint16(reader varbin.Reader) ([]uint16, error) {
|
||||
return varbin.ReadValue[[]uint16](reader, binary.BigEndian)
|
||||
}
|
||||
|
||||
func writeRuleItemUint16(writer varbin.Writer, itemType uint8, value []uint16) error {
|
||||
err := writer.WriteByte(itemType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range value {
|
||||
err = rw.WriteVString(writer, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return varbin.Write(writer, binary.BigEndian, value)
|
||||
}
|
||||
|
||||
func readRuleItemUint16(reader io.Reader) ([]uint16, error) {
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
value := make([]uint16, length)
|
||||
for i := uint64(0); i < length; i++ {
|
||||
err = binary.Read(reader, binary.BigEndian, &value[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func writeRuleItemUint16(writer io.Writer, itemType uint8, value []uint16) error {
|
||||
err := binary.Write(writer, binary.BigEndian, itemType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(value)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range value {
|
||||
err = binary.Write(writer, binary.BigEndian, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
|
||||
func writeRuleItemCIDR(writer varbin.Writer, itemType uint8, value []string) error {
|
||||
var builder netipx.IPSetBuilder
|
||||
for i, prefixString := range value {
|
||||
prefix, err := netip.ParsePrefix(prefixString)
|
||||
@@ -419,9 +387,8 @@ func writeRuleItemCIDR(writer io.Writer, itemType uint8, value []string) error {
|
||||
return writeIPSet(writer, ipSet)
|
||||
}
|
||||
|
||||
func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
|
||||
var mode uint8
|
||||
err = binary.Read(reader, binary.BigEndian, &mode)
|
||||
func readLogicalRule(reader varbin.Reader, recovery bool) (logicalRule option.LogicalHeadlessRule, err error) {
|
||||
mode, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -434,7 +401,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica
|
||||
err = E.New("unknown logical mode: ", mode)
|
||||
return
|
||||
}
|
||||
length, err := rw.ReadUVariant(reader)
|
||||
length, err := binary.ReadUvarint(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -453,7 +420,7 @@ func readLogicalRule(reader io.Reader, recovery bool) (logicalRule option.Logica
|
||||
return
|
||||
}
|
||||
|
||||
func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule) error {
|
||||
func writeLogicalRule(writer varbin.Writer, logicalRule option.LogicalHeadlessRule) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -469,7 +436,7 @@ func writeLogicalRule(writer io.Writer, logicalRule option.LogicalHeadlessRule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(logicalRule.Rules)))
|
||||
_, err = varbin.WriteUvarint(writer, uint64(len(logicalRule.Rules)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package srs
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
@@ -20,94 +22,57 @@ type myIPRange struct {
|
||||
to netip.Addr
|
||||
}
|
||||
|
||||
func readIPSet(reader io.Reader) (*netipx.IPSet, error) {
|
||||
var version uint8
|
||||
err := binary.Read(reader, binary.BigEndian, &version)
|
||||
type myIPRangeData struct {
|
||||
From []byte
|
||||
To []byte
|
||||
}
|
||||
|
||||
func readIPSet(reader varbin.Reader) (*netipx.IPSet, error) {
|
||||
version, err := reader.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version != 1 {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
// WTF why using uint64 here
|
||||
var length uint64
|
||||
err = binary.Read(reader, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mySet := &myIPSet{
|
||||
rr: make([]myIPRange, length),
|
||||
ranges := make([]myIPRangeData, length)
|
||||
err = varbin.Read(reader, binary.BigEndian, &ranges)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := uint64(0); i < length; i++ {
|
||||
var (
|
||||
fromLen uint64
|
||||
toLen uint64
|
||||
fromAddr netip.Addr
|
||||
toAddr netip.Addr
|
||||
)
|
||||
fromLen, err = rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fromBytes := make([]byte, fromLen)
|
||||
_, err = io.ReadFull(reader, fromBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = fromAddr.UnmarshalBinary(fromBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toLen, err = rw.ReadUVariant(reader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toBytes := make([]byte, toLen)
|
||||
_, err = io.ReadFull(reader, toBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = toAddr.UnmarshalBinary(toBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mySet.rr[i] = myIPRange{fromAddr, toAddr}
|
||||
mySet := &myIPSet{
|
||||
rr: make([]myIPRange, len(ranges)),
|
||||
}
|
||||
for i, rangeData := range ranges {
|
||||
mySet.rr[i].from = M.AddrFromIP(rangeData.From)
|
||||
mySet.rr[i].to = M.AddrFromIP(rangeData.To)
|
||||
}
|
||||
return (*netipx.IPSet)(unsafe.Pointer(mySet)), nil
|
||||
}
|
||||
|
||||
func writeIPSet(writer io.Writer, set *netipx.IPSet) error {
|
||||
err := binary.Write(writer, binary.BigEndian, uint8(1))
|
||||
func writeIPSet(writer varbin.Writer, set *netipx.IPSet) error {
|
||||
err := writer.WriteByte(1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mySet := (*myIPSet)(unsafe.Pointer(set))
|
||||
err = binary.Write(writer, binary.BigEndian, uint64(len(mySet.rr)))
|
||||
dataList := common.Map((*myIPSet)(unsafe.Pointer(set)).rr, func(rr myIPRange) myIPRangeData {
|
||||
return myIPRangeData{
|
||||
From: rr.from.AsSlice(),
|
||||
To: rr.to.AsSlice(),
|
||||
}
|
||||
})
|
||||
err = binary.Write(writer, binary.BigEndian, uint64(len(dataList)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, rr := range mySet.rr {
|
||||
var (
|
||||
fromBinary []byte
|
||||
toBinary []byte
|
||||
)
|
||||
fromBinary, err = rr.from.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(fromBinary)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(fromBinary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
toBinary, err = rr.to.MarshalBinary()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteUVariant(writer, uint64(len(toBinary)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = writer.Write(toBinary)
|
||||
for _, data := range dataList {
|
||||
err = varbin.Write(writer, binary.BigEndian, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -27,11 +27,10 @@ func (c *echClientConfig) DialEarly(ctx context.Context, conn net.PacketConn, ad
|
||||
return quic.DialEarly(ctx, conn, addr, c.config, config)
|
||||
}
|
||||
|
||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config, enableDatagrams bool) http.RoundTripper {
|
||||
func (c *echClientConfig) CreateTransport(conn net.PacketConn, quicConnPtr *quic.EarlyConnection, serverAddr M.Socksaddr, quicConfig *quic.Config) http.RoundTripper {
|
||||
return &http3.RoundTripper{
|
||||
TLSClientConfig: c.config,
|
||||
QuicConfig: quicConfig,
|
||||
EnableDatagrams: enableDatagrams,
|
||||
QUICConfig: quicConfig,
|
||||
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
quicConn, err := quic.DialEarly(ctx, conn, serverAddr.UDPAddr(), tlsCfg, cfg)
|
||||
if err != nil {
|
||||
|
||||
@@ -11,12 +11,11 @@ import (
|
||||
"strings"
|
||||
|
||||
cftls "github.com/sagernet/cloudflare-tls"
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/ntp"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
type echServerConfig struct {
|
||||
@@ -26,9 +25,8 @@ type echServerConfig struct {
|
||||
key []byte
|
||||
certificatePath string
|
||||
keyPath string
|
||||
watcher *fsnotify.Watcher
|
||||
echKeyPath string
|
||||
echWatcher *fsnotify.Watcher
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
|
||||
func (c *echServerConfig) ServerName() string {
|
||||
@@ -66,146 +64,84 @@ func (c *echServerConfig) Clone() Config {
|
||||
}
|
||||
|
||||
func (c *echServerConfig) Start() error {
|
||||
if c.certificatePath != "" && c.keyPath != "" {
|
||||
err := c.startWatcher()
|
||||
if err != nil {
|
||||
c.logger.Warn("create fsnotify watcher: ", err)
|
||||
}
|
||||
}
|
||||
if c.echKeyPath != "" {
|
||||
err := c.startECHWatcher()
|
||||
if err != nil {
|
||||
c.logger.Warn("create fsnotify watcher: ", err)
|
||||
}
|
||||
err := c.startWatcher()
|
||||
if err != nil {
|
||||
c.logger.Warn("create credentials watcher: ", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *echServerConfig) startWatcher() error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var watchPath []string
|
||||
if c.certificatePath != "" {
|
||||
err = watcher.Add(c.certificatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
watchPath = append(watchPath, c.certificatePath)
|
||||
}
|
||||
if c.keyPath != "" {
|
||||
err = watcher.Add(c.keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
watchPath = append(watchPath, c.keyPath)
|
||||
}
|
||||
if c.echKeyPath != "" {
|
||||
watchPath = append(watchPath, c.echKeyPath)
|
||||
}
|
||||
if len(watchPath) == 0 {
|
||||
return nil
|
||||
}
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: watchPath,
|
||||
Callback: func(path string) {
|
||||
err := c.credentialsUpdated(path)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload credentials from ", path))
|
||||
}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.watcher = watcher
|
||||
go c.loopUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *echServerConfig) loopUpdate() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-c.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||
continue
|
||||
}
|
||||
err := c.reloadKeyPair()
|
||||
func (c *echServerConfig) credentialsUpdated(path string) error {
|
||||
if path == c.certificatePath || path == c.keyPath {
|
||||
if path == c.certificatePath {
|
||||
certificate, err := os.ReadFile(c.certificatePath)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload TLS key pair"))
|
||||
return err
|
||||
}
|
||||
case err, ok := <-c.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *echServerConfig) reloadKeyPair() error {
|
||||
if c.certificatePath != "" {
|
||||
certificate, err := os.ReadFile(c.certificatePath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload certificate from ", c.certificatePath)
|
||||
}
|
||||
c.certificate = certificate
|
||||
}
|
||||
if c.keyPath != "" {
|
||||
key, err := os.ReadFile(c.keyPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload key from ", c.keyPath)
|
||||
}
|
||||
c.key = key
|
||||
}
|
||||
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload key pair")
|
||||
}
|
||||
c.config.Certificates = []cftls.Certificate{keyPair}
|
||||
c.logger.Info("reloaded TLS certificate")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *echServerConfig) startECHWatcher() error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = watcher.Add(c.echKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.echWatcher = watcher
|
||||
go c.loopECHUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *echServerConfig) loopECHUpdate() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-c.echWatcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||
continue
|
||||
}
|
||||
err := c.reloadECHKey()
|
||||
c.certificate = certificate
|
||||
} else {
|
||||
key, err := os.ReadFile(c.keyPath)
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload ECH key"))
|
||||
return err
|
||||
}
|
||||
case err, ok := <-c.echWatcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||
c.key = key
|
||||
}
|
||||
keyPair, err := cftls.X509KeyPair(c.certificate, c.key)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse key pair")
|
||||
}
|
||||
c.config.Certificates = []cftls.Certificate{keyPair}
|
||||
c.logger.Info("reloaded TLS certificate")
|
||||
} else {
|
||||
echKeyContent, err := os.ReadFile(c.echKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, rest := pem.Decode(echKeyContent)
|
||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||
return E.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse ECH keys")
|
||||
}
|
||||
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create ECH key set")
|
||||
}
|
||||
c.config.ServerECHProvider = echKeySet
|
||||
c.logger.Info("reloaded ECH keys")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *echServerConfig) reloadECHKey() error {
|
||||
echKeyContent, err := os.ReadFile(c.echKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, rest := pem.Decode(echKeyContent)
|
||||
if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
|
||||
return E.New("invalid ECH keys pem")
|
||||
}
|
||||
echKeys, err := cftls.EXP_UnmarshalECHKeys(block.Bytes)
|
||||
if err != nil {
|
||||
return E.Cause(err, "parse ECH keys")
|
||||
}
|
||||
echKeySet, err := cftls.EXP_NewECHKeySet(echKeys)
|
||||
if err != nil {
|
||||
return E.Cause(err, "create ECH key set")
|
||||
}
|
||||
c.config.ServerECHProvider = echKeySet
|
||||
c.logger.Info("reloaded ECH keys")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -213,12 +149,7 @@ func (c *echServerConfig) Close() error {
|
||||
var err error
|
||||
if c.watcher != nil {
|
||||
err = E.Append(err, c.watcher.Close(), func(err error) error {
|
||||
return E.Cause(err, "close certificate watcher")
|
||||
})
|
||||
}
|
||||
if c.echWatcher != nil {
|
||||
err = E.Append(err, c.echWatcher.Close(), func(err error) error {
|
||||
return E.Cause(err, "close ECH key watcher")
|
||||
return E.Cause(err, "close credentials watcher")
|
||||
})
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -7,14 +7,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/fswatch"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"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/ntp"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
var errInsecureUnused = E.New("tls: insecure unused")
|
||||
@@ -27,7 +26,7 @@ type STDServerConfig struct {
|
||||
key []byte
|
||||
certificatePath string
|
||||
keyPath string
|
||||
watcher *fsnotify.Watcher
|
||||
watcher *fswatch.Watcher
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) ServerName() string {
|
||||
@@ -88,59 +87,37 @@ func (c *STDServerConfig) Start() error {
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) startWatcher() error {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
var watchPath []string
|
||||
if c.certificatePath != "" {
|
||||
watchPath = append(watchPath, c.certificatePath)
|
||||
}
|
||||
if c.keyPath != "" {
|
||||
watchPath = append(watchPath, c.keyPath)
|
||||
}
|
||||
watcher, err := fswatch.NewWatcher(fswatch.Options{
|
||||
Path: watchPath,
|
||||
Callback: func(path string) {
|
||||
err := c.certificateUpdated(path)
|
||||
if err != nil {
|
||||
c.logger.Error(err)
|
||||
}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.certificatePath != "" {
|
||||
err = watcher.Add(c.certificatePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if c.keyPath != "" {
|
||||
err = watcher.Add(c.keyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
c.watcher = watcher
|
||||
go c.loopUpdate()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) loopUpdate() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-c.watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Op&fsnotify.Write != fsnotify.Write {
|
||||
continue
|
||||
}
|
||||
err := c.reloadKeyPair()
|
||||
if err != nil {
|
||||
c.logger.Error(E.Cause(err, "reload TLS key pair"))
|
||||
}
|
||||
case err, ok := <-c.watcher.Errors:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
c.logger.Error(E.Cause(err, "fsnotify error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *STDServerConfig) reloadKeyPair() error {
|
||||
if c.certificatePath != "" {
|
||||
func (c *STDServerConfig) certificateUpdated(path string) error {
|
||||
if path == c.certificatePath {
|
||||
certificate, err := os.ReadFile(c.certificatePath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload certificate from ", c.certificatePath)
|
||||
}
|
||||
c.certificate = certificate
|
||||
}
|
||||
if c.keyPath != "" {
|
||||
} else if path == c.keyPath {
|
||||
key, err := os.ReadFile(c.keyPath)
|
||||
if err != nil {
|
||||
return E.Cause(err, "reload key from ", c.keyPath)
|
||||
|
||||
@@ -13,14 +13,14 @@ var resourcePaths []string
|
||||
|
||||
func FindPath(name string) (string, bool) {
|
||||
name = os.ExpandEnv(name)
|
||||
if rw.FileExists(name) {
|
||||
if rw.IsFile(name) {
|
||||
return name, true
|
||||
}
|
||||
for _, dir := range resourcePaths {
|
||||
if path := filepath.Join(dir, dirName, name); rw.FileExists(path) {
|
||||
if path := filepath.Join(dir, dirName, name); rw.IsFile(path) {
|
||||
return path, true
|
||||
}
|
||||
if path := filepath.Join(dir, name); rw.FileExists(path) {
|
||||
if path := filepath.Join(dir, name); rw.IsFile(path) {
|
||||
return path, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package constant
|
||||
|
||||
const (
|
||||
ProtocolTLS = "tls"
|
||||
ProtocolHTTP = "http"
|
||||
ProtocolQUIC = "quic"
|
||||
ProtocolDNS = "dns"
|
||||
ProtocolSTUN = "stun"
|
||||
ProtocolTLS = "tls"
|
||||
ProtocolHTTP = "http"
|
||||
ProtocolQUIC = "quic"
|
||||
ProtocolDNS = "dns"
|
||||
ProtocolSTUN = "stun"
|
||||
ProtocolBitTorrent = "bittorrent"
|
||||
ProtocolDTLS = "dtls"
|
||||
)
|
||||
|
||||
@@ -32,6 +32,12 @@ const (
|
||||
|
||||
func ProxyDisplayName(proxyType string) string {
|
||||
switch proxyType {
|
||||
case TypeTun:
|
||||
return "TUN"
|
||||
case TypeRedirect:
|
||||
return "Redirect"
|
||||
case TypeTProxy:
|
||||
return "TProxy"
|
||||
case TypeDirect:
|
||||
return "Direct"
|
||||
case TypeBlock:
|
||||
@@ -42,6 +48,8 @@ func ProxyDisplayName(proxyType string) string {
|
||||
return "SOCKS"
|
||||
case TypeHTTP:
|
||||
return "HTTP"
|
||||
case TypeMixed:
|
||||
return "Mixed"
|
||||
case TypeShadowsocks:
|
||||
return "Shadowsocks"
|
||||
case TypeVMess:
|
||||
|
||||
5
constant/quic.go
Normal file
5
constant/quic.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build with_quic
|
||||
|
||||
package constant
|
||||
|
||||
const WithQUIC = true
|
||||
5
constant/quic_stub.go
Normal file
5
constant/quic_stub.go
Normal file
@@ -0,0 +1,5 @@
|
||||
//go:build !with_quic
|
||||
|
||||
package constant
|
||||
|
||||
const WithQUIC = false
|
||||
@@ -11,6 +11,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
RuleSetTypeInline = "inline"
|
||||
RuleSetTypeLocal = "local"
|
||||
RuleSetTypeRemote = "remote"
|
||||
RuleSetVersion1 = 1
|
||||
|
||||
@@ -3,6 +3,8 @@ package constant
|
||||
import "time"
|
||||
|
||||
const (
|
||||
TCPKeepAliveInitial = 10 * time.Minute
|
||||
TCPKeepAliveInterval = 75 * time.Second
|
||||
TCPTimeout = 5 * time.Second
|
||||
ReadPayloadTimeout = 300 * time.Millisecond
|
||||
DNSTimeout = 10 * time.Second
|
||||
|
||||
@@ -2,11 +2,296 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
#### 1.10.0-alpha.20
|
||||
|
||||
* Add DTLS sniffer
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.19
|
||||
|
||||
* Add `rule-set decompile` command
|
||||
* Add IP address support for `rule-set match` command
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.18
|
||||
|
||||
* Add new `inline` rule-set type **1**
|
||||
* Add auto reload support for local rule-set
|
||||
* Update fsnotify usages **2**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
The new [rule-set] type inline (which also becomes the default type)
|
||||
allows you to write headless rules directly without creating a rule-set file.
|
||||
|
||||
[rule-set]: /configuration/rule-set/
|
||||
|
||||
**2**:
|
||||
|
||||
sing-box now uses fsnotify correctly and will not cancel watching
|
||||
if the target file is deleted or recreated via rename (e.g. `mv`).
|
||||
|
||||
This affects all path options that support reload, including
|
||||
`tls.certificate_path`, `tls.key_path`, `tls.ech.key_path` and `rule_set.path`.
|
||||
|
||||
#### 1.10.0-alpha.17
|
||||
|
||||
* Some chaotic changes **1**
|
||||
* `rule_set_ipcidr_match_source` rule items are renamed **2**
|
||||
* Add `rule_set_ip_cidr_accept_empty` DNS address filter rule item **3**
|
||||
* Update quic-go to v0.45.1
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Something may be broken, please actively report problems with this version.
|
||||
|
||||
**2**:
|
||||
|
||||
`rule_set_ipcidr_match_source` route and DNS rule items are renamed to
|
||||
`rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||
|
||||
**3**:
|
||||
|
||||
See [DNS Rule](/configuration/dns/rule/#rule_set_ip_cidr_accept_empty).
|
||||
|
||||
#### 1.10.0-alpha.16
|
||||
|
||||
* Add custom options for `auto-route` and `auto-redirect` **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [iproute2_table_index](/configuration/inbound/tun/#iproute2_table_index),
|
||||
[iproute2_rule_index](/configuration/inbound/tun/#iproute2_rule_index),
|
||||
[auto_redirect_input_mark](/configuration/inbound/tun/#auto_redirect_input_mark) and
|
||||
[auto_redirect_output_mark](/configuration/inbound/tun/#auto_redirect_output_mark).
|
||||
|
||||
#### 1.10.0-alpha.13
|
||||
|
||||
* TUN address fields are merged **1**
|
||||
* Add route address set support for auto-redirect **2**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#tun-address-fields-are-merged).
|
||||
|
||||
**2**:
|
||||
|
||||
The new feature will allow you to configure the destination IP CIDR rules
|
||||
in the specified rule-sets to the firewall automatically.
|
||||
|
||||
Specified or unspecified destinations will bypass the sing-box routes to get better performance
|
||||
(for example, keep hardware offloading of direct traffics on the router).
|
||||
|
||||
See [route_address_set](/configuration/inbound/tun/#route_address_set)
|
||||
and [route_exclude_address_set](/configuration/inbound/tun/#route_exclude_address_set).
|
||||
|
||||
#### 1.10.0-alpha.12
|
||||
|
||||
* Fix auto-redirect not configuring nftables forward chain correctly
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.9.3
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.10
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
### 1.9.2
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.8
|
||||
|
||||
* Drop support for go1.18 and go1.19 **1**
|
||||
* Update quic-go to v0.45.0
|
||||
* Update Hysteria2 BBR congestion control
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||
|
||||
### 1.9.1
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.10.0-alpha.5
|
||||
|
||||
* Improve auto-redirect **1**
|
||||
|
||||
**1**:
|
||||
|
||||
nftables support and DNS hijacking has been added.
|
||||
|
||||
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers **without intervention**.
|
||||
|
||||
#### 1.10.0-alpha.4
|
||||
|
||||
* Fix auto-redirect **1**
|
||||
* Improve auto-route on linux **2**
|
||||
|
||||
**1**:
|
||||
|
||||
Tun inbounds with `auto_route` and `auto_redirect` now works as expected on routers.
|
||||
|
||||
**2**:
|
||||
|
||||
Tun inbounds with `auto_route` and `strict_route` now works as expected on routers and servers,
|
||||
but the usages of [exclude_interface](/configuration/inbound/tun/#exclude_interface) need to be updated.
|
||||
|
||||
#### 1.10.0-alpha.2
|
||||
|
||||
* Move auto-redirect to Tun **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Linux support are added.
|
||||
|
||||
See [Tun](/configuration/inbound/tun/#auto_redirect).
|
||||
|
||||
#### 1.10.0-alpha.1
|
||||
|
||||
* Add tailing comma support in JSON configuration
|
||||
* Add simple auto-redirect for Android **1**
|
||||
* Add BitTorrent sniffer **2**
|
||||
|
||||
**1**:
|
||||
|
||||
It allows you to use redirect inbound in the sing-box Android client
|
||||
and automatically configures IPv4 TCP redirection via su.
|
||||
|
||||
This may alleviate the symptoms of some OCD patients who think that
|
||||
redirect can effectively save power compared to the system HTTP Proxy.
|
||||
|
||||
See [Redirect](/configuration/inbound/redirect/).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Protocol Sniff](/configuration/route/sniff/).
|
||||
|
||||
### 1.9.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
Important changes since 1.8:
|
||||
|
||||
* `domain_suffix` behavior update **1**
|
||||
* `process_path` format update on Windows **2**
|
||||
* Add address filter DNS rule items **3**
|
||||
* Add support for `client-subnet` DNS options **4**
|
||||
* Add rejected DNS response cache support **5**
|
||||
* Add `bypass_domain` and `search_domain` platform HTTP proxy options **6**
|
||||
* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **7**
|
||||
* Handle Windows power events
|
||||
* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled
|
||||
* Improve DNS truncate behavior
|
||||
* Update Hysteria protocol
|
||||
* Update quic-go to v0.43.1
|
||||
* Update gVisor to 20240422.0
|
||||
* Mitigating TunnelVision attacks **8**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#domain_suffix-behavior-update).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Migration](/migration/#process_path-format-update-on-windows).
|
||||
|
||||
**3**:
|
||||
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||
if using this method.
|
||||
|
||||
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||
|
||||
**4**:
|
||||
|
||||
See [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).
|
||||
|
||||
Since this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,
|
||||
the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.
|
||||
|
||||
**5**:
|
||||
|
||||
The new feature allows you to cache the check results of
|
||||
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||
|
||||
**6**:
|
||||
|
||||
See [TUN](/configuration/inbound/tun) inbound.
|
||||
|
||||
**7**:
|
||||
|
||||
See [DNS Rule](/configuration/dns/rule/).
|
||||
|
||||
**8**:
|
||||
|
||||
See [TunnelVision](/manual/misc/tunnelvision).
|
||||
|
||||
#### 1.9.0-rc.22
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.20
|
||||
|
||||
* Prioritize `*_route_address` in linux auto-route
|
||||
* Fix `*_route_address` in darwin auto-route
|
||||
|
||||
#### 1.8.14
|
||||
|
||||
* Fix hysteria2 panic
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.18
|
||||
|
||||
* Add custom prefix support in EDNS0 client subnet options
|
||||
* Fix hysteria2 crash
|
||||
* Fix `store_rdrc` corrupted
|
||||
* Update quic-go to v0.43.1
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.16
|
||||
|
||||
* Mitigating TunnelVision attacks **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [TunnelVision](/manual/misc/tunnelvision).
|
||||
|
||||
#### 1.9.0-rc.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.13
|
||||
|
||||
* Fix fake-ip mapping
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.14
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-rc.13
|
||||
|
||||
* Update Hysteria protocol
|
||||
* Update quic-go to v0.43.0
|
||||
* Update gVisor to 20240422.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.12
|
||||
|
||||
* Now we have official APT and DNF repositories **1**
|
||||
@@ -17,6 +302,10 @@ icon: material/alert-decagram
|
||||
|
||||
Including stable and beta versions, see https://sing-box.sagernet.org/installation/package-manager/
|
||||
|
||||
#### 1.9.0-rc.11
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.11
|
||||
|
||||
* Fixes and improvements
|
||||
@@ -25,6 +314,24 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.17
|
||||
|
||||
* Update `quic-go` to v0.42.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.16
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
_Our Testflight distribution has been temporarily blocked by Apple (possibly due to too many beta versions)
|
||||
and you cannot join the test, install or update the sing-box beta app right now.
|
||||
Please wait patiently for processing._
|
||||
|
||||
#### 1.9.0-beta.14
|
||||
|
||||
* Update gVisor to 20240212.0-65-g71212d503
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.9
|
||||
|
||||
* Fixes and improvements
|
||||
@@ -33,14 +340,125 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-beta.6
|
||||
|
||||
* Fix address filter DNS rule items **1**
|
||||
* Fix DNS outbound responding with wrong data
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
Fixed an issue where address filter DNS rule was incorrectly rejected under certain circumstances.
|
||||
If you have enabled `store_rdrc` to save results, consider clearing the cache file.
|
||||
|
||||
#### 1.8.7
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.15
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.14
|
||||
|
||||
* Improve DNS truncate behavior
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.13
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.12
|
||||
|
||||
* Handle Windows power events
|
||||
* Always disable cache for fake-ip DNS transport if `dns.independent_cache` disabled
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.11
|
||||
|
||||
* Fix missing `rule_set_ipcidr_match_source` item in DNS rules **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [DNS Rule](/configuration/dns/rule/).
|
||||
|
||||
#### 1.9.0-alpha.10
|
||||
|
||||
* Add `bypass_domain` and `search_domain` platform HTTP proxy options **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [TUN](/configuration/inbound/tun) inbound.
|
||||
|
||||
#### 1.9.0-alpha.8
|
||||
|
||||
* Add rejected DNS response cache support **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
The new feature allows you to cache the check results of
|
||||
[Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields) until expiration.
|
||||
|
||||
#### 1.9.0-alpha.7
|
||||
|
||||
* Update gVisor to 20240206.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.6
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.3
|
||||
|
||||
* Update `quic-go` to v0.41.0
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.9.0-alpha.2
|
||||
|
||||
* Add support for `client-subnet` DNS options **1**
|
||||
* Fixes and improvements
|
||||
|
||||
**1**:
|
||||
|
||||
See [DNS](/configuration/dns), [DNS Server](/configuration/dns/server) and [DNS Rules](/configuration/dns/rule).
|
||||
|
||||
Since this feature makes the scenario mentioned in `alpha.1` no longer leak DNS requests,
|
||||
the [Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) has been updated.
|
||||
|
||||
#### 1.9.0-alpha.1
|
||||
|
||||
* `domain_suffix` behavior update **1**
|
||||
* `process_path` format update on Windows **2**
|
||||
* Add address filter DNS rule items **3**
|
||||
|
||||
**1**:
|
||||
|
||||
See [Migration](/migration/#domain_suffix-behavior-update).
|
||||
|
||||
**2**:
|
||||
|
||||
See [Migration](/migration/#process_path-format-update-on-windows).
|
||||
|
||||
**3**:
|
||||
|
||||
The new DNS feature allows you to more precisely bypass Chinese websites via **DNS leaks**. Do not use plain local DNS
|
||||
if using this method.
|
||||
|
||||
See [Address Filter Fields](/configuration/dns/rule#address-filter-fields).
|
||||
|
||||
[Client example](/manual/proxy/client#traffic-bypass-usage-for-chinese-users) updated.
|
||||
|
||||
#### 1.8.5
|
||||
|
||||
* Fixes and improvements
|
||||
@@ -57,14 +475,14 @@ Including stable and beta versions, see https://sing-box.sagernet.org/installati
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.8.0
|
||||
### 1.8.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
Important changes since 1.7:
|
||||
|
||||
* Migrate cache file from Clash API to independent options **1**
|
||||
* Introducing [Rule Set](/configuration/rule-set/) **2**
|
||||
* Introducing [rule-set](/configuration/rule-set/) **2**
|
||||
* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**
|
||||
* Allow nested logical rules **4**
|
||||
* Independent `source_ip_is_private` and `ip_is_private` rules **5**
|
||||
@@ -84,7 +502,7 @@ See [Cache File](/configuration/experimental/cache-file/) and
|
||||
|
||||
**2**:
|
||||
|
||||
Rule set is independent collections of rules that can be compiled into binaries to improve performance.
|
||||
rule-set is independent collections of rules that can be compiled into binaries to improve performance.
|
||||
Compared to legacy GeoIP and Geosite resources,
|
||||
it can include more types of rules, load faster,
|
||||
use less memory, and update automatically.
|
||||
@@ -92,16 +510,16 @@ use less memory, and update automatically.
|
||||
See [Route#rule_set](/configuration/route/#rule_set),
|
||||
[Route Rule](/configuration/route/rule/),
|
||||
[DNS Rule](/configuration/dns/rule/),
|
||||
[Rule Set](/configuration/rule-set/),
|
||||
[rule-set](/configuration/rule-set/),
|
||||
[Source Format](/configuration/rule-set/source-format/) and
|
||||
[Headless Rule](/configuration/rule-set/headless-rule/).
|
||||
|
||||
For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and
|
||||
[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets).
|
||||
For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and
|
||||
[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets).
|
||||
|
||||
**3**:
|
||||
|
||||
New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets.
|
||||
New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets.
|
||||
|
||||
**4**:
|
||||
|
||||
@@ -298,7 +716,7 @@ This change is intended to break incorrect usage and essentially requires no act
|
||||
|
||||
**1**:
|
||||
|
||||
Now the rules in the `rule_set` rule item can be logically considered to be merged into the rule using rule sets,
|
||||
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
|
||||
@@ -314,7 +732,7 @@ Since GeoIP was deprecated, we made this rule independent, see [Migration](/migr
|
||||
#### 1.8.0-alpha.1
|
||||
|
||||
* Migrate cache file from Clash API to independent options **1**
|
||||
* Introducing [Rule Set](/configuration/rule-set/) **2**
|
||||
* Introducing [rule-set](/configuration/rule-set/) **2**
|
||||
* Add `sing-box geoip`, `sing-box geosite` and `sing-box rule-set` commands **3**
|
||||
* Allow nested logical rules **4**
|
||||
|
||||
@@ -325,7 +743,7 @@ See [Cache File](/configuration/experimental/cache-file/) and
|
||||
|
||||
**2**:
|
||||
|
||||
Rule set is independent collections of rules that can be compiled into binaries to improve performance.
|
||||
rule-set is independent collections of rules that can be compiled into binaries to improve performance.
|
||||
Compared to legacy GeoIP and Geosite resources,
|
||||
it can include more types of rules, load faster,
|
||||
use less memory, and update automatically.
|
||||
@@ -333,22 +751,22 @@ use less memory, and update automatically.
|
||||
See [Route#rule_set](/configuration/route/#rule_set),
|
||||
[Route Rule](/configuration/route/rule/),
|
||||
[DNS Rule](/configuration/dns/rule/),
|
||||
[Rule Set](/configuration/rule-set/),
|
||||
[rule-set](/configuration/rule-set/),
|
||||
[Source Format](/configuration/rule-set/source-format/) and
|
||||
[Headless Rule](/configuration/rule-set/headless-rule/).
|
||||
|
||||
For GEO resources migration, see [Migrate GeoIP to rule sets](/migration/#migrate-geoip-to-rule-sets) and
|
||||
[Migrate Geosite to rule sets](/migration/#migrate-geosite-to-rule-sets).
|
||||
For GEO resources migration, see [Migrate GeoIP to rule-sets](/migration/#migrate-geoip-to-rule-sets) and
|
||||
[Migrate Geosite to rule-sets](/migration/#migrate-geosite-to-rule-sets).
|
||||
|
||||
**3**:
|
||||
|
||||
New commands manage GeoIP, Geosite and rule set resources, and help you migrate GEO resources to rule sets.
|
||||
New commands manage GeoIP, Geosite and rule-set resources, and help you migrate GEO resources to rule-sets.
|
||||
|
||||
**4**:
|
||||
|
||||
Logical rules in route rules, DNS rules, and the new headless rule now allow nesting of logical rules.
|
||||
|
||||
#### 1.7.0
|
||||
### 1.7.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -390,7 +808,7 @@ see [TCP Brutal](/configuration/shared/tcp-brutal/) for details.
|
||||
|
||||
**5**:
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
#### 1.7.0-rc.3
|
||||
|
||||
@@ -427,7 +845,7 @@ Only supported in graphical clients on Android and iOS.
|
||||
|
||||
**1**:
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
#### 1.7.0-beta.3
|
||||
|
||||
@@ -508,7 +926,7 @@ Introduced in V2Ray 5.10.0.
|
||||
|
||||
The new HTTPUpgrade transport has better performance than WebSocket and is better suited for CDN abuse.
|
||||
|
||||
#### 1.6.0
|
||||
### 1.6.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -687,7 +1105,7 @@ introduce new issues.
|
||||
None of the existing Golang BBR congestion control implementations have been reviewed or unit tested.
|
||||
This update is intended to address the multi-send defects of the old implementation and may introduce new issues.
|
||||
|
||||
#### 1.5.0
|
||||
### 1.5.0
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
@@ -881,7 +1299,7 @@ All inbounds and outbounds are supported, including `Naiveproxy`, `Hysteria`, `T
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.4.0
|
||||
### 1.4.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
@@ -1023,7 +1441,7 @@ The old testflight link and app are no longer valid.
|
||||
|
||||
* Fixes and improvements
|
||||
|
||||
#### 1.3.0
|
||||
### 1.3.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
@@ -1215,7 +1633,7 @@ to `domain` rule.
|
||||
* Flush DNS cache for macOS when tun start/close
|
||||
* Fix tun's DNS hijacking compatibility with systemd-resolved
|
||||
|
||||
#### 1.2.0
|
||||
### 1.2.0
|
||||
|
||||
* Fix bugs and update dependencies
|
||||
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
|
||||
# DNS
|
||||
|
||||
### Structure
|
||||
@@ -13,6 +21,7 @@
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
}
|
||||
}
|
||||
@@ -21,8 +30,8 @@
|
||||
|
||||
### Fields
|
||||
|
||||
| Key | Format |
|
||||
|----------|--------------------------------|
|
||||
| Key | Format |
|
||||
|----------|---------------------------------|
|
||||
| `server` | List of [DNS Server](./server/) |
|
||||
| `rules` | List of [DNS Rule](./rule/) |
|
||||
| `fakeip` | [FakeIP](./fakeip/) |
|
||||
@@ -60,6 +69,12 @@ Stores a reverse mapping of IP addresses after responding to a DNS query in orde
|
||||
Since this process relies on the act of resolving domain names by an application before making a request, it can be
|
||||
problematic in environments such as macOS, where DNS is proxied and cached by the system.
|
||||
|
||||
#### fakeip
|
||||
#### client_subnet
|
||||
|
||||
[FakeIP](./fakeip/) settings.
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Can be overrides by `servers.[].client_subnet` or `rules.[].client_subnet`.
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
|
||||
# DNS
|
||||
|
||||
### 结构
|
||||
@@ -13,6 +21,7 @@
|
||||
"disable_expire": false,
|
||||
"independent_cache": false,
|
||||
"reverse_mapping": false,
|
||||
"client_subnet": "",
|
||||
"fakeip": {}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +67,16 @@
|
||||
|
||||
由于此过程依赖于应用程序在发出请求之前解析域名的行为,因此在 macOS 等 DNS 由系统代理和缓存的环境中可能会出现问题。
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
可以被 `servers.[].client_subnet` 或 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
#### fakeip
|
||||
|
||||
[FakeIP](./fakeip/) 设置。
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [geoip](#geoip)
|
||||
:material-plus: [ip_cidr](#ip_cidr)
|
||||
:material-plus: [ip_is_private](#ip_is_private)
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -53,11 +67,19 @@ icon: material/alert-decagram
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"source_ip_is_private": false,
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
@@ -101,13 +123,18 @@ icon: material/alert-decagram
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
],
|
||||
// deprecated
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
],
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 100
|
||||
"rewrite_ttl": 100,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
@@ -115,7 +142,8 @@ icon: material/alert-decagram
|
||||
"rules": [],
|
||||
"server": "local",
|
||||
"disable_cache": false,
|
||||
"rewrite_ttl": 100
|
||||
"rewrite_ttl": 100,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -138,7 +166,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
|
||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -266,11 +294,9 @@ Match Clash mode.
|
||||
|
||||
#### wifi_ssid
|
||||
|
||||
<!-- md:version 1.7.0-beta.4 -->
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match WiFi SSID.
|
||||
|
||||
@@ -278,7 +304,7 @@ Match WiFi SSID.
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match WiFi BSSID.
|
||||
|
||||
@@ -286,7 +312,23 @@ Match WiFi BSSID.
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
Match [Rule Set](/configuration/route/#rule_set).
|
||||
Match [rule-set](/configuration/route/#rule_set).
|
||||
|
||||
#### rule_set_ipcidr_match_source
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||
|
||||
Make `ip_cidr` rule items in rule-sets match the source IP.
|
||||
|
||||
#### rule_set_ip_cidr_match_source
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Make `ip_cidr` rule items in rule-sets match the source IP.
|
||||
|
||||
#### invert
|
||||
|
||||
@@ -312,6 +354,52 @@ Disable cache and save cache in this query.
|
||||
|
||||
Rewrite TTL in DNS responses.
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Will overrides `dns.client_subnet` and `servers.[].client_subnet`.
|
||||
|
||||
### Address Filter Fields
|
||||
|
||||
Only takes effect for address requests (A/AAAA/HTTPS). When the query results do not match the address filtering rule items, the current rule will be skipped.
|
||||
|
||||
!!! info ""
|
||||
|
||||
`ip_cidr` items in included rule-sets also takes effect as an address filtering field.
|
||||
|
||||
!!! note ""
|
||||
|
||||
Enable `experimental.cache_file.store_rdrc` to cache results.
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match GeoIP with query response.
|
||||
|
||||
#### ip_cidr
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match IP CIDR with query response.
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Match private IP with query response.
|
||||
|
||||
#### rule_set_ip_cidr_accept_empty
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Make `ip_cidr` rules in rule-sets accept empty query response.
|
||||
|
||||
### Logical Fields
|
||||
|
||||
#### type
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_accept_empty](#rule_set_ip_cidr_accept_empty)
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [geoip](#geoip)
|
||||
:material-plus: [ip_cidr](#ip_cidr)
|
||||
:material-plus: [ip_is_private](#ip_is_private)
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
:material-plus: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -53,10 +67,19 @@ icon: material/alert-decagram
|
||||
"source_geoip": [
|
||||
"private"
|
||||
],
|
||||
"geoip": [
|
||||
"cn"
|
||||
],
|
||||
"source_ip_cidr": [
|
||||
"10.0.0.0/24"
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"source_ip_is_private": false,
|
||||
"ip_cidr": [
|
||||
"10.0.0.0/24",
|
||||
"192.168.0.1"
|
||||
],
|
||||
"ip_is_private": false,
|
||||
"source_port": [
|
||||
12345
|
||||
],
|
||||
@@ -100,19 +123,25 @@ icon: material/alert-decagram
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
],
|
||||
// 已弃用
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"rule_set_ip_cidr_accept_empty": false,
|
||||
"invert": false,
|
||||
"outbound": [
|
||||
"direct"
|
||||
],
|
||||
"server": "local",
|
||||
"disable_cache": false
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [],
|
||||
"server": "local",
|
||||
"disable_cache": false
|
||||
"disable_cache": false,
|
||||
"client_subnet": "127.0.0.1/24"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -265,7 +294,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 的图形客户端中支持。
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配 WiFi SSID。
|
||||
|
||||
@@ -273,7 +302,7 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 的图形客户端中支持。
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配 WiFi BSSID。
|
||||
|
||||
@@ -283,6 +312,22 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
匹配[规则集](/zh/configuration/route/#rule_set)。
|
||||
|
||||
#### rule_set_ipcidr_match_source
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
#### rule_set_ip_cidr_match_source
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
#### invert
|
||||
|
||||
反选匹配结果。
|
||||
@@ -307,6 +352,52 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
重写 DNS 回应中的 TTL。
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
将覆盖 `dns.client_subnet` 与 `servers.[].client_subnet`。
|
||||
|
||||
### 地址筛选字段
|
||||
|
||||
仅对地址请求 (A/AAAA/HTTPS) 生效。 当查询结果与地址筛选规则项不匹配时,将跳过当前规则。
|
||||
|
||||
!!! info ""
|
||||
|
||||
引用的规则集中的 `ip_cidr` 项也作为地址筛选字段生效。
|
||||
|
||||
!!! note ""
|
||||
|
||||
启用 `experimental.cache_file.store_rdrc` 以缓存结果。
|
||||
|
||||
#### geoip
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配 GeoIP。
|
||||
|
||||
#### ip_cidr
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配 IP CIDR。
|
||||
|
||||
#### ip_is_private
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
与查询响应匹配非公开 IP。
|
||||
|
||||
#### rule_set_ip_cidr_accept_empty
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
使规则集中的 `ip_cidr` 规则接受空查询响应。
|
||||
|
||||
### 逻辑字段
|
||||
|
||||
#### type
|
||||
@@ -319,4 +410,4 @@ DNS 查询类型。值可以为整数或者类型名称字符串。
|
||||
|
||||
#### rules
|
||||
|
||||
包括的规则。
|
||||
包括的规则。
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -5,17 +13,17 @@
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://dns.google",
|
||||
"address_resolver": "local",
|
||||
"address_strategy": "prefer_ipv4",
|
||||
"strategy": "ipv4_only",
|
||||
"detour": "direct"
|
||||
"tag": "",
|
||||
"address": "",
|
||||
"address_resolver": "",
|
||||
"address_strategy": "",
|
||||
"strategy": "",
|
||||
"detour": "",
|
||||
"client_subnet": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Fields
|
||||
@@ -80,10 +88,22 @@ Default domain strategy for resolving the domain names.
|
||||
|
||||
One of `prefer_ipv4` `prefer_ipv6` `ipv4_only` `ipv6_only`.
|
||||
|
||||
Take no effect if override by other settings.
|
||||
Take no effect if overridden by other settings.
|
||||
|
||||
#### detour
|
||||
|
||||
Tag of an outbound for connecting to the dns server.
|
||||
|
||||
Default outbound will be used if empty.
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! question "Since sing-box 1.9.0"
|
||||
|
||||
Append a `edns0-subnet` OPT extra record with the specified IP prefix to every query by default.
|
||||
|
||||
If value is an IP address instead of prefix, `/32` or `/128` will be appended automatically.
|
||||
|
||||
Can be overrides by `rules.[].client_subnet`.
|
||||
|
||||
Will overrides `dns.client_subnet`.
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [client_subnet](#client_subnet)
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
@@ -5,17 +13,17 @@
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://dns.google",
|
||||
"address_resolver": "local",
|
||||
"address_strategy": "prefer_ipv4",
|
||||
"strategy": "ipv4_only",
|
||||
"detour": "direct"
|
||||
"tag": "",
|
||||
"address": "",
|
||||
"address_resolver": "",
|
||||
"address_strategy": "",
|
||||
"strategy": "",
|
||||
"detour": "",
|
||||
"client_subnet": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### 字段
|
||||
@@ -87,3 +95,15 @@ DNS 服务器的地址。
|
||||
用于连接到 DNS 服务器的出站的标签。
|
||||
|
||||
如果为空,将使用默认出站。
|
||||
|
||||
#### client_subnet
|
||||
|
||||
!!! question "自 sing-box 1.9.0 起"
|
||||
|
||||
默认情况下,将带有指定 IP 前缀的 `edns0-subnet` OPT 附加记录附加到每个查询。
|
||||
|
||||
如果值是 IP 地址而不是前缀,则会自动附加 `/32` 或 `/128`。
|
||||
|
||||
可以被 `rules.[].client_subnet` 覆盖。
|
||||
|
||||
将覆盖 `dns.client_subnet`。
|
||||
|
||||
@@ -4,6 +4,11 @@ icon: material/new-box
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [store_rdrc](#store_rdrc)
|
||||
:material-plus: [rdrc_timeout](#rdrc_timeout)
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
@@ -11,7 +16,9 @@ icon: material/new-box
|
||||
"enabled": true,
|
||||
"path": "",
|
||||
"cache_id": "",
|
||||
"store_fakeip": false
|
||||
"store_fakeip": false,
|
||||
"store_rdrc": false,
|
||||
"rdrc_timeout": ""
|
||||
}
|
||||
```
|
||||
|
||||
@@ -29,6 +36,23 @@ Path to the cache file.
|
||||
|
||||
#### cache_id
|
||||
|
||||
Identifier in cache file.
|
||||
Identifier in the cache file
|
||||
|
||||
If not empty, configuration specified data will use a separate store keyed by it.
|
||||
|
||||
#### store_fakeip
|
||||
|
||||
Store fakeip in the cache file
|
||||
|
||||
#### store_rdrc
|
||||
|
||||
Store rejected DNS response cache in the cache file
|
||||
|
||||
The check results of [Address filter DNS rule items](/configuration/dns/rule/#address-filter-fields)
|
||||
will be cached until expiration.
|
||||
|
||||
#### rdrc_timeout
|
||||
|
||||
Timeout of rejected DNS response cache.
|
||||
|
||||
`7d` is used by default.
|
||||
|
||||
@@ -4,6 +4,11 @@ icon: material/new-box
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [store_rdrc](#store_rdrc)
|
||||
:material-plus: [rdrc_timeout](#rdrc_timeout)
|
||||
|
||||
### 结构
|
||||
|
||||
```json
|
||||
@@ -11,7 +16,9 @@ icon: material/new-box
|
||||
"enabled": true,
|
||||
"path": "",
|
||||
"cache_id": "",
|
||||
"store_fakeip": false
|
||||
"store_fakeip": false,
|
||||
"store_rdrc": false,
|
||||
"rdrc_timeout": ""
|
||||
}
|
||||
```
|
||||
|
||||
@@ -30,3 +37,19 @@ icon: material/new-box
|
||||
缓存文件中的标识符。
|
||||
|
||||
如果不为空,配置特定的数据将使用由其键控的单独存储。
|
||||
|
||||
#### store_fakeip
|
||||
|
||||
将 fakeip 存储在缓存文件中。
|
||||
|
||||
#### store_rdrc
|
||||
|
||||
将拒绝的 DNS 响应缓存存储在缓存文件中。
|
||||
|
||||
[地址筛选 DNS 规则项](/zh/configuration/dns/rule/#_3) 的检查结果将被缓存至过期。
|
||||
|
||||
#### rdrc_timeout
|
||||
|
||||
拒绝的 DNS 响应缓存超时。
|
||||
|
||||
默认使用 `7d`。
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-delete-alert: [store_mode](#store_mode)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-delete-alert: [store_mode](#store_mode)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# Experimental
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# 实验性
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
@@ -42,6 +42,6 @@ No authentication required if empty.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
||||
To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
|
||||
@@ -39,6 +39,6 @@ No authentication required if empty.
|
||||
|
||||
!!! warning ""
|
||||
|
||||
To work on Android and iOS without privileges, use tun.platform.http_proxy instead.
|
||||
To work on Android and Apple platforms without privileges, use tun.platform.http_proxy instead.
|
||||
|
||||
Automatically set system proxy configuration when start and clean up when stop.
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
:material-delete-clock: [inet4_address](#inet4_address)
|
||||
:material-delete-clock: [inet6_address](#inet6_address)
|
||||
:material-plus: [route_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_address](#inet4_route_address)
|
||||
:material-delete-clock: [inet6_route_address](#inet6_route_address)
|
||||
:material-plus: [route_exclude_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
|
||||
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
|
||||
:material-plus: [iproute2_table_index](#iproute2_table_index)
|
||||
:material-plus: [iproute2_rule_index](#iproute2_table_index)
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
:material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark)
|
||||
:material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark)
|
||||
:material-plus: [route_address_set](#route_address_set)
|
||||
:material-plus: [route_exclude_address_set](#route_address_set)
|
||||
|
||||
!!! quote "Changes in sing-box 1.9.0"
|
||||
|
||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||
:material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
@@ -18,26 +42,61 @@ icon: material/alert-decagram
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"address": [
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
"auto_redirect": false,
|
||||
"auto_redirect_input_mark": "0x2023",
|
||||
"auto_redirect_output_mark": "0x2024",
|
||||
"strict_route": true,
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// deprecated
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// deprecated
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
"route_exclude_address_set": [
|
||||
"geoip-cn"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"udp_timeout": "5m",
|
||||
"stack": "system",
|
||||
@@ -73,11 +132,13 @@ icon: material/alert-decagram
|
||||
"http_proxy": {
|
||||
"enabled": false,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
"server_port": 8080,
|
||||
"bypass_domain": [],
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
|
||||
... // Listen Fields
|
||||
...
|
||||
// Listen Fields
|
||||
}
|
||||
```
|
||||
|
||||
@@ -95,14 +156,26 @@ icon: material/alert-decagram
|
||||
|
||||
Virtual device name, automatically selected if empty.
|
||||
|
||||
#### address
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
IPv4 and IPv6 prefix for the tun interface.
|
||||
|
||||
#### inet4_address
|
||||
|
||||
==Required==
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
|
||||
IPv4 prefix for the tun interface.
|
||||
|
||||
#### inet6_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_address` is merged to `address` and will be removed in sing-box 1.11.0.
|
||||
|
||||
IPv6 prefix for the tun interface.
|
||||
|
||||
#### mtu
|
||||
@@ -115,7 +188,7 @@ The maximum transmission unit.
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux.
|
||||
Only supported on Linux with `auto_route` enabled.
|
||||
|
||||
Enable generic segmentation offload.
|
||||
|
||||
@@ -131,6 +204,57 @@ Set the default route to the Tun.
|
||||
|
||||
By default, VPN takes precedence over tun. To make tun go through VPN, enable `route.override_android_vpn`.
|
||||
|
||||
#### iproute2_table_index
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Linux iproute2 table index generated by `auto_route`.
|
||||
|
||||
`2022` is used by default.
|
||||
|
||||
#### iproute2_rule_index
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Linux iproute2 rule start index generated by `auto_route`.
|
||||
|
||||
`9000` is used by default.
|
||||
|
||||
#### auto_redirect
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with `auto_route` enabled.
|
||||
|
||||
Automatically configure iptables/nftables to redirect connections.
|
||||
|
||||
*In Android*:
|
||||
|
||||
Only local connections are forwarded. To share your VPN connection over hotspot or repeater,
|
||||
use [VPNHotspot](https://github.com/Mygod/VPNHotspot).
|
||||
|
||||
*In Linux*:
|
||||
|
||||
`auto_route` with `auto_redirect` now works as expected on routers **without intervention**.
|
||||
|
||||
#### auto_redirect_input_mark
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Connection input mark used by `route_address_set` and `route_exclude_address_set`.
|
||||
|
||||
`0x2023` is used by default.
|
||||
|
||||
#### auto_redirect_output_mark
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Connection output mark used by `route_address_set` and `route_exclude_address_set`.
|
||||
|
||||
`0x2024` is used by default.
|
||||
|
||||
#### strict_route
|
||||
|
||||
Enforce strict routing rules when `auto_route` is enabled:
|
||||
@@ -138,9 +262,10 @@ Enforce strict routing rules when `auto_route` is enabled:
|
||||
*In Linux*:
|
||||
|
||||
* Let unsupported network unreachable
|
||||
* Make ICMP traffic route to tun instead of upstream interfaces
|
||||
* Route all connections to tun
|
||||
|
||||
It prevents address leaks and makes DNS hijacking work on Android.
|
||||
It prevents IP address leaks and makes DNS hijacking work on Android.
|
||||
|
||||
*In Windows*:
|
||||
|
||||
@@ -149,22 +274,80 @@ It prevents address leaks and makes DNS hijacking work on Android.
|
||||
|
||||
It may prevent some applications (such as VirtualBox) from working properly in certain situations.
|
||||
|
||||
#### route_address
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
|
||||
instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### inet6_route_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_address` is deprecated and will be removed in sing-box 1.11.0, please use [route_address](#route_address)
|
||||
instead.
|
||||
|
||||
Use custom routes instead of default when `auto_route` is enabled.
|
||||
|
||||
#### route_exclude_address
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet4_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
|
||||
use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`inet6_route_exclude_address` is deprecated and will be removed in sing-box 1.11.0, please
|
||||
use [route_exclude_address](#route_exclude_address) instead.
|
||||
|
||||
Exclude custom routes when `auto_route` is enabled.
|
||||
|
||||
#### route_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Unmatched traffic will bypass the sing-box routes.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
#### route_exclude_address_set
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported on Linux with nftables and requires `auto_route` and `auto_redirect` enabled.
|
||||
|
||||
Add the destination IP CIDR rules in the specified rule-sets to the firewall.
|
||||
Matched traffic will bypass the sing-box routes.
|
||||
|
||||
Conflict with `route.default_mark` and `[dialOptions].routing_mark`.
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
!!! info ""
|
||||
@@ -207,6 +390,10 @@ Conflict with `exclude_interface`.
|
||||
|
||||
#### exclude_interface
|
||||
|
||||
!!! warning ""
|
||||
|
||||
When `strict_route` enabled, return traffic to excluded interfaces will not be automatically excluded, so add them as well (example: `br-lan` and `pppoe-wan`).
|
||||
|
||||
Exclude interfaces in route.
|
||||
|
||||
Conflict with `include_interface`.
|
||||
@@ -260,6 +447,38 @@ Platform-specific settings, provided by client applications.
|
||||
|
||||
System HTTP proxy settings.
|
||||
|
||||
#### platform.http_proxy.enabled
|
||||
|
||||
Enable system HTTP proxy.
|
||||
|
||||
#### platform.http_proxy.server
|
||||
|
||||
==Required==
|
||||
|
||||
HTTP proxy server address.
|
||||
|
||||
#### platform.http_proxy.server_port
|
||||
|
||||
==Required==
|
||||
|
||||
HTTP proxy server port.
|
||||
|
||||
#### platform.http_proxy.bypass_domain
|
||||
|
||||
!!! note ""
|
||||
|
||||
On Apple platforms, `bypass_domain` items matches hostname **suffixes**.
|
||||
|
||||
Hostnames that bypass the HTTP proxy.
|
||||
|
||||
#### platform.http_proxy.match_domain
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Apple platforms.
|
||||
|
||||
Hostnames that use the HTTP proxy.
|
||||
|
||||
### Listen Fields
|
||||
|
||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: [address](#address)
|
||||
:material-delete-clock: [inet4_address](#inet4_address)
|
||||
:material-delete-clock: [inet6_address](#inet6_address)
|
||||
:material-plus: [route_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_address](#inet4_route_address)
|
||||
:material-delete-clock: [inet6_route_address](#inet6_route_address)
|
||||
:material-plus: [route_exclude_address](#route_address)
|
||||
:material-delete-clock: [inet4_route_exclude_address](#inet4_route_exclude_address)
|
||||
:material-delete-clock: [inet6_route_exclude_address](#inet6_route_exclude_address)
|
||||
:material-plus: [iproute2_table_index](#iproute2_table_index)
|
||||
:material-plus: [iproute2_rule_index](#iproute2_table_index)
|
||||
:material-plus: [auto_redirect](#auto_redirect)
|
||||
:material-plus: [auto_redirect_input_mark](#auto_redirect_input_mark)
|
||||
:material-plus: [auto_redirect_output_mark](#auto_redirect_output_mark)
|
||||
:material-plus: [route_address_set](#route_address_set)
|
||||
:material-plus: [route_exclude_address_set](#route_address_set)
|
||||
|
||||
!!! quote "sing-box 1.9.0 中的更改"
|
||||
|
||||
:material-plus: [platform.http_proxy.bypass_domain](#platformhttp_proxybypass_domain)
|
||||
:material-plus: [platform.http_proxy.match_domain](#platformhttp_proxymatch_domain)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
@@ -18,26 +42,61 @@ icon: material/alert-decagram
|
||||
"type": "tun",
|
||||
"tag": "tun-in",
|
||||
"interface_name": "tun0",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"address": [
|
||||
"172.18.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_address": [
|
||||
"172.19.0.1/30"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_address": [
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"mtu": 9000,
|
||||
"gso": false,
|
||||
"auto_route": true,
|
||||
"iproute2_table_index": 2022,
|
||||
"iproute2_rule_index": 9000,
|
||||
"auto_redirect": false,
|
||||
"auto_redirect_input_mark": "0x2023",
|
||||
"auto_redirect_output_mark": "0x2024",
|
||||
"strict_route": true,
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
],
|
||||
// 已弃用
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
// 已弃用
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
],
|
||||
"route_address_set": [
|
||||
"geoip-cloudflare"
|
||||
],
|
||||
"route_exclude_address_set": [
|
||||
"geoip-cn"
|
||||
],
|
||||
"endpoint_independent_nat": false,
|
||||
"udp_timeout": "5m",
|
||||
"stack": "system",
|
||||
@@ -73,7 +132,9 @@ icon: material/alert-decagram
|
||||
"http_proxy": {
|
||||
"enabled": false,
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080
|
||||
"server_port": 8080,
|
||||
"bypass_domain": [],
|
||||
"match_domain": []
|
||||
}
|
||||
},
|
||||
|
||||
@@ -95,14 +156,30 @@ icon: material/alert-decagram
|
||||
|
||||
虚拟设备名称,默认自动选择。
|
||||
|
||||
#### address
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
==必填==
|
||||
|
||||
tun 接口的 IPv4 和 IPv6 前缀。
|
||||
|
||||
#### inet4_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
==必填==
|
||||
|
||||
tun 接口的 IPv4 前缀。
|
||||
|
||||
#### inet6_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_address` 已合并到 `address` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
tun 接口的 IPv6 前缀。
|
||||
|
||||
#### mtu
|
||||
@@ -131,6 +208,56 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
VPN 默认优先于 tun。要使 tun 经过 VPN,启用 `route.override_android_vpn`。
|
||||
|
||||
#### iproute2_table_index
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
`auto_route` 生成的 iproute2 路由表索引。
|
||||
|
||||
默认使用 `2022`。
|
||||
|
||||
#### iproute2_rule_index
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
`auto_route` 生成的 iproute2 规则起始索引。
|
||||
|
||||
默认使用 `9000`。
|
||||
|
||||
#### auto_redirect
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 `auto_route` 已启用。
|
||||
|
||||
自动配置 iptables 以重定向 TCP 连接。
|
||||
|
||||
*在 Android 中*:
|
||||
|
||||
仅转发本地 IPv4 连接。 要通过热点或中继共享您的 VPN 连接,请使用 [VPNHotspot](https://github.com/Mygod/VPNHotspot)。
|
||||
|
||||
*在 Linux 中*:
|
||||
|
||||
带有 `auto_redirect `的 `auto_route` 现在可以在路由器上按预期工作,**无需干预**。
|
||||
|
||||
#### auto_redirect_input_mark
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
`route_address_set` 和 `route_exclude_address_set` 使用的连接输入标记。
|
||||
|
||||
默认使用 `0x2023`。
|
||||
|
||||
#### auto_redirect_output_mark
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
`route_address_set` 和 `route_exclude_address_set` 使用的连接输出标记。
|
||||
|
||||
默认使用 `0x2024`。
|
||||
|
||||
#### strict_route
|
||||
|
||||
启用 `auto_route` 时执行严格的路由规则。
|
||||
@@ -138,9 +265,10 @@ tun 接口的 IPv6 前缀。
|
||||
*在 Linux 中*:
|
||||
|
||||
* 让不支持的网络无法到达
|
||||
* 使 ICMP 流量路由到 tun 而不是上游接口
|
||||
* 将所有连接路由到 tun
|
||||
|
||||
它可以防止地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||
它可以防止 IP 地址泄漏,并使 DNS 劫持在 Android 上工作。
|
||||
|
||||
*在 Windows 中*:
|
||||
|
||||
@@ -150,22 +278,76 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
它可能会使某些应用程序(如 VirtualBox)在某些情况下无法正常工作。
|
||||
|
||||
#### route_address
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
设置到 Tun 的自定义路由。
|
||||
|
||||
#### inet4_route_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
#### inet6_route_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_address` 已合并到 `route_address` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
启用 `auto_route` 时使用自定义路由而不是默认路由。
|
||||
|
||||
#### route_exclude_address
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
设置到 Tun 的排除自定义路由。
|
||||
|
||||
#### inet4_route_exclude_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet4_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### inet6_route_exclude_address
|
||||
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`inet6_route_exclude_address` 已合并到 `route_exclude_address` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
启用 `auto_route` 时排除自定义路由。
|
||||
|
||||
#### route_address_set
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
不匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
#### route_exclude_address_set
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅支持 Linux,且需要 nftables,`auto_route` 和 `auto_redirect` 已启用。
|
||||
|
||||
将指定规则集中的目标 IP CIDR 规则添加到防火墙。
|
||||
匹配的流量将绕过 sing-box 路由。
|
||||
|
||||
与 `route.default_mark` 和 `[dialOptions].routing_mark` 冲突。
|
||||
|
||||
#### endpoint_independent_nat
|
||||
|
||||
启用独立于端点的 NAT。
|
||||
@@ -204,6 +386,10 @@ TCP/IP 栈。
|
||||
|
||||
#### exclude_interface
|
||||
|
||||
!!! warning ""
|
||||
|
||||
当 `strict_route` 启用,到被排除接口的回程流量将不会被自动排除,因此也要添加它们(例:`br-lan` 与 `pppoe-wan`)。
|
||||
|
||||
排除路由的接口。
|
||||
|
||||
与 `include_interface` 冲突。
|
||||
@@ -257,6 +443,38 @@ TCP/IP 栈。
|
||||
|
||||
系统 HTTP 代理设置。
|
||||
|
||||
##### platform.http_proxy.enabled
|
||||
|
||||
启用系统 HTTP 代理。
|
||||
|
||||
##### platform.http_proxy.server
|
||||
|
||||
==必填==
|
||||
|
||||
系统 HTTP 代理服务器地址。
|
||||
|
||||
##### platform.http_proxy.server_port
|
||||
|
||||
==必填==
|
||||
|
||||
系统 HTTP 代理服务器端口。
|
||||
|
||||
##### platform.http_proxy.bypass_domain
|
||||
|
||||
!!! note ""
|
||||
|
||||
在 Apple 平台,`bypass_domain` 项匹配主机名 **后缀**.
|
||||
|
||||
绕过代理的主机名列表。
|
||||
|
||||
##### platform.http_proxy.match_domain
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Apple 平台图形客户端中支持。
|
||||
|
||||
代理的主机名列表。
|
||||
|
||||
### 监听字段
|
||||
|
||||
参阅 [监听字段](/zh/configuration/shared/listen/)。
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
}
|
||||
```
|
||||
|
||||
### 字段
|
||||
### Fields
|
||||
|
||||
No fields.
|
||||
No fields.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [gso](#gso)
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# Route
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
@@ -43,7 +39,7 @@ List of [Route Rule](./rule/)
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
List of [Rule Set](/configuration/rule-set/)
|
||||
List of [rule-set](/configuration/rule-set/)
|
||||
|
||||
#### final
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
# 路由
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -109,6 +114,9 @@ icon: material/alert-decagram
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
],
|
||||
// deprecated
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"invert": false,
|
||||
"outbound": "direct"
|
||||
},
|
||||
@@ -140,7 +148,7 @@ icon: material/alert-decagram
|
||||
(`source_port` || `source_port_range`) &&
|
||||
`other fields`
|
||||
|
||||
Additionally, included rule sets can be considered merged rather than as a single rule sub-item.
|
||||
Additionally, included rule-sets can be considered merged rather than as a single rule sub-item.
|
||||
|
||||
#### inbound
|
||||
|
||||
@@ -284,7 +292,7 @@ Match Clash mode.
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match WiFi SSID.
|
||||
|
||||
@@ -292,7 +300,7 @@ Match WiFi SSID.
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match WiFi BSSID.
|
||||
|
||||
@@ -300,13 +308,23 @@ Match WiFi BSSID.
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
Match [Rule Set](/configuration/route/#rule_set).
|
||||
Match [rule-set](/configuration/route/#rule_set).
|
||||
|
||||
#### rule_set_ipcidr_match_source
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
Make `ipcidr` in rule sets match the source IP.
|
||||
!!! failure "Deprecated in sing-box 1.10.0"
|
||||
|
||||
`rule_set_ipcidr_match_source` is renamed to `rule_set_ip_cidr_match_source` and will be remove in sing-box 1.11.0.
|
||||
|
||||
Make `ip_cidr` in rule-sets match the source IP.
|
||||
|
||||
#### rule_set_ip_cidr_match_source
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
Make `ip_cidr` in rule-sets match the source IP.
|
||||
|
||||
#### invert
|
||||
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-delete-clock: [rule_set_ipcidr_match_source](#rule_set_ipcidr_match_source)
|
||||
:material-plus: [rule_set_ip_cidr_match_source](#rule_set_ip_cidr_match_source)
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-plus: [rule_set](#rule_set)
|
||||
@@ -107,6 +112,9 @@ icon: material/alert-decagram
|
||||
"geoip-cn",
|
||||
"geosite-cn"
|
||||
],
|
||||
// 已弃用
|
||||
"rule_set_ipcidr_match_source": false,
|
||||
"rule_set_ip_cidr_match_source": false,
|
||||
"invert": false,
|
||||
"outbound": "direct"
|
||||
},
|
||||
@@ -282,7 +290,7 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 的图形客户端中支持。
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配 WiFi SSID。
|
||||
|
||||
@@ -290,7 +298,7 @@ icon: material/alert-decagram
|
||||
|
||||
!!! quote ""
|
||||
|
||||
仅在 Android 与 iOS 的图形客户端中支持。
|
||||
仅在 Android 与 Apple 平台图形客户端中支持。
|
||||
|
||||
匹配 WiFi BSSID。
|
||||
|
||||
@@ -304,7 +312,17 @@ icon: material/alert-decagram
|
||||
|
||||
!!! question "自 sing-box 1.8.0 起"
|
||||
|
||||
使规则集中的 `ipcidr` 规则匹配源 IP。
|
||||
!!! failure "已在 sing-box 1.10.0 废弃"
|
||||
|
||||
`rule_set_ipcidr_match_source` 已重命名为 `rule_set_ip_cidr_match_source` 且将在 sing-box 1.11.0 移除。
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
#### rule_set_ip_cidr_match_source
|
||||
|
||||
!!! question "自 sing-box 1.10.0 起"
|
||||
|
||||
使规则集中的 `ip_cidr` 规则匹配源 IP。
|
||||
|
||||
#### invert
|
||||
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: BitTorrent support
|
||||
:material-plus: DTLS support
|
||||
|
||||
If enabled in the inbound, the protocol and domain name (if present) of by the connection can be sniffed.
|
||||
|
||||
#### Supported Protocols
|
||||
|
||||
| Network | Protocol | Domain Name |
|
||||
|:-------:|:--------:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| Network | Protocol | Domain Name |
|
||||
|:-------:|:------------:|:-----------:|
|
||||
| TCP | `http` | Host |
|
||||
| TCP | `tls` | Server Name |
|
||||
| UDP | `quic` | Server Name |
|
||||
| UDP | `stun` | / |
|
||||
| TCP/UDP | `dns` | / |
|
||||
| TCP/UDP | `bittorrent` | / |
|
||||
| UDP | `dtls` | / |
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.10.0 中的更改"
|
||||
|
||||
:material-plus: BitTorrent 支持
|
||||
:material-plus: DTLS 支持
|
||||
|
||||
如果在入站中启用,则可以嗅探连接的协议和域名(如果存在)。
|
||||
|
||||
#### 支持的协议
|
||||
|
||||
| 网络 | 协议 | 域名 |
|
||||
|:-------:|:----:|:-----------:|
|
||||
| TCP | HTTP | Host |
|
||||
| TCP | TLS | Server Name |
|
||||
| UDP | QUIC | Server Name |
|
||||
| UDP | STUN | / |
|
||||
| TCP/UDP | DNS | / |
|
||||
| 网络 | 协议 | 域名 |
|
||||
|:-------:|:------------:|:-----------:|
|
||||
| TCP | `http` | Host |
|
||||
| TCP | `tls` | Server Name |
|
||||
| UDP | `quic` | Server Name |
|
||||
| UDP | `stun` | / |
|
||||
| TCP/UDP | `dns` | / |
|
||||
| TCP/UDP | `bittorrent` | / |
|
||||
| UDP | `dtls` | / |
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
### Structure
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
@@ -128,7 +124,7 @@ Match source IP CIDR.
|
||||
|
||||
!!! info ""
|
||||
|
||||
`ip_cidr` is an alias for `source_ip_cidr` when the Rule Set is used in DNS rules or `rule_set_ipcidr_match_source` enabled in route rules.
|
||||
`ip_cidr` is an alias for `source_ip_cidr` when `rule_set_ipcidr_match_source` enabled in route/DNS rules.
|
||||
|
||||
Match IP CIDR.
|
||||
|
||||
@@ -172,7 +168,7 @@ Match android package name.
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match WiFi SSID.
|
||||
|
||||
@@ -180,7 +176,7 @@ Match WiFi SSID.
|
||||
|
||||
!!! quote ""
|
||||
|
||||
Only supported in graphical clients on Android and iOS.
|
||||
Only supported in graphical clients on Android and Apple platforms.
|
||||
|
||||
Match WiFi BSSID.
|
||||
|
||||
|
||||
@@ -2,51 +2,55 @@
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Rule Set
|
||||
!!! quote "Changes in sing-box 1.10.0"
|
||||
|
||||
:material-plus: `type: inline`
|
||||
|
||||
# rule-set
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
|
||||
### Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "",
|
||||
"tag": "",
|
||||
"format": "",
|
||||
|
||||
... // Typed Fields
|
||||
}
|
||||
```
|
||||
=== "Inline"
|
||||
|
||||
#### Local Structure
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "local",
|
||||
|
||||
...
|
||||
|
||||
"path": ""
|
||||
}
|
||||
```
|
||||
```json
|
||||
{
|
||||
"type": "inline", // optional
|
||||
"tag": "",
|
||||
"rules": []
|
||||
}
|
||||
```
|
||||
|
||||
#### Remote Structure
|
||||
=== "Local File"
|
||||
|
||||
!!! info ""
|
||||
```json
|
||||
{
|
||||
"type": "local",
|
||||
"tag": "",
|
||||
"format": "source", // or binary
|
||||
"path": ""
|
||||
}
|
||||
```
|
||||
|
||||
Remote rule-set will be cached if `experimental.cache_file.enabled`.
|
||||
=== "Remote File"
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "remote",
|
||||
|
||||
...,
|
||||
|
||||
"url": "",
|
||||
"download_detour": "",
|
||||
"update_interval": ""
|
||||
}
|
||||
```
|
||||
!!! info ""
|
||||
|
||||
Remote rule-set will be cached if `experimental.cache_file.enabled`.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "",
|
||||
"format": "source", // or binary
|
||||
"url": "",
|
||||
"download_detour": "", // optional
|
||||
"update_interval": "" // optional
|
||||
}
|
||||
```
|
||||
|
||||
### Fields
|
||||
|
||||
@@ -54,19 +58,31 @@ icon: material/new-box
|
||||
|
||||
==Required==
|
||||
|
||||
Type of Rule Set, `local` or `remote`.
|
||||
Type of rule-set, `local` or `remote`.
|
||||
|
||||
#### tag
|
||||
|
||||
==Required==
|
||||
|
||||
Tag of Rule Set.
|
||||
Tag of rule-set.
|
||||
|
||||
### Inline Fields
|
||||
|
||||
!!! question "Since sing-box 1.10.0"
|
||||
|
||||
#### rules
|
||||
|
||||
==Required==
|
||||
|
||||
List of [Headless Rule](./headless-rule.md/).
|
||||
|
||||
### Local or Remote Fields
|
||||
|
||||
#### format
|
||||
|
||||
==Required==
|
||||
|
||||
Format of Rule Set, `source` or `binary`.
|
||||
Format of rule-set file, `source` or `binary`.
|
||||
|
||||
### Local Fields
|
||||
|
||||
@@ -74,7 +90,11 @@ Format of Rule Set, `source` or `binary`.
|
||||
|
||||
==Required==
|
||||
|
||||
File path of Rule Set.
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified since sing-box 1.10.0.
|
||||
|
||||
File path of rule-set.
|
||||
|
||||
### Remote Fields
|
||||
|
||||
@@ -82,7 +102,7 @@ File path of Rule Set.
|
||||
|
||||
==Required==
|
||||
|
||||
Download URL of Rule Set.
|
||||
Download URL of rule-set.
|
||||
|
||||
#### download_detour
|
||||
|
||||
@@ -92,6 +112,6 @@ Default outbound will be used if empty.
|
||||
|
||||
#### update_interval
|
||||
|
||||
Update interval of Rule Set.
|
||||
Update interval of rule-set.
|
||||
|
||||
`1d` will be used if empty.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/new-box
|
||||
---
|
||||
|
||||
# Source Format
|
||||
|
||||
!!! question "Since sing-box 1.8.0"
|
||||
@@ -25,7 +21,7 @@ Use `sing-box rule-set compile [--output <file-name>.srs] <file-name>.json` to c
|
||||
|
||||
==Required==
|
||||
|
||||
Version of Rule Set, must be `1`.
|
||||
Version of rule-set, must be `1`.
|
||||
|
||||
#### rules
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
|
||||
!!! quote "Changes in sing-box 1.8.0"
|
||||
|
||||
:material-alert-decagram: [utls](#utls)
|
||||
@@ -183,6 +178,10 @@ The server certificate line array, in PEM format.
|
||||
|
||||
#### certificate_path
|
||||
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
The path to the server certificate, in PEM format.
|
||||
|
||||
#### key
|
||||
@@ -195,6 +194,10 @@ The server private key line array, in PEM format.
|
||||
|
||||
==Server only==
|
||||
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
The path to the server private key, in PEM format.
|
||||
|
||||
## Custom TLS support
|
||||
@@ -271,6 +274,10 @@ ECH key line array, in PEM format.
|
||||
|
||||
==Server only==
|
||||
|
||||
!!! note ""
|
||||
|
||||
Will be automatically reloaded if file modified.
|
||||
|
||||
The path to ECH key, in PEM format.
|
||||
|
||||
#### config
|
||||
@@ -402,8 +409,4 @@ A hexadecimal string with zero to eight digits.
|
||||
|
||||
The maximum time difference between the server and the client.
|
||||
|
||||
Check disabled if empty.
|
||||
|
||||
### Reload
|
||||
|
||||
For server configuration, certificate, key and ECH key will be automatically reloaded if modified.
|
||||
Check disabled if empty.
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
icon: material/alert-decagram
|
||||
---
|
||||
|
||||
!!! quote "sing-box 1.8.0 中的更改"
|
||||
|
||||
:material-alert-decagram: [utls](#utls)
|
||||
@@ -180,12 +176,20 @@ TLS 版本值:
|
||||
|
||||
#### certificate_path
|
||||
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
服务器 PEM 证书路径。
|
||||
|
||||
#### key
|
||||
|
||||
==仅服务器==
|
||||
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
服务器 PEM 私钥行数组。
|
||||
|
||||
#### key_path
|
||||
@@ -262,6 +266,10 @@ ECH PEM 密钥行数组
|
||||
|
||||
==仅服务器==
|
||||
|
||||
!!! note ""
|
||||
|
||||
文件更改时将自动重新加载。
|
||||
|
||||
ECH PEM 密钥路径
|
||||
|
||||
#### config
|
||||
@@ -388,7 +396,3 @@ ACME DNS01 验证字段。如果配置,将禁用其他验证方法。
|
||||
服务器与和客户端之间允许的最大时间差。
|
||||
|
||||
默认禁用检查。
|
||||
|
||||
### 重载
|
||||
|
||||
对于服务器配置,如果修改,证书和密钥将自动重新加载。
|
||||
@@ -4,6 +4,20 @@ icon: material/delete-alert
|
||||
|
||||
# Deprecated Feature List
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### TUN address fields are merged
|
||||
|
||||
`inet4_address` and `inet6_address` are merged into `address`,
|
||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||
|
||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||
|
||||
#### Drop support for go1.18 and go1.19
|
||||
|
||||
Due to maintenance difficulties, sing-box 1.10.0 requires at least Go 1.20 to compile.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
#### Cache file and related features in Clash API
|
||||
@@ -19,7 +33,7 @@ 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,
|
||||
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
|
||||
@@ -29,7 +43,7 @@ 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,
|
||||
sing-box 1.8.0 introduces [rule-set](/configuration/rule-set/), which can completely replace Geosite,
|
||||
check [Migration](/migration/#migrate-geosite-to-rule-sets).
|
||||
|
||||
## 1.6.0
|
||||
|
||||
@@ -4,6 +4,20 @@ icon: material/delete-alert
|
||||
|
||||
# 废弃功能列表
|
||||
|
||||
## 1.10.0
|
||||
|
||||
#### TUN 地址字段已合并
|
||||
|
||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
|
||||
#### 移除对 go1.18 和 go1.19 的支持
|
||||
|
||||
由于维护困难,sing-box 1.10.0 要求至少 Go 1.20 才能编译。
|
||||
|
||||
## 1.8.0
|
||||
|
||||
#### Clash API 中的 Cache file 及相关功能
|
||||
|
||||
38
docs/manual/misc/tunnelvision.md
Normal file
38
docs/manual/misc/tunnelvision.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
icon: material/book-lock-open
|
||||
---
|
||||
|
||||
# TunnelVision
|
||||
|
||||
TunnelVision is an attack that uses DHCP option 121 to set higher priority routes
|
||||
so that traffic does not go through the VPN.
|
||||
|
||||
Reference: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3661
|
||||
|
||||
## Status
|
||||
|
||||
### Android
|
||||
|
||||
Android does not handle DHCP option 121 and is not affected.
|
||||
|
||||
### Apple platforms
|
||||
|
||||
Update [sing-box graphical client](/clients/apple/#download) to `1.9.0-rc.16` or newer,
|
||||
then enable `includeAllNetworks` in `Settings` — `Packet Tunnel` and you will be unaffected.
|
||||
|
||||
Note: when `includeAllNetworks` is enabled, the default TUN stack is changed to `gvisor`,
|
||||
and the `system` and `mixed` stacks are not available.
|
||||
|
||||
### Linux
|
||||
|
||||
Update sing-box to `1.9.0-rc.16` or newer, rules generated by `auto-route` are unaffected.
|
||||
|
||||
### Windows
|
||||
|
||||
No solution yet.
|
||||
|
||||
## Workarounds
|
||||
|
||||
* Don't connect to untrusted networks
|
||||
* Relay untrusted network through another device
|
||||
* Just ignore it
|
||||
@@ -1,208 +0,0 @@
|
||||
---
|
||||
icon: material/alpha-t-box
|
||||
---
|
||||
|
||||
# TUIC
|
||||
|
||||
A recently popular Chinese-made simple protocol based on QUIC, the selling point is the BBR congestion control algorithm.
|
||||
|
||||
!!! warning
|
||||
|
||||
Even though GFW rarely blocks UDP-based proxies, such protocols actually have far more characteristics than TCP based proxies.
|
||||
|
||||
| Specification | Binary Characteristics | Active Detect Hiddenness |
|
||||
|-----------------------------------------------------------|------------------------|--------------------------|
|
||||
| [GitHub](https://github.com/EAimTY/tuic/blob/dev/SPEC.md) | :material-alert: | :material-check: |
|
||||
|
||||
## Password Generator
|
||||
|
||||
| Generated UUID | Generated Password | Action |
|
||||
|------------------------|----------------------------|-----------------------------------------------------------------|
|
||||
| <code id="uuid"><code> | <code id="password"><code> | <button class="md-button" onclick="generate()">Refresh</button> |
|
||||
|
||||
<script>
|
||||
function generateUUID() {
|
||||
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
let r = Math.random() * 16 | 0,
|
||||
v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
document.getElementById("uuid").textContent = uuid;
|
||||
}
|
||||
function generatePassword() {
|
||||
const array = new Uint8Array(16);
|
||||
window.crypto.getRandomValues(array);
|
||||
document.getElementById("password").textContent = btoa(String.fromCharCode.apply(null, array));
|
||||
}
|
||||
function generate() {
|
||||
generateUUID();
|
||||
generatePassword();
|
||||
}
|
||||
generate();
|
||||
</script>
|
||||
|
||||
## :material-server: Server Example
|
||||
|
||||
=== ":material-harddisk: With local certificate"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>"
|
||||
}
|
||||
],
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"key_path": "/path/to/key.pem",
|
||||
"certificate_path": "/path/to/certificate.pem"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-auto-fix: With ACME"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>"
|
||||
}
|
||||
],
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"acme": {
|
||||
"domain": "example.org",
|
||||
"email": "admin@example.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-cloud: With ACME and Cloudflare API"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"listen": "::",
|
||||
"listen_port": 8080,
|
||||
"users": [
|
||||
{
|
||||
"name": "sekai",
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>"
|
||||
}
|
||||
],
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"acme": {
|
||||
"domain": "example.org",
|
||||
"email": "admin@example.org",
|
||||
"dns01_challenge": {
|
||||
"provider": "cloudflare",
|
||||
"api_token": "my_token"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## :material-cellphone-link: Client Example
|
||||
|
||||
=== ":material-web-check: With valid certificate"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>",
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-check: With self-sign certificate"
|
||||
|
||||
!!! info "Tip"
|
||||
|
||||
Use `sing-box merge` command to merge configuration and certificate into one file.
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>",
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"certificate_path": "/path/to/certificate.pem"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-alert: Ignore certificate verification"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
{
|
||||
"type": "tuic",
|
||||
"server": "127.0.0.1",
|
||||
"server_port": 8080,
|
||||
"uuid": "<uuid>",
|
||||
"password": "<password>",
|
||||
"congestion_control": "bbr",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": "example.org",
|
||||
"insecure": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -290,52 +290,6 @@ flowchart TB
|
||||
|
||||
=== ":material-dns: DNS rules"
|
||||
|
||||
!!! info
|
||||
|
||||
DNS rules are optional if FakeIP is used.
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"geosite": "geolocation-cn",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-dns: DNS rules (1.8.0+)"
|
||||
|
||||
!!! info
|
||||
|
||||
DNS rules are optional if FakeIP is used.
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
@@ -382,74 +336,180 @@ flowchart TB
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-router-network: Route rules"
|
||||
=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)"
|
||||
|
||||
```json
|
||||
{
|
||||
"outbounds": [
|
||||
=== ":material-shield-off: With DNS leaks"
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"protocol": "dns"
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"port": 53
|
||||
"tag": "local",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
},
|
||||
{
|
||||
"geoip": "private",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
"rules": [
|
||||
{
|
||||
"port": 853
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"network": "udp",
|
||||
"port": 443
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"protocol": "stun"
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Default",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn",
|
||||
"invert": true
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
}
|
||||
],
|
||||
"server": "local"
|
||||
}
|
||||
],
|
||||
"outbound": "block"
|
||||
]
|
||||
},
|
||||
{
|
||||
"geosite": "geolocation-cn",
|
||||
"outbound": "direct"
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-!cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_rdrc": true
|
||||
},
|
||||
"clash_api": {
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-router-network: Route rules (1.8.0+)"
|
||||
=== ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn",
|
||||
"invert": true
|
||||
},
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
}
|
||||
],
|
||||
"server": "google",
|
||||
"client_subnet": "114.114.114.114/24" // Any China client IP address
|
||||
}
|
||||
]
|
||||
},
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-!cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-!cn.srs"
|
||||
},
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geoip-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geoip/rule-set/geoip-cn.srs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true,
|
||||
"store_rdrc": true
|
||||
},
|
||||
"clash_api": {
|
||||
"default_mode": "Enhanced"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-router-network: Route rules"
|
||||
|
||||
```json
|
||||
{
|
||||
|
||||
@@ -2,6 +2,92 @@
|
||||
icon: material/arrange-bring-forward
|
||||
---
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN address fields are merged
|
||||
|
||||
`inet4_address` and `inet6_address` are merged into `address`,
|
||||
`inet4_route_address` and `inet6_route_address` are merged into `route_address`,
|
||||
`inet4_route_exclude_address` and `inet6_route_exclude_address` are merged into `route_exclude_address`.
|
||||
|
||||
Old fields are deprecated and will be removed in sing-box 1.11.0.
|
||||
|
||||
!!! info "References"
|
||||
|
||||
[TUN](/configuration/inbound/tun/)
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: New"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"address": [
|
||||
"172.19.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### `domain_suffix` behavior update
|
||||
|
||||
For historical reasons, sing-box's `domain_suffix` rule matches literal prefixes instead of the same as other projects.
|
||||
|
||||
sing-box 1.9.0 modifies the behavior of `domain_suffix`: If the rule value is prefixed with `.`,
|
||||
the behavior is unchanged, otherwise it matches `(domain|.+\.domain)` instead.
|
||||
|
||||
### `process_path` format update on Windows
|
||||
|
||||
The `process_path` rule of sing-box is inherited from Clash,
|
||||
the original code uses the local system's path format (e.g. `\Device\HarddiskVolume1\folder\program.exe`),
|
||||
but when the device has multiple disks, the HarddiskVolume serial number is not stable.
|
||||
|
||||
sing-box 1.9.0 make QueryFullProcessImageNameW output a Win32 path (such as `C:\folder\program.exe`),
|
||||
which will disrupt the existing `process_path` use cases in Windows.
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### :material-close-box: Migrate cache file from Clash API to independent options
|
||||
@@ -42,7 +128,7 @@ icon: material/arrange-bring-forward
|
||||
}
|
||||
```
|
||||
|
||||
### :material-checkbox-intermediate: Migrate GeoIP to rule sets
|
||||
### :material-checkbox-intermediate: Migrate GeoIP to rule-sets
|
||||
|
||||
!!! info "References"
|
||||
|
||||
@@ -50,11 +136,11 @@ icon: material/arrange-bring-forward
|
||||
[Route](/configuration/route/) /
|
||||
[Route Rule](/configuration/route/rule/) /
|
||||
[DNS Rule](/configuration/dns/rule/) /
|
||||
[Rule Set](/configuration/rule-set/)
|
||||
[rule-set](/configuration/rule-set/)
|
||||
|
||||
!!! tip
|
||||
|
||||
`sing-box geoip` commands can help you convert custom GeoIP into rule sets.
|
||||
`sing-box geoip` commands can help you convert custom GeoIP into rule-sets.
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
@@ -121,13 +207,13 @@ icon: material/arrange-bring-forward
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true // required to save Rule Set cache
|
||||
"enabled": true // required to save rule-set cache
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### :material-checkbox-intermediate: Migrate Geosite to rule sets
|
||||
### :material-checkbox-intermediate: Migrate Geosite to rule-sets
|
||||
|
||||
!!! info "References"
|
||||
|
||||
@@ -135,11 +221,11 @@ icon: material/arrange-bring-forward
|
||||
[Route](/configuration/route/) /
|
||||
[Route Rule](/configuration/route/rule/) /
|
||||
[DNS Rule](/configuration/dns/rule/) /
|
||||
[Rule Set](/configuration/rule-set/)
|
||||
[rule-set](/configuration/rule-set/)
|
||||
|
||||
!!! tip
|
||||
|
||||
`sing-box geosite` commands can help you convert custom Geosite into rule sets.
|
||||
`sing-box geosite` commands can help you convert custom Geosite into rule-sets.
|
||||
|
||||
=== ":material-card-remove: Deprecated"
|
||||
|
||||
@@ -182,7 +268,7 @@ icon: material/arrange-bring-forward
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true // required to save Rule Set cache
|
||||
"enabled": true // required to save rule-set cache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,91 @@
|
||||
icon: material/arrange-bring-forward
|
||||
---
|
||||
|
||||
## 1.10.0
|
||||
|
||||
### TUN 地址字段已合并
|
||||
|
||||
`inet4_address` 和 `inet6_address` 已合并为 `address`,
|
||||
`inet4_route_address` 和 `inet6_route_address` 已合并为 `route_address`,
|
||||
`inet4_route_exclude_address` 和 `inet6_route_exclude_address` 已合并为 `route_exclude_address`。
|
||||
|
||||
旧字段已废弃,且将在 sing-box 1.11.0 中移除。
|
||||
|
||||
!!! info "参考"
|
||||
|
||||
[TUN](/zh/configuration/inbound/tun/)
|
||||
|
||||
=== ":material-card-remove: 弃用的"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"inet4_route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1"
|
||||
],
|
||||
"inet6_route_address": [
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"inet4_route_exclude_address": [
|
||||
"192.168.0.0/16"
|
||||
],
|
||||
"inet6_route_exclude_address": [
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-card-multiple: 新的"
|
||||
|
||||
```json
|
||||
{
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
"address": [
|
||||
"172.19.0.1/30",
|
||||
"fdfe:dcba:9876::1/126"
|
||||
],
|
||||
"route_address": [
|
||||
"0.0.0.0/1",
|
||||
"128.0.0.0/1",
|
||||
"::/1",
|
||||
"8000::/1"
|
||||
],
|
||||
"route_exclude_address": [
|
||||
"192.168.0.0/16",
|
||||
"fc00::/7"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 1.9.0
|
||||
|
||||
### `domain_suffix` 行为更新
|
||||
|
||||
由于历史原因,sing-box 的 `domain_suffix` 规则匹配字面前缀,而不与其他项目相同。
|
||||
|
||||
sing-box 1.9.0 修改了 `domain_suffix` 的行为:如果规则值以 `.` 为前缀则行为不变,否则改为匹配 `(domain|.+\.domain)`。
|
||||
|
||||
### 对 Windows 上 `process_path` 格式的更新
|
||||
|
||||
sing-box 的 `process_path` 规则继承自Clash,
|
||||
原始代码使用本地系统的路径格式(例如 `\Device\HarddiskVolume1\folder\program.exe`),
|
||||
但是当设备有多个硬盘时,该 HarddiskVolume 系列号并不稳定。
|
||||
|
||||
sing-box 1.9.0 使 QueryFullProcessImageNameW 输出 Win32 路径(如 `C:\folder\program.exe`),
|
||||
这将会破坏现有的 Windows `process_path` 用例。
|
||||
|
||||
## 1.8.0
|
||||
|
||||
### :material-close-box: 将缓存文件从 Clash API 迁移到独立选项
|
||||
@@ -121,7 +206,7 @@ icon: material/arrange-bring-forward
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true // required to save Rule Set cache
|
||||
"enabled": true // required to save rule-set cache
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,7 +267,7 @@ icon: material/arrange-bring-forward
|
||||
},
|
||||
"experimental": {
|
||||
"cache_file": {
|
||||
"enabled": true // required to save Rule Set cache
|
||||
"enabled": true // required to save rule-set cache
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ var (
|
||||
string(bucketExpand),
|
||||
string(bucketMode),
|
||||
string(bucketRuleSet),
|
||||
string(bucketRDRC),
|
||||
}
|
||||
|
||||
cacheIDDefault = []byte("default")
|
||||
@@ -37,17 +38,26 @@ var (
|
||||
var _ adapter.CacheFile = (*CacheFile)(nil)
|
||||
|
||||
type CacheFile struct {
|
||||
ctx context.Context
|
||||
path string
|
||||
cacheID []byte
|
||||
storeFakeIP bool
|
||||
|
||||
ctx context.Context
|
||||
path string
|
||||
cacheID []byte
|
||||
storeFakeIP bool
|
||||
storeRDRC bool
|
||||
rdrcTimeout time.Duration
|
||||
DB *bbolt.DB
|
||||
saveAccess sync.RWMutex
|
||||
saveMetadataTimer *time.Timer
|
||||
saveFakeIPAccess sync.RWMutex
|
||||
saveDomain map[netip.Addr]string
|
||||
saveAddress4 map[string]netip.Addr
|
||||
saveAddress6 map[string]netip.Addr
|
||||
saveMetadataTimer *time.Timer
|
||||
saveRDRCAccess sync.RWMutex
|
||||
saveRDRC map[saveRDRCCacheKey]bool
|
||||
}
|
||||
|
||||
type saveRDRCCacheKey struct {
|
||||
TransportName string
|
||||
QuestionName string
|
||||
QType uint16
|
||||
}
|
||||
|
||||
func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
@@ -61,14 +71,25 @@ func New(ctx context.Context, options option.CacheFileOptions) *CacheFile {
|
||||
if options.CacheID != "" {
|
||||
cacheIDBytes = append([]byte{0}, []byte(options.CacheID)...)
|
||||
}
|
||||
var rdrcTimeout time.Duration
|
||||
if options.StoreRDRC {
|
||||
if options.RDRCTimeout > 0 {
|
||||
rdrcTimeout = time.Duration(options.RDRCTimeout)
|
||||
} else {
|
||||
rdrcTimeout = 7 * 24 * time.Hour
|
||||
}
|
||||
}
|
||||
return &CacheFile{
|
||||
ctx: ctx,
|
||||
path: filemanager.BasePath(ctx, path),
|
||||
cacheID: cacheIDBytes,
|
||||
storeFakeIP: options.StoreFakeIP,
|
||||
storeRDRC: options.StoreRDRC,
|
||||
rdrcTimeout: rdrcTimeout,
|
||||
saveDomain: make(map[netip.Addr]string),
|
||||
saveAddress4: make(map[string]netip.Addr),
|
||||
saveAddress6: make(map[string]netip.Addr),
|
||||
saveRDRC: make(map[saveRDRCCacheKey]bool),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
|
||||
}
|
||||
|
||||
func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger logger.Logger) {
|
||||
c.saveAccess.Lock()
|
||||
c.saveFakeIPAccess.Lock()
|
||||
if oldDomain, loaded := c.saveDomain[address]; loaded {
|
||||
if address.Is4() {
|
||||
delete(c.saveAddress4, oldDomain)
|
||||
@@ -111,27 +111,27 @@ func (c *CacheFile) FakeIPStoreAsync(address netip.Addr, domain string, logger l
|
||||
} else {
|
||||
c.saveAddress6[domain] = address
|
||||
}
|
||||
c.saveAccess.Unlock()
|
||||
c.saveFakeIPAccess.Unlock()
|
||||
go func() {
|
||||
err := c.FakeIPStore(address, domain)
|
||||
if err != nil {
|
||||
logger.Warn("save FakeIP address pair: ", err)
|
||||
logger.Warn("save FakeIP cache: ", err)
|
||||
}
|
||||
c.saveAccess.Lock()
|
||||
c.saveFakeIPAccess.Lock()
|
||||
delete(c.saveDomain, address)
|
||||
if address.Is4() {
|
||||
delete(c.saveAddress4, domain)
|
||||
} else {
|
||||
delete(c.saveAddress6, domain)
|
||||
}
|
||||
c.saveAccess.Unlock()
|
||||
c.saveFakeIPAccess.Unlock()
|
||||
}()
|
||||
}
|
||||
|
||||
func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {
|
||||
c.saveAccess.RLock()
|
||||
c.saveFakeIPAccess.RLock()
|
||||
cachedDomain, cached := c.saveDomain[address]
|
||||
c.saveAccess.RUnlock()
|
||||
c.saveFakeIPAccess.RUnlock()
|
||||
if cached {
|
||||
return cachedDomain, true
|
||||
}
|
||||
@@ -152,13 +152,13 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo
|
||||
cachedAddress netip.Addr
|
||||
cached bool
|
||||
)
|
||||
c.saveAccess.RLock()
|
||||
c.saveFakeIPAccess.RLock()
|
||||
if !isIPv6 {
|
||||
cachedAddress, cached = c.saveAddress4[domain]
|
||||
} else {
|
||||
cachedAddress, cached = c.saveAddress6[domain]
|
||||
}
|
||||
c.saveAccess.RUnlock()
|
||||
c.saveFakeIPAccess.RUnlock()
|
||||
if cached {
|
||||
return cachedAddress, true
|
||||
}
|
||||
|
||||
109
experimental/cachefile/rdrc.go
Normal file
109
experimental/cachefile/rdrc.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package cachefile
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/bbolt"
|
||||
"github.com/sagernet/sing/common/buf"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
var bucketRDRC = []byte("rdrc2")
|
||||
|
||||
func (c *CacheFile) StoreRDRC() bool {
|
||||
return c.storeRDRC
|
||||
}
|
||||
|
||||
func (c *CacheFile) RDRCTimeout() time.Duration {
|
||||
return c.rdrcTimeout
|
||||
}
|
||||
|
||||
func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (rejected bool) {
|
||||
c.saveRDRCAccess.RLock()
|
||||
rejected, cached := c.saveRDRC[saveRDRCCacheKey{transportName, qName, qType}]
|
||||
c.saveRDRCAccess.RUnlock()
|
||||
if cached {
|
||||
return
|
||||
}
|
||||
key := buf.Get(2 + len(qName))
|
||||
binary.BigEndian.PutUint16(key, qType)
|
||||
copy(key[2:], qName)
|
||||
defer buf.Put(key)
|
||||
var deleteCache bool
|
||||
err := c.DB.View(func(tx *bbolt.Tx) error {
|
||||
bucket := c.bucket(tx, bucketRDRC)
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
bucket = bucket.Bucket([]byte(transportName))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
content := bucket.Get(key)
|
||||
if content == nil {
|
||||
return nil
|
||||
}
|
||||
expiresAt := time.Unix(int64(binary.BigEndian.Uint64(content)), 0)
|
||||
if time.Now().After(expiresAt) {
|
||||
deleteCache = true
|
||||
return nil
|
||||
}
|
||||
rejected = true
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if deleteCache {
|
||||
c.DB.Update(func(tx *bbolt.Tx) error {
|
||||
bucket := c.bucket(tx, bucketRDRC)
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
bucket = bucket.Bucket([]byte(transportName))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
return bucket.Delete(key)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error {
|
||||
return c.DB.Batch(func(tx *bbolt.Tx) error {
|
||||
bucket, err := c.createBucket(tx, bucketRDRC)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bucket, err = bucket.CreateBucketIfNotExists([]byte(transportName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := buf.Get(2 + len(qName))
|
||||
binary.BigEndian.PutUint16(key, qType)
|
||||
copy(key[2:], qName)
|
||||
defer buf.Put(key)
|
||||
expiresAt := buf.Get(8)
|
||||
defer buf.Put(expiresAt)
|
||||
binary.BigEndian.PutUint64(expiresAt, uint64(time.Now().Add(c.rdrcTimeout).Unix()))
|
||||
return bucket.Put(key, expiresAt)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *CacheFile) SaveRDRCAsync(transportName string, qName string, qType uint16, logger logger.Logger) {
|
||||
saveKey := saveRDRCCacheKey{transportName, qName, qType}
|
||||
c.saveRDRCAccess.Lock()
|
||||
c.saveRDRC[saveKey] = true
|
||||
c.saveRDRCAccess.Unlock()
|
||||
go func() {
|
||||
err := c.SaveRDRC(transportName, qName, qType)
|
||||
if err != nil {
|
||||
logger.Warn("save RDRC: ", err)
|
||||
}
|
||||
c.saveRDRCAccess.Lock()
|
||||
delete(c.saveRDRC, saveKey)
|
||||
c.saveRDRCAccess.Unlock()
|
||||
}()
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package experimental
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
@@ -27,11 +28,26 @@ func NewClashServer(ctx context.Context, router adapter.Router, logFactory log.O
|
||||
}
|
||||
|
||||
func CalculateClashModeList(options option.Options) []string {
|
||||
var clashMode []string
|
||||
clashMode = append(clashMode, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...)
|
||||
clashMode = append(clashMode, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...)
|
||||
clashMode = common.FilterNotDefault(common.Uniq(clashMode))
|
||||
return clashMode
|
||||
var clashModes []string
|
||||
clashModes = append(clashModes, extraClashModeFromRule(common.PtrValueOrDefault(options.Route).Rules)...)
|
||||
clashModes = append(clashModes, extraClashModeFromDNSRule(common.PtrValueOrDefault(options.DNS).Rules)...)
|
||||
clashModes = common.FilterNotDefault(common.Uniq(clashModes))
|
||||
predefinedOrder := []string{
|
||||
"Rule", "Global", "Direct",
|
||||
}
|
||||
var newClashModes []string
|
||||
for _, mode := range clashModes {
|
||||
if !common.Contains(predefinedOrder, mode) {
|
||||
newClashModes = append(newClashModes, mode)
|
||||
}
|
||||
}
|
||||
sort.Strings(newClashModes)
|
||||
for _, mode := range predefinedOrder {
|
||||
if common.Contains(clashModes, mode) {
|
||||
newClashModes = append(newClashModes, mode)
|
||||
}
|
||||
}
|
||||
return newClashModes
|
||||
}
|
||||
|
||||
func extraClashModeFromRule(rules []option.Rule) []string {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func connectionRouter(router adapter.Router, trafficManager *trafficontrol.Manager) http.Handler {
|
||||
@@ -76,10 +77,10 @@ func getConnections(trafficManager *trafficontrol.Manager) func(w http.ResponseW
|
||||
|
||||
func closeConnection(trafficManager *trafficontrol.Manager) func(w http.ResponseWriter, r *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
id := uuid.FromStringOrNil(chi.URLParam(r, "id"))
|
||||
snapshot := trafficManager.Snapshot()
|
||||
for _, c := range snapshot.Connections {
|
||||
if id == c.ID() {
|
||||
if id == c.Metadata().ID {
|
||||
c.Close()
|
||||
break
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
@@ -19,7 +21,6 @@ import (
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
"github.com/sagernet/sing/service"
|
||||
@@ -144,7 +145,18 @@ func (s *Server) PreStart() error {
|
||||
func (s *Server) Start() error {
|
||||
if s.externalController {
|
||||
s.checkAndDownloadExternalUI()
|
||||
listener, err := net.Listen("tcp", s.httpServer.Addr)
|
||||
var (
|
||||
listener net.Listener
|
||||
err error
|
||||
)
|
||||
for i := 0; i < 3; i++ {
|
||||
listener, err = net.Listen("tcp", s.httpServer.Addr)
|
||||
if runtime.GOOS == "android" && errors.Is(err, syscall.EADDRINUSE) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return E.Cause(err, "external controller listen error")
|
||||
}
|
||||
@@ -218,58 +230,15 @@ func (s *Server) TrafficManager() *trafficontrol.Manager {
|
||||
}
|
||||
|
||||
func (s *Server) RoutedConnection(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, matchedRule adapter.Rule) (net.Conn, adapter.Tracker) {
|
||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||
tracker := trafficontrol.NewTCPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||
return tracker, tracker
|
||||
}
|
||||
|
||||
func (s *Server) RoutedPacketConnection(ctx context.Context, conn N.PacketConn, metadata adapter.InboundContext, matchedRule adapter.Rule) (N.PacketConn, adapter.Tracker) {
|
||||
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, castMetadata(metadata), s.router, matchedRule)
|
||||
tracker := trafficontrol.NewUDPTracker(conn, s.trafficManager, metadata, s.router, matchedRule)
|
||||
return tracker, tracker
|
||||
}
|
||||
|
||||
func castMetadata(metadata adapter.InboundContext) trafficontrol.Metadata {
|
||||
var inbound string
|
||||
if metadata.Inbound != "" {
|
||||
inbound = metadata.InboundType + "/" + metadata.Inbound
|
||||
} else {
|
||||
inbound = metadata.InboundType
|
||||
}
|
||||
var domain string
|
||||
if metadata.Domain != "" {
|
||||
domain = metadata.Domain
|
||||
} else {
|
||||
domain = metadata.Destination.Fqdn
|
||||
}
|
||||
var processPath string
|
||||
if metadata.ProcessInfo != nil {
|
||||
if metadata.ProcessInfo.ProcessPath != "" {
|
||||
processPath = metadata.ProcessInfo.ProcessPath
|
||||
} else if metadata.ProcessInfo.PackageName != "" {
|
||||
processPath = metadata.ProcessInfo.PackageName
|
||||
}
|
||||
if processPath == "" {
|
||||
if metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(metadata.ProcessInfo.UserId)
|
||||
}
|
||||
} else if metadata.ProcessInfo.User != "" {
|
||||
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.User, ")")
|
||||
} else if metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(processPath, " (", metadata.ProcessInfo.UserId, ")")
|
||||
}
|
||||
}
|
||||
return trafficontrol.Metadata{
|
||||
NetWork: metadata.Network,
|
||||
Type: inbound,
|
||||
SrcIP: metadata.Source.Addr,
|
||||
DstIP: metadata.Destination.Addr,
|
||||
SrcPort: F.ToString(metadata.Source.Port),
|
||||
DstPort: F.ToString(metadata.Destination.Port),
|
||||
Host: domain,
|
||||
DNSMode: "normal",
|
||||
ProcessPath: processPath,
|
||||
}
|
||||
}
|
||||
|
||||
func authentication(serverSecret string) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -2,10 +2,17 @@ package trafficontrol
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi/compatible"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/x/list"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
@@ -16,9 +23,11 @@ type Manager struct {
|
||||
uploadTotal atomic.Int64
|
||||
downloadTotal atomic.Int64
|
||||
|
||||
connections compatible.Map[string, tracker]
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
connections compatible.Map[uuid.UUID, Tracker]
|
||||
closedConnectionsAccess sync.Mutex
|
||||
closedConnections list.List[TrackerMetadata]
|
||||
ticker *time.Ticker
|
||||
done chan struct{}
|
||||
// process *process.Process
|
||||
memory uint64
|
||||
}
|
||||
@@ -33,12 +42,22 @@ func NewManager() *Manager {
|
||||
return manager
|
||||
}
|
||||
|
||||
func (m *Manager) Join(c tracker) {
|
||||
m.connections.Store(c.ID(), c)
|
||||
func (m *Manager) Join(c Tracker) {
|
||||
m.connections.Store(c.Metadata().ID, c)
|
||||
}
|
||||
|
||||
func (m *Manager) Leave(c tracker) {
|
||||
m.connections.Delete(c.ID())
|
||||
func (m *Manager) Leave(c Tracker) {
|
||||
metadata := c.Metadata()
|
||||
_, loaded := m.connections.LoadAndDelete(metadata.ID)
|
||||
if loaded {
|
||||
metadata.ClosedAt = time.Now()
|
||||
m.closedConnectionsAccess.Lock()
|
||||
defer m.closedConnectionsAccess.Unlock()
|
||||
if m.closedConnections.Len() >= 1000 {
|
||||
m.closedConnections.PopFront()
|
||||
}
|
||||
m.closedConnections.PushBack(metadata)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) PushUploaded(size int64) {
|
||||
@@ -59,14 +78,39 @@ func (m *Manager) Total() (up int64, down int64) {
|
||||
return m.uploadTotal.Load(), m.downloadTotal.Load()
|
||||
}
|
||||
|
||||
func (m *Manager) Connections() int {
|
||||
func (m *Manager) ConnectionsLen() int {
|
||||
return m.connections.Len()
|
||||
}
|
||||
|
||||
func (m *Manager) Connections() []TrackerMetadata {
|
||||
var connections []TrackerMetadata
|
||||
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
|
||||
connections = append(connections, value.Metadata())
|
||||
return true
|
||||
})
|
||||
return connections
|
||||
}
|
||||
|
||||
func (m *Manager) ClosedConnections() []TrackerMetadata {
|
||||
m.closedConnectionsAccess.Lock()
|
||||
defer m.closedConnectionsAccess.Unlock()
|
||||
return m.closedConnections.Array()
|
||||
}
|
||||
|
||||
func (m *Manager) Connection(id uuid.UUID) Tracker {
|
||||
connection, loaded := m.connections.Load(id)
|
||||
if !loaded {
|
||||
return nil
|
||||
}
|
||||
return connection
|
||||
}
|
||||
|
||||
func (m *Manager) Snapshot() *Snapshot {
|
||||
var connections []tracker
|
||||
m.connections.Range(func(_ string, value tracker) bool {
|
||||
connections = append(connections, value)
|
||||
var connections []Tracker
|
||||
m.connections.Range(func(_ uuid.UUID, value Tracker) bool {
|
||||
if value.Metadata().OutboundType != C.TypeDNS {
|
||||
connections = append(connections, value)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
@@ -75,10 +119,10 @@ func (m *Manager) Snapshot() *Snapshot {
|
||||
m.memory = memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased
|
||||
|
||||
return &Snapshot{
|
||||
UploadTotal: m.uploadTotal.Load(),
|
||||
DownloadTotal: m.downloadTotal.Load(),
|
||||
Connections: connections,
|
||||
Memory: m.memory,
|
||||
Upload: m.uploadTotal.Load(),
|
||||
Download: m.downloadTotal.Load(),
|
||||
Connections: connections,
|
||||
Memory: m.memory,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,8 +158,17 @@ func (m *Manager) Close() error {
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
DownloadTotal int64 `json:"downloadTotal"`
|
||||
UploadTotal int64 `json:"uploadTotal"`
|
||||
Connections []tracker `json:"connections"`
|
||||
Memory uint64 `json:"memory"`
|
||||
Download int64
|
||||
Upload int64
|
||||
Connections []Tracker
|
||||
Memory uint64
|
||||
}
|
||||
|
||||
func (s *Snapshot) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"downloadTotal": s.Download,
|
||||
"uploadTotal": s.Upload,
|
||||
"connections": common.Map(s.Connections, func(t Tracker) TrackerMetadata { return t.Metadata() }),
|
||||
"memory": s.Memory,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,97 +2,135 @@ package trafficontrol
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/atomic"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
NetWork string `json:"network"`
|
||||
Type string `json:"type"`
|
||||
SrcIP netip.Addr `json:"sourceIP"`
|
||||
DstIP netip.Addr `json:"destinationIP"`
|
||||
SrcPort string `json:"sourcePort"`
|
||||
DstPort string `json:"destinationPort"`
|
||||
Host string `json:"host"`
|
||||
DNSMode string `json:"dnsMode"`
|
||||
ProcessPath string `json:"processPath"`
|
||||
type TrackerMetadata struct {
|
||||
ID uuid.UUID
|
||||
Metadata adapter.InboundContext
|
||||
CreatedAt time.Time
|
||||
ClosedAt time.Time
|
||||
Upload *atomic.Int64
|
||||
Download *atomic.Int64
|
||||
Chain []string
|
||||
Rule adapter.Rule
|
||||
Outbound string
|
||||
OutboundType string
|
||||
}
|
||||
|
||||
type tracker interface {
|
||||
ID() string
|
||||
Close() error
|
||||
Leave()
|
||||
}
|
||||
|
||||
type trackerInfo struct {
|
||||
UUID uuid.UUID `json:"id"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
UploadTotal *atomic.Int64 `json:"upload"`
|
||||
DownloadTotal *atomic.Int64 `json:"download"`
|
||||
Start time.Time `json:"start"`
|
||||
Chain []string `json:"chains"`
|
||||
Rule string `json:"rule"`
|
||||
RulePayload string `json:"rulePayload"`
|
||||
}
|
||||
|
||||
func (t trackerInfo) MarshalJSON() ([]byte, error) {
|
||||
func (t TrackerMetadata) MarshalJSON() ([]byte, error) {
|
||||
var inbound string
|
||||
if t.Metadata.Inbound != "" {
|
||||
inbound = t.Metadata.InboundType + "/" + t.Metadata.Inbound
|
||||
} else {
|
||||
inbound = t.Metadata.InboundType
|
||||
}
|
||||
var domain string
|
||||
if t.Metadata.Domain != "" {
|
||||
domain = t.Metadata.Domain
|
||||
} else {
|
||||
domain = t.Metadata.Destination.Fqdn
|
||||
}
|
||||
var processPath string
|
||||
if t.Metadata.ProcessInfo != nil {
|
||||
if t.Metadata.ProcessInfo.ProcessPath != "" {
|
||||
processPath = t.Metadata.ProcessInfo.ProcessPath
|
||||
} else if t.Metadata.ProcessInfo.PackageName != "" {
|
||||
processPath = t.Metadata.ProcessInfo.PackageName
|
||||
}
|
||||
if processPath == "" {
|
||||
if t.Metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(t.Metadata.ProcessInfo.UserId)
|
||||
}
|
||||
} else if t.Metadata.ProcessInfo.User != "" {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.User, ")")
|
||||
} else if t.Metadata.ProcessInfo.UserId != -1 {
|
||||
processPath = F.ToString(processPath, " (", t.Metadata.ProcessInfo.UserId, ")")
|
||||
}
|
||||
}
|
||||
var rule string
|
||||
if t.Rule != nil {
|
||||
rule = F.ToString(t.Rule, " => ", t.Rule.Outbound())
|
||||
} else {
|
||||
rule = "final"
|
||||
}
|
||||
return json.Marshal(map[string]any{
|
||||
"id": t.UUID.String(),
|
||||
"metadata": t.Metadata,
|
||||
"upload": t.UploadTotal.Load(),
|
||||
"download": t.DownloadTotal.Load(),
|
||||
"start": t.Start,
|
||||
"id": t.ID,
|
||||
"metadata": map[string]any{
|
||||
"network": t.Metadata.Network,
|
||||
"type": inbound,
|
||||
"sourceIP": t.Metadata.Source.Addr,
|
||||
"destinationIP": t.Metadata.Destination.Addr,
|
||||
"sourcePort": F.ToString(t.Metadata.Source.Port),
|
||||
"destinationPort": F.ToString(t.Metadata.Destination.Port),
|
||||
"host": domain,
|
||||
"dnsMode": "normal",
|
||||
"processPath": processPath,
|
||||
},
|
||||
"upload": t.Upload.Load(),
|
||||
"download": t.Download.Load(),
|
||||
"start": t.CreatedAt,
|
||||
"chains": t.Chain,
|
||||
"rule": t.Rule,
|
||||
"rulePayload": t.RulePayload,
|
||||
"rule": rule,
|
||||
"rulePayload": "",
|
||||
})
|
||||
}
|
||||
|
||||
type tcpTracker struct {
|
||||
N.ExtendedConn `json:"-"`
|
||||
*trackerInfo
|
||||
manager *Manager
|
||||
type Tracker interface {
|
||||
adapter.Tracker
|
||||
Metadata() TrackerMetadata
|
||||
Close() error
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) ID() string {
|
||||
return tt.UUID.String()
|
||||
type TCPConn struct {
|
||||
N.ExtendedConn
|
||||
metadata TrackerMetadata
|
||||
manager *Manager
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Close() error {
|
||||
func (tt *TCPConn) Metadata() TrackerMetadata {
|
||||
return tt.metadata
|
||||
}
|
||||
|
||||
func (tt *TCPConn) Close() error {
|
||||
tt.manager.Leave(tt)
|
||||
return tt.ExtendedConn.Close()
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Leave() {
|
||||
func (tt *TCPConn) Leave() {
|
||||
tt.manager.Leave(tt)
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) Upstream() any {
|
||||
func (tt *TCPConn) Upstream() any {
|
||||
return tt.ExtendedConn
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) ReaderReplaceable() bool {
|
||||
func (tt *TCPConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (tt *tcpTracker) WriterReplaceable() bool {
|
||||
func (tt *TCPConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *tcpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
|
||||
var chain []string
|
||||
var next string
|
||||
func NewTCPTracker(conn net.Conn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *TCPConn {
|
||||
id, _ := uuid.NewV4()
|
||||
var (
|
||||
chain []string
|
||||
next string
|
||||
outbound string
|
||||
outboundType string
|
||||
)
|
||||
if rule == nil {
|
||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkTCP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
@@ -106,17 +144,17 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
outbound = detour.Tag()
|
||||
outboundType = detour.Type()
|
||||
group, isGroup := detour.(adapter.OutboundGroup)
|
||||
if !isGroup {
|
||||
break
|
||||
}
|
||||
next = group.Now()
|
||||
}
|
||||
|
||||
upload := new(atomic.Int64)
|
||||
download := new(atomic.Int64)
|
||||
|
||||
t := &tcpTracker{
|
||||
tracker := &TCPConn{
|
||||
ExtendedConn: bufio.NewCounterConn(conn, []N.CountFunc{func(n int64) {
|
||||
upload.Add(n)
|
||||
manager.PushUploaded(n)
|
||||
@@ -124,64 +162,62 @@ func NewTCPTracker(conn net.Conn, manager *Manager, metadata Metadata, router ad
|
||||
download.Add(n)
|
||||
manager.PushDownloaded(n)
|
||||
}}),
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: "",
|
||||
UploadTotal: upload,
|
||||
DownloadTotal: download,
|
||||
metadata: TrackerMetadata{
|
||||
ID: id,
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now(),
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: rule,
|
||||
Outbound: outbound,
|
||||
OutboundType: outboundType,
|
||||
},
|
||||
manager: manager,
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
t.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
||||
} else {
|
||||
t.trackerInfo.Rule = "final"
|
||||
}
|
||||
|
||||
manager.Join(t)
|
||||
return t
|
||||
manager.Join(tracker)
|
||||
return tracker
|
||||
}
|
||||
|
||||
type udpTracker struct {
|
||||
type UDPConn struct {
|
||||
N.PacketConn `json:"-"`
|
||||
*trackerInfo
|
||||
manager *Manager
|
||||
metadata TrackerMetadata
|
||||
manager *Manager
|
||||
}
|
||||
|
||||
func (ut *udpTracker) ID() string {
|
||||
return ut.UUID.String()
|
||||
func (ut *UDPConn) Metadata() TrackerMetadata {
|
||||
return ut.metadata
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Close() error {
|
||||
func (ut *UDPConn) Close() error {
|
||||
ut.manager.Leave(ut)
|
||||
return ut.PacketConn.Close()
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Leave() {
|
||||
func (ut *UDPConn) Leave() {
|
||||
ut.manager.Leave(ut)
|
||||
}
|
||||
|
||||
func (ut *udpTracker) Upstream() any {
|
||||
func (ut *UDPConn) Upstream() any {
|
||||
return ut.PacketConn
|
||||
}
|
||||
|
||||
func (ut *udpTracker) ReaderReplaceable() bool {
|
||||
func (ut *UDPConn) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ut *udpTracker) WriterReplaceable() bool {
|
||||
func (ut *UDPConn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, router adapter.Router, rule adapter.Rule) *udpTracker {
|
||||
uuid, _ := uuid.NewV4()
|
||||
|
||||
var chain []string
|
||||
var next string
|
||||
func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata adapter.InboundContext, router adapter.Router, rule adapter.Rule) *UDPConn {
|
||||
id, _ := uuid.NewV4()
|
||||
var (
|
||||
chain []string
|
||||
next string
|
||||
outbound string
|
||||
outboundType string
|
||||
)
|
||||
if rule == nil {
|
||||
if defaultOutbound, err := router.DefaultOutbound(N.NetworkUDP); err == nil {
|
||||
next = defaultOutbound.Tag()
|
||||
@@ -195,17 +231,17 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
||||
if !loaded {
|
||||
break
|
||||
}
|
||||
outbound = detour.Tag()
|
||||
outboundType = detour.Type()
|
||||
group, isGroup := detour.(adapter.OutboundGroup)
|
||||
if !isGroup {
|
||||
break
|
||||
}
|
||||
next = group.Now()
|
||||
}
|
||||
|
||||
upload := new(atomic.Int64)
|
||||
download := new(atomic.Int64)
|
||||
|
||||
ut := &udpTracker{
|
||||
trackerConn := &UDPConn{
|
||||
PacketConn: bufio.NewCounterPacketConn(conn, []N.CountFunc{func(n int64) {
|
||||
upload.Add(n)
|
||||
manager.PushUploaded(n)
|
||||
@@ -213,24 +249,19 @@ func NewUDPTracker(conn N.PacketConn, manager *Manager, metadata Metadata, route
|
||||
download.Add(n)
|
||||
manager.PushDownloaded(n)
|
||||
}}),
|
||||
manager: manager,
|
||||
trackerInfo: &trackerInfo{
|
||||
UUID: uuid,
|
||||
Start: time.Now(),
|
||||
Metadata: metadata,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: "",
|
||||
UploadTotal: upload,
|
||||
DownloadTotal: download,
|
||||
metadata: TrackerMetadata{
|
||||
ID: id,
|
||||
Metadata: metadata,
|
||||
CreatedAt: time.Now(),
|
||||
Upload: upload,
|
||||
Download: download,
|
||||
Chain: common.Reverse(chain),
|
||||
Rule: rule,
|
||||
Outbound: outbound,
|
||||
OutboundType: outboundType,
|
||||
},
|
||||
manager: manager,
|
||||
}
|
||||
|
||||
if rule != nil {
|
||||
ut.trackerInfo.Rule = rule.String() + " => " + rule.Outbound()
|
||||
} else {
|
||||
ut.trackerInfo.Rule = "final"
|
||||
}
|
||||
|
||||
manager.Join(ut)
|
||||
return ut
|
||||
manager.Join(trackerConn)
|
||||
return trackerConn
|
||||
}
|
||||
|
||||
@@ -14,4 +14,6 @@ const (
|
||||
CommandSetClashMode
|
||||
CommandGetSystemProxyStatus
|
||||
CommandSetSystemProxyEnabled
|
||||
CommandConnections
|
||||
CommandCloseConnection
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/experimental/clashapi"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/rw"
|
||||
"github.com/sagernet/sing/common/varbin"
|
||||
)
|
||||
|
||||
func (c *CommandClient) SetClashMode(newMode string) error {
|
||||
@@ -22,7 +22,7 @@ func (c *CommandClient) SetClashMode(newMode string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = rw.WriteVString(conn, newMode)
|
||||
err = varbin.Write(conn, binary.BigEndian, newMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func (c *CommandClient) SetClashMode(newMode string) error {
|
||||
}
|
||||
|
||||
func (s *CommandServer) handleSetClashMode(conn net.Conn) error {
|
||||
newMode, err := rw.ReadVString(conn)
|
||||
newMode, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (c *CommandClient) handleModeConn(conn net.Conn) {
|
||||
defer conn.Close()
|
||||
|
||||
for {
|
||||
newMode, err := rw.ReadVString(conn)
|
||||
newMode, err := varbin.ReadValue[string](conn, binary.BigEndian)
|
||||
if err != nil {
|
||||
c.handler.Disconnected(err.Error())
|
||||
return
|
||||
@@ -80,7 +80,7 @@ func (s *CommandServer) handleModeConn(conn net.Conn) error {
|
||||
for {
|
||||
select {
|
||||
case <-s.modeUpdate:
|
||||
err = rw.WriteVString(conn, clashServer.Mode())
|
||||
err = varbin.Write(conn, binary.BigEndian, clashServer.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,12 +101,12 @@ func readClashModeList(reader io.Reader) (modeList []string, currentMode string,
|
||||
}
|
||||
modeList = make([]string, modeListLength)
|
||||
for i := 0; i < int(modeListLength); i++ {
|
||||
modeList[i], err = rw.ReadVString(reader)
|
||||
modeList[i], err = varbin.ReadValue[string](reader, binary.BigEndian)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
currentMode, err = rw.ReadVString(reader)
|
||||
currentMode, err = varbin.ReadValue[string](reader, binary.BigEndian)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -118,12 +118,12 @@ func writeClashModeList(writer io.Writer, clashServer adapter.ClashServer) error
|
||||
}
|
||||
if len(modeList) > 0 {
|
||||
for _, mode := range modeList {
|
||||
err = rw.WriteVString(writer, mode)
|
||||
err = varbin.Write(writer, binary.BigEndian, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = rw.WriteVString(writer, clashServer.Mode())
|
||||
err = varbin.Write(writer, binary.BigEndian, clashServer.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ type CommandClientOptions struct {
|
||||
type CommandClientHandler interface {
|
||||
Connected()
|
||||
Disconnected(message string)
|
||||
ClearLog()
|
||||
WriteLog(message string)
|
||||
ClearLogs()
|
||||
WriteLogs(messageList StringIterator)
|
||||
WriteStatus(message *StatusMessage)
|
||||
WriteGroups(message OutboundGroupIterator)
|
||||
InitializeClashMode(modeList StringIterator, currentMode string)
|
||||
UpdateClashMode(newMode string)
|
||||
WriteConnections(message *Connections)
|
||||
}
|
||||
|
||||
func NewStandaloneCommandClient() *CommandClient {
|
||||
@@ -83,6 +84,10 @@ func (c *CommandClient) Connect() error {
|
||||
}
|
||||
switch c.options.Command {
|
||||
case CommandLog:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleLogConn(conn)
|
||||
case CommandStatus:
|
||||
@@ -116,6 +121,13 @@ func (c *CommandClient) Connect() error {
|
||||
return nil
|
||||
}
|
||||
go c.handleModeConn(conn)
|
||||
case CommandConnections:
|
||||
err = binary.Write(conn, binary.BigEndian, c.options.StatusInterval)
|
||||
if err != nil {
|
||||
return E.Cause(err, "write interval")
|
||||
}
|
||||
c.handler.Connected()
|
||||
go c.handleConnectionsConn(conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user