mirror of
https://github.com/SagerNet/sing-box.git
synced 2026-04-13 20:28:32 +10:00
Compare commits
80 Commits
dev-dns-ti
...
v1.12.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fc9dfa9d2 | ||
|
|
48349b209d | ||
|
|
df0d1fd512 | ||
|
|
efab4bdf05 | ||
|
|
8c0d813f34 | ||
|
|
271a12431b | ||
|
|
432f282d7f | ||
|
|
40ba77ff4e | ||
|
|
d13f159753 | ||
|
|
7578bf6425 | ||
|
|
bb785f4450 | ||
|
|
40aa64fe8b | ||
|
|
5591ad5348 | ||
|
|
2dab16bc63 | ||
|
|
1cbf5db517 | ||
|
|
6be4881368 | ||
|
|
0332ae7b63 | ||
|
|
17c0494d58 | ||
|
|
123b0c949b | ||
|
|
ced5623af1 | ||
|
|
06562c9dd9 | ||
|
|
3a038c8b05 | ||
|
|
dba42bce9e | ||
|
|
be91df61c9 | ||
|
|
3f46bfc2ca | ||
|
|
8afb6108c0 | ||
|
|
850e37d1e5 | ||
|
|
a6e30281e1 | ||
|
|
12c14f9ba3 | ||
|
|
2bf9dcbc0e | ||
|
|
c83933c856 | ||
|
|
10da6d6c8e | ||
|
|
008a012a08 | ||
|
|
cc39fc7701 | ||
|
|
b4c75cdcb2 | ||
|
|
8f1e6ae1b5 | ||
|
|
36167419ff | ||
|
|
d8d2ed635f | ||
|
|
50541a9785 | ||
|
|
2ddece2965 | ||
|
|
5f8bd97653 | ||
|
|
19e19085dc | ||
|
|
1e1e204c42 | ||
|
|
3a41f3a553 | ||
|
|
dff5c7a1e9 | ||
|
|
cedbdcb509 | ||
|
|
42b86b1a63 | ||
|
|
eca8348171 | ||
|
|
585ed4be52 | ||
|
|
31030787db | ||
|
|
dd62c3f1f1 | ||
|
|
901a986b8e | ||
|
|
367d486220 | ||
|
|
2934d440d0 | ||
|
|
5c137f3d99 | ||
|
|
a3ce614384 | ||
|
|
3b73b7a107 | ||
|
|
547d596448 | ||
|
|
552183de63 | ||
|
|
254138968c | ||
|
|
42e47aca83 | ||
|
|
8ad35513f0 | ||
|
|
d446e4f9fd | ||
|
|
be7b73d112 | ||
|
|
60632fc906 | ||
|
|
3efc7f6cfc | ||
|
|
6d180e1023 | ||
|
|
fd536ad2f2 | ||
|
|
1d14aaba76 | ||
|
|
271c8cb02c | ||
|
|
6b19bda397 | ||
|
|
01e00a506e | ||
|
|
5e09417569 | ||
|
|
ec752973ea | ||
|
|
a31592a04c | ||
|
|
d917a9ff4f | ||
|
|
85ce9157ca | ||
|
|
948973bad1 | ||
|
|
5c5f6d2e17 | ||
|
|
a1095f8332 |
@@ -8,15 +8,11 @@
|
|||||||
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
|
||||||
--no-deb-generate-changes
|
--no-deb-generate-changes
|
||||||
--config-files /etc/sing-box/config.json
|
--config-files /etc/sing-box/config.json
|
||||||
--after-install release/config/sing-box.postinst
|
|
||||||
|
|
||||||
release/config/config.json=/etc/sing-box/config.json
|
release/config/config.json=/etc/sing-box/config.json
|
||||||
|
|
||||||
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
release/config/sing-box.service=/usr/lib/systemd/system/sing-box.service
|
||||||
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
release/config/sing-box@.service=/usr/lib/systemd/system/sing-box@.service
|
||||||
release/config/sing-box.sysusers=/usr/lib/sysusers.d/sing-box.conf
|
|
||||||
release/config/sing-box.rules=usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
release/config/sing-box-split-dns.xml=/usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
release/completions/sing-box.bash=/usr/share/bash-completion/completions/sing-box.bash
|
||||||
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
release/completions/sing-box.fish=/usr/share/fish/vendor_completions.d/sing-box.fish
|
||||||
|
|||||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -476,11 +476,11 @@ jobs:
|
|||||||
- name: Setup Xcode stable
|
- name: Setup Xcode stable
|
||||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Setup Xcode beta
|
- name: Setup Xcode beta
|
||||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||||
run: |-
|
run: |-
|
||||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||||
- name: Set tag
|
- name: Set tag
|
||||||
if: matrix.if
|
if: matrix.if
|
||||||
run: |-
|
run: |-
|
||||||
@@ -615,7 +615,7 @@ jobs:
|
|||||||
path: 'dist'
|
path: 'dist'
|
||||||
upload:
|
upload:
|
||||||
name: Upload builds
|
name: Upload builds
|
||||||
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')"
|
if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs:
|
needs:
|
||||||
- calculate_version
|
- calculate_version
|
||||||
|
|||||||
2
.github/workflows/linux.yml
vendored
2
.github/workflows/linux.yml
vendored
@@ -80,7 +80,7 @@ jobs:
|
|||||||
- name: Set build tags
|
- name: Set build tags
|
||||||
run: |
|
run: |
|
||||||
set -xeuo pipefail
|
set -xeuo pipefail
|
||||||
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale'
|
TAGS='with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api'
|
||||||
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
echo "BUILD_TAGS=${TAGS}" >> "${GITHUB_ENV}"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -55,12 +55,6 @@ nfpms:
|
|||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
- src: release/config/sing-box.sysusers
|
|
||||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
|
||||||
- src: release/config/sing-box.rules
|
|
||||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
- src: release/config/sing-box-split-dns.xml
|
|
||||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
- src: release/completions/sing-box.bash
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
|||||||
@@ -136,12 +136,6 @@ nfpms:
|
|||||||
dst: /usr/lib/systemd/system/sing-box.service
|
dst: /usr/lib/systemd/system/sing-box.service
|
||||||
- src: release/config/sing-box@.service
|
- src: release/config/sing-box@.service
|
||||||
dst: /usr/lib/systemd/system/sing-box@.service
|
dst: /usr/lib/systemd/system/sing-box@.service
|
||||||
- src: release/config/sing-box.sysusers
|
|
||||||
dst: /usr/lib/sysusers.d/sing-box.conf
|
|
||||||
- src: release/config/sing-box.rules
|
|
||||||
dst: /usr/share/polkit-1/rules.d/sing-box.rules
|
|
||||||
- src: release/config/sing-box-split-dns.xml
|
|
||||||
dst: /usr/share/dbus-1/system.d/sing-box-split-dns.conf
|
|
||||||
|
|
||||||
- src: release/completions/sing-box.bash
|
- src: release/completions/sing-box.bash
|
||||||
dst: /usr/share/bash-completion/completions/sing-box.bash
|
dst: /usr/share/bash-completion/completions/sing-box.bash
|
||||||
|
|||||||
25
Makefile
25
Makefile
@@ -1,10 +1,11 @@
|
|||||||
NAME = sing-box
|
NAME = sing-box
|
||||||
COMMIT = $(shell git rev-parse --short HEAD)
|
COMMIT = $(shell git rev-parse --short HEAD)
|
||||||
TAGS ?= with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale
|
TAGS ?= with_gvisor,with_dhcp,with_wireguard,with_clash_api,with_quic,with_utls,with_tailscale
|
||||||
|
TAGS_TEST ?= with_gvisor,with_quic,with_wireguard,with_grpc,with_utls
|
||||||
|
|
||||||
GOHOSTOS = $(shell go env GOHOSTOS)
|
GOHOSTOS = $(shell go env GOHOSTOS)
|
||||||
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
GOHOSTARCH = $(shell go env GOHOSTARCH)
|
||||||
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest)
|
VERSION=$(shell CGO_ENABLED=0 GOOS=$(GOHOSTOS) GOARCH=$(GOHOSTARCH) go run ./cmd/internal/read_tag)
|
||||||
|
|
||||||
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
PARAMS = -v -trimpath -ldflags "-X 'github.com/sagernet/sing-box/constant.Version=$(VERSION)' -s -w -buildid="
|
||||||
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
|
||||||
@@ -108,16 +109,6 @@ upload_ios_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_ios_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
|
||||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
|
||||||
|
|
||||||
upload_ios_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_ios: build_ios upload_ios_app_store
|
release_ios: build_ios upload_ios_app_store
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
@@ -185,16 +176,6 @@ upload_tvos_app_store:
|
|||||||
cd ../sing-box-for-apple && \
|
cd ../sing-box-for-apple && \
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||||
|
|
||||||
export_tvos_ipa:
|
|
||||||
cd ../sing-box-for-apple && \
|
|
||||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
|
||||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
|
||||||
|
|
||||||
upload_tvos_ipa:
|
|
||||||
cd dist && \
|
|
||||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
|
||||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
|
||||||
|
|
||||||
release_tvos: build_tvos upload_tvos_app_store
|
release_tvos: build_tvos upload_tvos_app_store
|
||||||
|
|
||||||
update_apple_version:
|
update_apple_version:
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ package adapter
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"time"
|
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/logger"
|
"github.com/sagernet/sing/common/logger"
|
||||||
"github.com/sagernet/sing/service"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
@@ -34,32 +31,11 @@ type DNSClient interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSQueryOptions struct {
|
type DNSQueryOptions struct {
|
||||||
Transport DNSTransport
|
Transport DNSTransport
|
||||||
Strategy C.DomainStrategy
|
Strategy C.DomainStrategy
|
||||||
LookupStrategy C.DomainStrategy
|
DisableCache bool
|
||||||
Timeout time.Duration
|
RewriteTTL *uint32
|
||||||
DisableCache bool
|
ClientSubnet netip.Prefix
|
||||||
RewriteTTL *uint32
|
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptions) (*DNSQueryOptions, error) {
|
|
||||||
if options == nil {
|
|
||||||
return &DNSQueryOptions{}, nil
|
|
||||||
}
|
|
||||||
transportManager := service.FromContext[DNSTransportManager](ctx)
|
|
||||||
transport, loaded := transportManager.Transport(options.Server)
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("domain resolver not found: " + options.Server)
|
|
||||||
}
|
|
||||||
return &DNSQueryOptions{
|
|
||||||
Transport: transport,
|
|
||||||
Strategy: C.DomainStrategy(options.Strategy),
|
|
||||||
Timeout: time.Duration(options.Timeout),
|
|
||||||
DisableCache: options.DisableCache,
|
|
||||||
RewriteTTL: options.RewriteTTL,
|
|
||||||
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RDRCStore interface {
|
type RDRCStore interface {
|
||||||
@@ -73,7 +49,6 @@ type DNSTransport interface {
|
|||||||
Type() string
|
Type() string
|
||||||
Tag() string
|
Tag() string
|
||||||
Dependencies() []string
|
Dependencies() []string
|
||||||
HasDetour() bool
|
|
||||||
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FakeIPStore interface {
|
type FakeIPStore interface {
|
||||||
SimpleLifecycle
|
Service
|
||||||
Contains(address netip.Addr) bool
|
Contains(address netip.Addr) bool
|
||||||
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
Create(domain string, isIPv6 bool) (netip.Addr, error)
|
||||||
Lookup(address netip.Addr) (string, bool)
|
Lookup(address netip.Addr) (string, bool)
|
||||||
|
|||||||
@@ -53,11 +53,11 @@ type InboundContext struct {
|
|||||||
|
|
||||||
// sniffer
|
// sniffer
|
||||||
|
|
||||||
Protocol string
|
Protocol string
|
||||||
Domain string
|
Domain string
|
||||||
Client string
|
Client string
|
||||||
SniffContext any
|
SniffContext any
|
||||||
SniffError error
|
PacketSniffError error
|
||||||
|
|
||||||
// cache
|
// cache
|
||||||
|
|
||||||
@@ -74,7 +74,6 @@ type InboundContext struct {
|
|||||||
UDPTimeout time.Duration
|
UDPTimeout time.Duration
|
||||||
TLSFragment bool
|
TLSFragment bool
|
||||||
TLSFragmentFallbackDelay time.Duration
|
TLSFragmentFallbackDelay time.Duration
|
||||||
TLSRecordFragment bool
|
|
||||||
|
|
||||||
NetworkStrategy *C.NetworkStrategy
|
NetworkStrategy *C.NetworkStrategy
|
||||||
NetworkType []C.InterfaceType
|
NetworkType []C.InterfaceType
|
||||||
|
|||||||
@@ -37,14 +37,13 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
|
|||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
func (m *Manager) Start(stage adapter.StartStage) error {
|
||||||
m.access.Lock()
|
m.access.Lock()
|
||||||
|
defer m.access.Unlock()
|
||||||
if m.started && m.stage >= stage {
|
if m.started && m.stage >= stage {
|
||||||
panic("already started")
|
panic("already started")
|
||||||
}
|
}
|
||||||
m.started = true
|
m.started = true
|
||||||
m.stage = stage
|
m.stage = stage
|
||||||
inbounds := m.inbounds
|
for _, inbound := range m.inbounds {
|
||||||
m.access.Unlock()
|
|
||||||
for _, inbound := range inbounds {
|
|
||||||
err := adapter.LegacyStart(inbound, stage)
|
err := adapter.LegacyStart(inbound, stage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ package adapter
|
|||||||
|
|
||||||
import E "github.com/sagernet/sing/common/exceptions"
|
import E "github.com/sagernet/sing/common/exceptions"
|
||||||
|
|
||||||
type SimpleLifecycle interface {
|
|
||||||
Start() error
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type StartStage uint8
|
type StartStage uint8
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lifecycleServiceWrapper struct {
|
type lifecycleServiceWrapper struct {
|
||||||
SimpleLifecycle
|
Service
|
||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
|
func NewLifecycleService(service Service, name string) LifecycleService {
|
||||||
return &lifecycleServiceWrapper{
|
return &lifecycleServiceWrapper{
|
||||||
SimpleLifecycle: service,
|
Service: service,
|
||||||
name: name,
|
name: name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
|
||||||
return LegacyStart(l.SimpleLifecycle, stage)
|
return LegacyStart(l.Service, stage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *lifecycleServiceWrapper) Close() error {
|
func (l *lifecycleServiceWrapper) Close() error {
|
||||||
return l.SimpleLifecycle.Close()
|
return l.Service.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type HeadlessRule interface {
|
|||||||
|
|
||||||
type Rule interface {
|
type Rule interface {
|
||||||
HeadlessRule
|
HeadlessRule
|
||||||
SimpleLifecycle
|
Service
|
||||||
Type() string
|
Type() string
|
||||||
Action() RuleAction
|
Action() RuleAction
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,6 @@
|
|||||||
package adapter
|
package adapter
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
Lifecycle
|
Start() error
|
||||||
Type() string
|
Close() error
|
||||||
Tag() string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceRegistry interface {
|
|
||||||
option.ServiceOptionsRegistry
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) (Service, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceManager interface {
|
|
||||||
Lifecycle
|
|
||||||
Services() []Service
|
|
||||||
Get(tag string) (Service, bool)
|
|
||||||
Remove(tag string) error
|
|
||||||
Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
type Adapter struct {
|
|
||||||
serviceType string
|
|
||||||
serviceTag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdapter(serviceType string, serviceTag string) Adapter {
|
|
||||||
return Adapter{
|
|
||||||
serviceType: serviceType,
|
|
||||||
serviceTag: serviceTag,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Type() string {
|
|
||||||
return a.serviceType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Adapter) Tag() string {
|
|
||||||
return a.serviceTag
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ adapter.ServiceManager = (*Manager)(nil)
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
logger log.ContextLogger
|
|
||||||
registry adapter.ServiceRegistry
|
|
||||||
access sync.Mutex
|
|
||||||
started bool
|
|
||||||
stage adapter.StartStage
|
|
||||||
services []adapter.Service
|
|
||||||
serviceByTag map[string]adapter.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewManager(logger log.ContextLogger, registry adapter.ServiceRegistry) *Manager {
|
|
||||||
return &Manager{
|
|
||||||
logger: logger,
|
|
||||||
registry: registry,
|
|
||||||
serviceByTag: make(map[string]adapter.Service),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Start(stage adapter.StartStage) error {
|
|
||||||
m.access.Lock()
|
|
||||||
if m.started && m.stage >= stage {
|
|
||||||
panic("already started")
|
|
||||||
}
|
|
||||||
m.started = true
|
|
||||||
m.stage = stage
|
|
||||||
services := m.services
|
|
||||||
m.access.Unlock()
|
|
||||||
for _, service := range services {
|
|
||||||
err := adapter.LegacyStart(service, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Close() error {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if !m.started {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
m.started = false
|
|
||||||
services := m.services
|
|
||||||
m.services = nil
|
|
||||||
monitor := taskmonitor.New(m.logger, C.StopTimeout)
|
|
||||||
var err error
|
|
||||||
for _, service := range services {
|
|
||||||
monitor.Start("close service/", service.Type(), "[", service.Tag(), "]")
|
|
||||||
err = E.Append(err, service.Close(), func(err error) error {
|
|
||||||
return E.Cause(err, "close service/", service.Type(), "[", service.Tag(), "]")
|
|
||||||
})
|
|
||||||
monitor.Finish()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Services() []adapter.Service {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
return m.services
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Get(tag string) (adapter.Service, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
service, found := m.serviceByTag[tag]
|
|
||||||
m.access.Unlock()
|
|
||||||
return service, found
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Remove(tag string) error {
|
|
||||||
m.access.Lock()
|
|
||||||
service, found := m.serviceByTag[tag]
|
|
||||||
if !found {
|
|
||||||
m.access.Unlock()
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(m.serviceByTag, tag)
|
|
||||||
index := common.Index(m.services, func(it adapter.Service) bool {
|
|
||||||
return it == service
|
|
||||||
})
|
|
||||||
if index == -1 {
|
|
||||||
panic("invalid service index")
|
|
||||||
}
|
|
||||||
m.services = append(m.services[:index], m.services[index+1:]...)
|
|
||||||
started := m.started
|
|
||||||
m.access.Unlock()
|
|
||||||
if started {
|
|
||||||
return service.Close()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Manager) Create(ctx context.Context, logger log.ContextLogger, tag string, serviceType string, options any) error {
|
|
||||||
service, err := m.registry.Create(ctx, logger, tag, serviceType, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
if m.started {
|
|
||||||
for _, stage := range adapter.ListStartStages {
|
|
||||||
err = adapter.LegacyStart(service, stage)
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, stage, " service/", service.Type(), "[", service.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if existsService, loaded := m.serviceByTag[tag]; loaded {
|
|
||||||
if m.started {
|
|
||||||
err = existsService.Close()
|
|
||||||
if err != nil {
|
|
||||||
return E.Cause(err, "close service/", existsService.Type(), "[", existsService.Tag(), "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
existsIndex := common.Index(m.services, func(it adapter.Service) bool {
|
|
||||||
return it == existsService
|
|
||||||
})
|
|
||||||
if existsIndex == -1 {
|
|
||||||
panic("invalid service index")
|
|
||||||
}
|
|
||||||
m.services = append(m.services[:existsIndex], m.services[existsIndex+1:]...)
|
|
||||||
}
|
|
||||||
m.services = append(m.services, service)
|
|
||||||
m.serviceByTag[tag] = service
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
|
||||||
"github.com/sagernet/sing-box/log"
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConstructorFunc[T any] func(ctx context.Context, logger log.ContextLogger, tag string, options T) (adapter.Service, error)
|
|
||||||
|
|
||||||
func Register[Options any](registry *Registry, outboundType string, constructor ConstructorFunc[Options]) {
|
|
||||||
registry.register(outboundType, func() any {
|
|
||||||
return new(Options)
|
|
||||||
}, func(ctx context.Context, logger log.ContextLogger, tag string, rawOptions any) (adapter.Service, error) {
|
|
||||||
var options *Options
|
|
||||||
if rawOptions != nil {
|
|
||||||
options = rawOptions.(*Options)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, common.PtrValueOrDefault(options))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ adapter.ServiceRegistry = (*Registry)(nil)
|
|
||||||
|
|
||||||
type (
|
|
||||||
optionsConstructorFunc func() any
|
|
||||||
constructorFunc func(ctx context.Context, logger log.ContextLogger, tag string, options any) (adapter.Service, error)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Registry struct {
|
|
||||||
access sync.Mutex
|
|
||||||
optionsType map[string]optionsConstructorFunc
|
|
||||||
constructor map[string]constructorFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRegistry() *Registry {
|
|
||||||
return &Registry{
|
|
||||||
optionsType: make(map[string]optionsConstructorFunc),
|
|
||||||
constructor: make(map[string]constructorFunc),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) CreateOptions(outboundType string) (any, bool) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
optionsConstructor, loaded := m.optionsType[outboundType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return optionsConstructor(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) Create(ctx context.Context, logger log.ContextLogger, tag string, outboundType string, options any) (adapter.Service, error) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
constructor, loaded := m.constructor[outboundType]
|
|
||||||
if !loaded {
|
|
||||||
return nil, E.New("outbound type not found: " + outboundType)
|
|
||||||
}
|
|
||||||
return constructor(ctx, logger, tag, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Registry) register(outboundType string, optionsConstructor optionsConstructorFunc, constructor constructorFunc) {
|
|
||||||
m.access.Lock()
|
|
||||||
defer m.access.Unlock()
|
|
||||||
m.optionsType[outboundType] = optionsConstructor
|
|
||||||
m.constructor[outboundType] = constructor
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package adapter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ManagedSSMServer interface {
|
|
||||||
Inbound
|
|
||||||
SetTracker(tracker SSMTracker)
|
|
||||||
UpdateUsers(users []string, uPSKs []string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type SSMTracker interface {
|
|
||||||
TrackConnection(conn net.Conn, metadata InboundContext) net.Conn
|
|
||||||
TrackPacketConnection(conn N.PacketConn, metadata InboundContext) N.PacketConn
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,6 @@ package adapter
|
|||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type TimeService interface {
|
type TimeService interface {
|
||||||
SimpleLifecycle
|
Service
|
||||||
TimeFunc() func() time.Time
|
TimeFunc() func() time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
125
box.go
125
box.go
@@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
boxService "github.com/sagernet/sing-box/adapter/service"
|
|
||||||
"github.com/sagernet/sing-box/common/certificate"
|
"github.com/sagernet/sing-box/common/certificate"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
"github.com/sagernet/sing-box/common/taskmonitor"
|
"github.com/sagernet/sing-box/common/taskmonitor"
|
||||||
@@ -35,23 +34,22 @@ import (
|
|||||||
"github.com/sagernet/sing/service/pause"
|
"github.com/sagernet/sing/service/pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ adapter.SimpleLifecycle = (*Box)(nil)
|
var _ adapter.Service = (*Box)(nil)
|
||||||
|
|
||||||
type Box struct {
|
type Box struct {
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
logFactory log.Factory
|
logFactory log.Factory
|
||||||
logger log.ContextLogger
|
logger log.ContextLogger
|
||||||
network *route.NetworkManager
|
network *route.NetworkManager
|
||||||
endpoint *endpoint.Manager
|
endpoint *endpoint.Manager
|
||||||
inbound *inbound.Manager
|
inbound *inbound.Manager
|
||||||
outbound *outbound.Manager
|
outbound *outbound.Manager
|
||||||
service *boxService.Manager
|
dnsTransport *dns.TransportManager
|
||||||
dnsTransport *dns.TransportManager
|
dnsRouter *dns.Router
|
||||||
dnsRouter *dns.Router
|
connection *route.ConnectionManager
|
||||||
connection *route.ConnectionManager
|
router *route.Router
|
||||||
router *route.Router
|
services []adapter.LifecycleService
|
||||||
internalService []adapter.LifecycleService
|
done chan struct{}
|
||||||
done chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
@@ -66,7 +64,6 @@ func Context(
|
|||||||
outboundRegistry adapter.OutboundRegistry,
|
outboundRegistry adapter.OutboundRegistry,
|
||||||
endpointRegistry adapter.EndpointRegistry,
|
endpointRegistry adapter.EndpointRegistry,
|
||||||
dnsTransportRegistry adapter.DNSTransportRegistry,
|
dnsTransportRegistry adapter.DNSTransportRegistry,
|
||||||
serviceRegistry adapter.ServiceRegistry,
|
|
||||||
) context.Context {
|
) context.Context {
|
||||||
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
|
||||||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
service.FromContext[adapter.InboundRegistry](ctx) == nil {
|
||||||
@@ -87,10 +84,6 @@ func Context(
|
|||||||
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry)
|
||||||
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry)
|
||||||
}
|
}
|
||||||
if service.FromContext[adapter.ServiceRegistry](ctx) == nil {
|
|
||||||
ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry)
|
|
||||||
ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry)
|
|
||||||
}
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +99,6 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
|
||||||
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
|
||||||
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
|
||||||
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
|
|
||||||
|
|
||||||
if endpointRegistry == nil {
|
if endpointRegistry == nil {
|
||||||
return nil, E.New("missing endpoint registry in context")
|
return nil, E.New("missing endpoint registry in context")
|
||||||
@@ -117,12 +109,6 @@ func New(options Options) (*Box, error) {
|
|||||||
if outboundRegistry == nil {
|
if outboundRegistry == nil {
|
||||||
return nil, E.New("missing outbound registry in context")
|
return nil, E.New("missing outbound registry in context")
|
||||||
}
|
}
|
||||||
if dnsTransportRegistry == nil {
|
|
||||||
return nil, E.New("missing DNS transport registry in context")
|
|
||||||
}
|
|
||||||
if serviceRegistry == nil {
|
|
||||||
return nil, E.New("missing service registry in context")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = pause.WithDefaultManager(ctx)
|
ctx = pause.WithDefaultManager(ctx)
|
||||||
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
|
||||||
@@ -156,7 +142,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "create log factory")
|
return nil, E.Cause(err, "create log factory")
|
||||||
}
|
}
|
||||||
|
|
||||||
var internalServices []adapter.LifecycleService
|
var services []adapter.LifecycleService
|
||||||
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
certificateOptions := common.PtrValueOrDefault(options.Certificate)
|
||||||
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
|
||||||
len(certificateOptions.Certificate) > 0 ||
|
len(certificateOptions.Certificate) > 0 ||
|
||||||
@@ -167,7 +153,7 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
|
||||||
internalServices = append(internalServices, certificateStore)
|
services = append(services, certificateStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
routeOptions := common.PtrValueOrDefault(options.Route)
|
routeOptions := common.PtrValueOrDefault(options.Route)
|
||||||
@@ -176,12 +162,10 @@ func New(options Options) (*Box, error) {
|
|||||||
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
|
||||||
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final)
|
||||||
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final)
|
||||||
serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry)
|
|
||||||
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
service.MustRegister[adapter.EndpointManager](ctx, endpointManager)
|
||||||
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
service.MustRegister[adapter.InboundManager](ctx, inboundManager)
|
||||||
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
|
||||||
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
|
||||||
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
|
|
||||||
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
|
||||||
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
|
||||||
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions)
|
||||||
@@ -296,24 +280,6 @@ func New(options Options) (*Box, error) {
|
|||||||
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
return nil, E.Cause(err, "initialize outbound[", i, "]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i, serviceOptions := range options.Services {
|
|
||||||
var tag string
|
|
||||||
if serviceOptions.Tag != "" {
|
|
||||||
tag = serviceOptions.Tag
|
|
||||||
} else {
|
|
||||||
tag = F.ToString(i)
|
|
||||||
}
|
|
||||||
err = serviceManager.Create(
|
|
||||||
ctx,
|
|
||||||
logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")),
|
|
||||||
tag,
|
|
||||||
serviceOptions.Type,
|
|
||||||
serviceOptions.Options,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, E.Cause(err, "initialize service[", i, "]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
outboundManager.Initialize(common.Must1(
|
outboundManager.Initialize(common.Must1(
|
||||||
direct.NewOutbound(
|
direct.NewOutbound(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -339,7 +305,7 @@ func New(options Options) (*Box, error) {
|
|||||||
if needCacheFile {
|
if needCacheFile {
|
||||||
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
|
||||||
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
|
||||||
internalServices = append(internalServices, cacheFile)
|
services = append(services, cacheFile)
|
||||||
}
|
}
|
||||||
if needClashAPI {
|
if needClashAPI {
|
||||||
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
|
||||||
@@ -350,7 +316,7 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
router.AppendTracker(clashServer)
|
router.AppendTracker(clashServer)
|
||||||
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
service.MustRegister[adapter.ClashServer](ctx, clashServer)
|
||||||
internalServices = append(internalServices, clashServer)
|
services = append(services, clashServer)
|
||||||
}
|
}
|
||||||
if needV2RayAPI {
|
if needV2RayAPI {
|
||||||
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI))
|
||||||
@@ -359,7 +325,7 @@ func New(options Options) (*Box, error) {
|
|||||||
}
|
}
|
||||||
if v2rayServer.StatsService() != nil {
|
if v2rayServer.StatsService() != nil {
|
||||||
router.AppendTracker(v2rayServer.StatsService())
|
router.AppendTracker(v2rayServer.StatsService())
|
||||||
internalServices = append(internalServices, v2rayServer)
|
services = append(services, v2rayServer)
|
||||||
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,23 +343,22 @@ func New(options Options) (*Box, error) {
|
|||||||
WriteToSystem: ntpOptions.WriteToSystem,
|
WriteToSystem: ntpOptions.WriteToSystem,
|
||||||
})
|
})
|
||||||
timeService.TimeService = ntpService
|
timeService.TimeService = ntpService
|
||||||
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
|
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
|
||||||
}
|
}
|
||||||
return &Box{
|
return &Box{
|
||||||
network: networkManager,
|
network: networkManager,
|
||||||
endpoint: endpointManager,
|
endpoint: endpointManager,
|
||||||
inbound: inboundManager,
|
inbound: inboundManager,
|
||||||
outbound: outboundManager,
|
outbound: outboundManager,
|
||||||
dnsTransport: dnsTransportManager,
|
dnsTransport: dnsTransportManager,
|
||||||
service: serviceManager,
|
dnsRouter: dnsRouter,
|
||||||
dnsRouter: dnsRouter,
|
connection: connectionManager,
|
||||||
connection: connectionManager,
|
router: router,
|
||||||
router: router,
|
createdAt: createdAt,
|
||||||
createdAt: createdAt,
|
logFactory: logFactory,
|
||||||
logFactory: logFactory,
|
logger: logFactory.Logger(),
|
||||||
logger: logFactory.Logger(),
|
services: services,
|
||||||
internalService: internalServices,
|
done: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,11 +408,11 @@ func (s *Box) preStart() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return E.Cause(err, "start logger")
|
return E.Cause(err, "start logger")
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api
|
err = adapter.StartNamed(adapter.StartStateInitialize, s.services) // cache-file clash-api v2ray-api
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -463,27 +428,31 @@ func (s *Box) start() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
|
err = adapter.StartNamed(adapter.StartStateStart, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
|
err = s.inbound.Start(adapter.StartStateStart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint, s.service)
|
err = adapter.Start(adapter.StartStateStart, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStatePostStart, s.internalService)
|
err = adapter.Start(adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.inbound, s.endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service)
|
err = adapter.StartNamed(adapter.StartStatePostStart, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = adapter.StartNamed(adapter.StartStateStarted, s.internalService)
|
err = adapter.Start(adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = adapter.StartNamed(adapter.StartStateStarted, s.services)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -498,9 +467,9 @@ func (s *Box) Close() error {
|
|||||||
close(s.done)
|
close(s.done)
|
||||||
}
|
}
|
||||||
err := common.Close(
|
err := common.Close(
|
||||||
s.service, s.endpoint, s.inbound, s.outbound, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
s.inbound, s.outbound, s.endpoint, s.router, s.connection, s.dnsRouter, s.dnsTransport, s.network,
|
||||||
)
|
)
|
||||||
for _, lifecycleService := range s.internalService {
|
for _, lifecycleService := range s.services {
|
||||||
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
err = E.Append(err, lifecycleService.Close(), func(err error) error {
|
||||||
return E.Cause(err, "close ", lifecycleService.Name())
|
return E.Cause(err, "close ", lifecycleService.Name())
|
||||||
})
|
})
|
||||||
|
|||||||
Submodule clients/android updated: eb2e13a6f9...9dd336679d
@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tag := tagVersion.VersionString()
|
tag := tagVersion.VersionString()
|
||||||
client := createClient(20 * time.Minute)
|
client := createClient(10 * time.Minute)
|
||||||
|
|
||||||
log.Info(tag, " list build IDs")
|
log.Info(tag, " list build IDs")
|
||||||
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
|
||||||
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
build := builds.Data[0]
|
build := builds.Data[0]
|
||||||
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 30*time.Minute {
|
if common.Contains(buildIDs, build.ID) || time.Since(build.Attributes.UploadedDate.Time) > 5*time.Minute {
|
||||||
log.Info(string(platform), " ", tag, " waiting for process")
|
log.Info(string(platform), " ", tag, " waiting for process")
|
||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -16,17 +16,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
debugEnabled bool
|
debugEnabled bool
|
||||||
target string
|
target string
|
||||||
platform string
|
platform string
|
||||||
withTailscale bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||||
flag.StringVar(&target, "target", "android", "target platform")
|
flag.StringVar(&target, "target", "android", "target platform")
|
||||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -61,8 +59,8 @@ func init() {
|
|||||||
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
sharedFlags = append(sharedFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag+" -s -w -buildid=")
|
||||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||||
|
|
||||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
|
||||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
|
||||||
memcTags = append(memcTags, "with_tailscale")
|
memcTags = append(memcTags, "with_tailscale")
|
||||||
debugTags = append(debugTags, "debug")
|
debugTags = append(debugTags, "debug")
|
||||||
}
|
}
|
||||||
@@ -153,9 +151,7 @@ func buildApple() {
|
|||||||
"-v",
|
"-v",
|
||||||
"-target", bindTarget,
|
"-target", bindTarget,
|
||||||
"-libname=box",
|
"-libname=box",
|
||||||
}
|
"-tags-macos=" + strings.Join(memcTags, ","),
|
||||||
if !withTailscale {
|
|
||||||
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !debugEnabled {
|
if !debugEnabled {
|
||||||
@@ -165,9 +161,6 @@ func buildApple() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tags := append(sharedTags, iosTags...)
|
tags := append(sharedTags, iosTags...)
|
||||||
if withTailscale {
|
|
||||||
tags = append(tags, memcTags...)
|
|
||||||
}
|
|
||||||
if debugEnabled {
|
if debugEnabled {
|
||||||
tags = append(tags, debugTags...)
|
tags = append(tags, debugTags...)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing-box"
|
||||||
"github.com/sagernet/sing-box/experimental/deprecated"
|
"github.com/sagernet/sing-box/experimental/deprecated"
|
||||||
"github.com/sagernet/sing-box/include"
|
"github.com/sagernet/sing-box/include"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -67,5 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
|
|||||||
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
if len(configPaths) == 0 && len(configDirectories) == 0 {
|
||||||
configPaths = append(configPaths, "config.json")
|
configPaths = append(configPaths, "config.json")
|
||||||
}
|
}
|
||||||
globalCtx = include.Context(service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger())))
|
globalCtx = service.ContextWith(globalCtx, deprecated.NewStderrManager(log.StdLogger()))
|
||||||
|
globalCtx = box.Context(globalCtx, include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), include.DNSTransportRegistry())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/convertor/adguard"
|
"github.com/sagernet/sing-box/cmd/sing-box/internal/convertor/adguard"
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"github.com/sagernet/sing-box/common/srs"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
|||||||
var rules []option.HeadlessRule
|
var rules []option.HeadlessRule
|
||||||
switch flagRuleSetConvertType {
|
switch flagRuleSetConvertType {
|
||||||
case "adguard":
|
case "adguard":
|
||||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
rules, err = adguard.Convert(reader)
|
||||||
case "":
|
case "":
|
||||||
return E.New("source type is required")
|
return E.New("source type is required")
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/common/srs"
|
"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/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
"github.com/sagernet/sing/common/json"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
|
||||||
return len(rule.AdGuardDomain) > 0
|
|
||||||
}) {
|
|
||||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
|
||||||
}
|
|
||||||
var outputPath string
|
var outputPath string
|
||||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||||
if strings.HasSuffix(sourcePath, ".srs") {
|
if strings.HasSuffix(sourcePath, ".srs") {
|
||||||
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
|
|||||||
outputFile.Close()
|
outputFile.Close()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
|
||||||
for _, rule := range rules {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
if cond(rule.DefaultOptions) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package adguard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
@@ -10,10 +9,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
"github.com/sagernet/sing/common"
|
"github.com/sagernet/sing/common"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
|
|||||||
isImportant bool
|
isImportant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) {
|
func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||||
scanner := bufio.NewScanner(reader)
|
scanner := bufio.NewScanner(reader)
|
||||||
var (
|
var (
|
||||||
ruleLines []agdguardRuleLine
|
ruleLines []agdguardRuleLine
|
||||||
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
|
|||||||
parseLine:
|
parseLine:
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
ruleLine := scanner.Text()
|
ruleLine := scanner.Text()
|
||||||
if ruleLine == "" {
|
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
originRuleLine := ruleLine
|
originRuleLine := ruleLine
|
||||||
@@ -96,7 +92,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if !ignored {
|
if !ignored {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||||
continue parseLine
|
continue parseLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,35 +120,27 @@ parseLine:
|
|||||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||||
if ignoreIPCIDRRegexp(ruleLine) {
|
if ignoreIPCIDRRegexp(ruleLine) {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
isRegexp = true
|
isRegexp = true
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(ruleLine, "://") {
|
if strings.Contains(ruleLine, "://") {
|
||||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||||
isSuffix = true
|
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "/") {
|
if strings.Contains(ruleLine, "/") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
if strings.Contains(ruleLine, "##") {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
|
if strings.Contains(ruleLine, "#$#") {
|
||||||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
|
|
||||||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
|
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
|
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if strings.Contains(ruleLine, "~") {
|
|
||||||
ignoredLines++
|
|
||||||
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var domainCheck string
|
var domainCheck string
|
||||||
@@ -163,7 +151,7 @@ parseLine:
|
|||||||
}
|
}
|
||||||
if ruleLine == "" {
|
if ruleLine == "" {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||||
@@ -171,13 +159,13 @@ parseLine:
|
|||||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||||
if ipErr == nil {
|
if ipErr == nil {
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||||
}
|
}
|
||||||
ignoredLines++
|
ignoredLines++
|
||||||
continue
|
continue
|
||||||
@@ -295,112 +283,10 @@ parseLine:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ignoredLines > 0 {
|
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||||
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
|
||||||
}
|
|
||||||
return []option.HeadlessRule{currentRule}, nil
|
return []option.HeadlessRule{currentRule}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
|
||||||
|
|
||||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
|
||||||
if len(rules) != 1 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule := rules[0]
|
|
||||||
var (
|
|
||||||
importantDomain []string
|
|
||||||
importantDomainRegex []string
|
|
||||||
importantExcludeDomain []string
|
|
||||||
importantExcludeDomainRegex []string
|
|
||||||
domain []string
|
|
||||||
domainRegex []string
|
|
||||||
excludeDomain []string
|
|
||||||
excludeDomainRegex []string
|
|
||||||
)
|
|
||||||
parse:
|
|
||||||
for {
|
|
||||||
switch rule.Type {
|
|
||||||
case C.RuleTypeLogical:
|
|
||||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
|
||||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
|
||||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
|
||||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
|
||||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
rule = rule.LogicalOptions.Rules[1]
|
|
||||||
case C.RuleTypeDefault:
|
|
||||||
domain = rule.DefaultOptions.AdGuardDomain
|
|
||||||
domainRegex = rule.DefaultOptions.DomainRegex
|
|
||||||
if len(domain)+len(domainRegex) == 0 {
|
|
||||||
return nil, ErrInvalid
|
|
||||||
}
|
|
||||||
break parse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var output bytes.Buffer
|
|
||||||
for _, ruleLine := range importantDomain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantDomainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range importantExcludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/$important\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domain {
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range domainRegex {
|
|
||||||
output.WriteString("/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomain {
|
|
||||||
output.WriteString("@@")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("\n")
|
|
||||||
}
|
|
||||||
for _, ruleLine := range excludeDomainRegex {
|
|
||||||
output.WriteString("@@/")
|
|
||||||
output.WriteString(ruleLine)
|
|
||||||
output.WriteString("/\n")
|
|
||||||
}
|
|
||||||
return output.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||||
ruleLine = ruleLine[12:]
|
ruleLine = ruleLine[12:]
|
||||||
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
|||||||
ruleLine = ruleLine[13:]
|
ruleLine = ruleLine[13:]
|
||||||
} else if strings.HasPrefix(ruleLine, "^") {
|
} else if strings.HasPrefix(ruleLine, "^") {
|
||||||
ruleLine = ruleLine[1:]
|
ruleLine = ruleLine[1:]
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
return parseErr == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
||||||
@@ -7,15 +7,13 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/route/rule"
|
"github.com/sagernet/sing-box/route/rule"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConverter(t *testing.T) {
|
func TestConverter(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
ruleString := `||sagernet.org^$important
|
rules, err := Convert(strings.NewReader(`
|
||||||
@@|sing-box.sagernet.org^$important
|
|
||||||
||example.org^
|
||example.org^
|
||||||
|example.com^
|
|example.com^
|
||||||
example.net^
|
example.net^
|
||||||
@@ -23,9 +21,10 @@ example.net^
|
|||||||
||example.edu.tw^
|
||example.edu.tw^
|
||||||
|example.gov
|
|example.gov
|
||||||
example.arpa
|
example.arpa
|
||||||
@@|sagernet.example.org^
|
@@|sagernet.example.org|
|
||||||
`
|
||sagernet.org^$important
|
||||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
@@|sing-box.sagernet.org^$important
|
||||||
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -76,18 +75,15 @@ example.arpa
|
|||||||
Domain: domain,
|
Domain: domain,
|
||||||
}), domain)
|
}), domain)
|
||||||
}
|
}
|
||||||
ruleFromOptions, err := FromOptions(rules)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHosts(t *testing.T) {
|
func TestHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
127.0.0.1 localhost
|
127.0.0.1 localhost
|
||||||
::1 localhost #[IPv6]
|
::1 localhost #[IPv6]
|
||||||
0.0.0.0 google.com
|
0.0.0.0 google.com
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
|
|||||||
|
|
||||||
func TestSimpleHosts(t *testing.T) {
|
func TestSimpleHosts(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
rules, err := ToOptions(strings.NewReader(`
|
rules, err := Convert(strings.NewReader(`
|
||||||
example.com
|
example.com
|
||||||
www.example.org
|
www.example.org
|
||||||
`), logger.NOP())
|
`))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, rules, 1)
|
require.Len(t, rules, 1)
|
||||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||||
@@ -66,17 +66,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
interfaceFinder = control.NewDefaultInterfaceFinder()
|
interfaceFinder = control.NewDefaultInterfaceFinder()
|
||||||
}
|
}
|
||||||
if options.BindInterface != "" {
|
if options.BindInterface != "" {
|
||||||
if !(C.IsLinux || C.IsDarwin || C.IsWindows) {
|
|
||||||
return nil, E.New("`bind_interface` is only supported on Linux, macOS and Windows")
|
|
||||||
}
|
|
||||||
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
bindFunc := control.BindToInterface(interfaceFinder, options.BindInterface, -1)
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
}
|
}
|
||||||
if options.RoutingMark > 0 {
|
if options.RoutingMark > 0 {
|
||||||
if !C.IsLinux {
|
|
||||||
return nil, E.New("`routing_mark` is only supported on Linux")
|
|
||||||
}
|
|
||||||
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
dialer.Control = control.Append(dialer.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
listener.Control = control.Append(listener.Control, setMarkWrapper(networkManager, uint32(options.RoutingMark), false))
|
||||||
}
|
}
|
||||||
@@ -97,6 +91,10 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
} else if networkManager.AutoDetectInterface() {
|
} else if networkManager.AutoDetectInterface() {
|
||||||
if platformInterface != nil {
|
if platformInterface != nil {
|
||||||
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
|
||||||
|
if networkStrategy == nil {
|
||||||
|
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
||||||
|
defaultNetworkStrategy = true
|
||||||
|
}
|
||||||
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
|
||||||
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
|
||||||
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
if networkStrategy == nil && len(networkType) == 0 && len(fallbackNetworkType) == 0 {
|
||||||
@@ -108,10 +106,6 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
|
|||||||
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
if networkFallbackDelay == 0 && defaultOptions.FallbackDelay != 0 {
|
||||||
networkFallbackDelay = defaultOptions.FallbackDelay
|
networkFallbackDelay = defaultOptions.FallbackDelay
|
||||||
}
|
}
|
||||||
if networkStrategy == nil {
|
|
||||||
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
|
|
||||||
defaultNetworkStrategy = true
|
|
||||||
}
|
|
||||||
bindFunc := networkManager.ProtectFunc()
|
bindFunc := networkManager.ProtectFunc()
|
||||||
dialer.Control = control.Append(dialer.Control, bindFunc)
|
dialer.Control = control.Append(dialer.Control, bindFunc)
|
||||||
listener.Control = control.Append(listener.Control, bindFunc)
|
listener.Control = control.Append(listener.Control, bindFunc)
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ func NewWithOptions(options Options) (N.Dialer, error) {
|
|||||||
dnsQueryOptions = adapter.DNSQueryOptions{
|
dnsQueryOptions = adapter.DNSQueryOptions{
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
Strategy: strategy,
|
Strategy: strategy,
|
||||||
Timeout: time.Duration(dialOptions.DomainResolver.Timeout),
|
|
||||||
DisableCache: dialOptions.DomainResolver.DisableCache,
|
DisableCache: dialOptions.DomainResolver.DisableCache,
|
||||||
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
|
||||||
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common"
|
||||||
"github.com/sagernet/sing/common/bufio"
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
@@ -24,9 +26,7 @@ type slowOpenConn struct {
|
|||||||
destination M.Socksaddr
|
destination M.Socksaddr
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
create chan struct{}
|
create chan struct{}
|
||||||
done chan struct{}
|
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
closeOnce sync.Once
|
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,6 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
|
|||||||
network: network,
|
network: network,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
create: make(chan struct{}),
|
create: make(chan struct{}),
|
||||||
done: make(chan struct{}),
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,8 +55,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, os.ErrClosed
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.conn.Read(b)
|
return c.conn.Read(b)
|
||||||
@@ -75,15 +74,12 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
return c.conn.Write(b)
|
return c.conn.Write(b)
|
||||||
case <-c.done:
|
|
||||||
return 0, os.ErrClosed
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
conn, err := c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
c.conn, err = c.dialer.DialContext(c.ctx, c.network, c.destination.String(), b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.err = err
|
c.conn = nil
|
||||||
} else {
|
c.err = E.Cause(err, "dial tcp fast open")
|
||||||
c.conn = conn
|
|
||||||
}
|
}
|
||||||
n = len(b)
|
n = len(b)
|
||||||
close(c.create)
|
close(c.create)
|
||||||
@@ -91,13 +87,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) Close() error {
|
func (c *slowOpenConn) Close() error {
|
||||||
c.closeOnce.Do(func() {
|
return common.Close(c.conn)
|
||||||
close(c.done)
|
|
||||||
if c.conn != nil {
|
|
||||||
c.conn.Close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *slowOpenConn) LocalAddr() net.Addr {
|
func (c *slowOpenConn) LocalAddr() net.Addr {
|
||||||
@@ -162,8 +152,8 @@ func (c *slowOpenConn) WriteTo(w io.Writer) (n int64, err error) {
|
|||||||
if c.err != nil {
|
if c.err != nil {
|
||||||
return 0, c.err
|
return 0, c.err
|
||||||
}
|
}
|
||||||
case <-c.done:
|
case <-c.ctx.Done():
|
||||||
return 0, c.err
|
return 0, c.ctx.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return bufio.Copy(w, c.conn)
|
return bufio.Copy(w, c.conn)
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ type Listener struct {
|
|||||||
disablePacketOutput bool
|
disablePacketOutput bool
|
||||||
setSystemProxy bool
|
setSystemProxy bool
|
||||||
systemProxySOCKS bool
|
systemProxySOCKS bool
|
||||||
tproxy bool
|
|
||||||
|
|
||||||
tcpListener net.Listener
|
tcpListener net.Listener
|
||||||
systemProxy settings.SystemProxy
|
systemProxy settings.SystemProxy
|
||||||
@@ -55,7 +54,6 @@ type Options struct {
|
|||||||
DisablePacketOutput bool
|
DisablePacketOutput bool
|
||||||
SetSystemProxy bool
|
SetSystemProxy bool
|
||||||
SystemProxySOCKS bool
|
SystemProxySOCKS bool
|
||||||
TProxy bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@@ -73,7 +71,6 @@ func New(
|
|||||||
disablePacketOutput: options.DisablePacketOutput,
|
disablePacketOutput: options.DisablePacketOutput,
|
||||||
setSystemProxy: options.SetSystemProxy,
|
setSystemProxy: options.SetSystemProxy,
|
||||||
systemProxySOCKS: options.SystemProxySOCKS,
|
systemProxySOCKS: options.SystemProxySOCKS,
|
||||||
tproxy: options.TProxy,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ package listener
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/redir"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
@@ -53,13 +51,6 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
|
|||||||
}
|
}
|
||||||
setMultiPathTCP(&listenConfig)
|
setMultiPathTCP(&listenConfig)
|
||||||
}
|
}
|
||||||
if l.tproxy {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
tcpListener, err := ListenNetworkNamespace[net.Listener](l.listenOptions.NetNs, func() (net.Listener, error) {
|
||||||
if l.listenOptions.TCPFastOpen {
|
if l.listenOptions.TCPFastOpen {
|
||||||
var tfoConfig tfo.ListenConfig
|
var tfoConfig tfo.ListenConfig
|
||||||
|
|||||||
@@ -5,10 +5,8 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"syscall"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/redir"
|
|
||||||
"github.com/sagernet/sing/common/buf"
|
"github.com/sagernet/sing/common/buf"
|
||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
@@ -38,13 +36,6 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
if !udpFragment {
|
if !udpFragment {
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
|
listenConfig.Control = control.Append(listenConfig.Control, control.DisableUDPFragment())
|
||||||
}
|
}
|
||||||
if l.tproxy {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, func(network, address string, conn syscall.RawConn) error {
|
|
||||||
return control.Raw(conn, func(fd uintptr) error {
|
|
||||||
return redir.TProxy(fd, !M.ParseSocksaddr(address).IsIPv4(), true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
udpConn, err := ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
|
||||||
})
|
})
|
||||||
@@ -59,30 +50,12 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
|
|||||||
|
|
||||||
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
|
func (l *Listener) DialContext(dialer net.Dialer, ctx context.Context, network string, address string) (net.Conn, error) {
|
||||||
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
|
return ListenNetworkNamespace[net.Conn](l.listenOptions.NetNs, func() (net.Conn, error) {
|
||||||
if l.listenOptions.BindInterface != "" {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
|
||||||
}
|
|
||||||
if l.listenOptions.RoutingMark != 0 {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
|
||||||
}
|
|
||||||
if l.listenOptions.ReuseAddr {
|
|
||||||
dialer.Control = control.Append(dialer.Control, control.ReuseAddr())
|
|
||||||
}
|
|
||||||
return dialer.DialContext(ctx, network, address)
|
return dialer.DialContext(ctx, network, address)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
func (l *Listener) ListenPacket(listenConfig net.ListenConfig, ctx context.Context, network string, address string) (net.PacketConn, error) {
|
||||||
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
return ListenNetworkNamespace[net.PacketConn](l.listenOptions.NetNs, func() (net.PacketConn, error) {
|
||||||
if l.listenOptions.BindInterface != "" {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.BindToInterface(service.FromContext[adapter.NetworkManager](l.ctx).InterfaceFinder(), l.listenOptions.BindInterface, -1))
|
|
||||||
}
|
|
||||||
if l.listenOptions.RoutingMark != 0 {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.RoutingMark(uint32(l.listenOptions.RoutingMark)))
|
|
||||||
}
|
|
||||||
if l.listenOptions.ReuseAddr {
|
|
||||||
listenConfig.Control = control.Append(listenConfig.Control, control.ReuseAddr())
|
|
||||||
}
|
|
||||||
return listenConfig.ListenPacket(ctx, network, address)
|
return listenConfig.ListenPacket(ctx, network, address)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
// rup8(sizeof(xtcpcb_n))
|
// rup8(sizeof(xtcpcb_n))
|
||||||
itemSize += 208
|
itemSize += 208
|
||||||
}
|
}
|
||||||
|
|
||||||
var fallbackUDPProcess string
|
|
||||||
// skip the first xinpgen(24 bytes) block
|
// skip the first xinpgen(24 bytes) block
|
||||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||||
// offset of xinpcb_n and xsocket_n
|
// offset of xinpcb_n and xsocket_n
|
||||||
@@ -92,12 +90,10 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
flag := buf[inp+44]
|
flag := buf[inp+44]
|
||||||
|
|
||||||
var srcIP netip.Addr
|
var srcIP netip.Addr
|
||||||
srcIsIPv4 := false
|
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||||
srcIsIPv4 = true
|
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||||
@@ -105,21 +101,13 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ip == srcIP {
|
if ip != srcIP {
|
||||||
// xsocket_n.so_last_pid
|
continue
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
|
||||||
return getExecPathFromPID(pid)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// udp packet connection may be not equal with srcIP
|
// xsocket_n.so_last_pid
|
||||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
pid := readNativeUint32(buf[so+68 : so+72])
|
||||||
pid := readNativeUint32(buf[so+68 : so+72])
|
return getExecPathFromPID(pid)
|
||||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
|
||||||
return fallbackUDPProcess, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", ErrNotFound
|
return "", ErrNotFound
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||||
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
err := syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1)
|
||||||
@@ -20,13 +20,11 @@ func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
|||||||
if err == nil && isIPv6 {
|
if err == nil && isIPv6 {
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
|
||||||
}
|
}
|
||||||
if isUDP {
|
if err == nil {
|
||||||
if err == nil {
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
|
}
|
||||||
}
|
if err == nil && isIPv6 {
|
||||||
if err == nil && isIPv6 {
|
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
||||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/sagernet/sing/common/control"
|
"github.com/sagernet/sing/common/control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TProxy(fd uintptr, isIPv6 bool, isUDP bool) error {
|
func TProxy(fd uintptr, isIPv6 bool) error {
|
||||||
return os.ErrInvalid
|
return os.ErrInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,15 +215,16 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
|||||||
case ruleItemWIFIBSSID:
|
case ruleItemWIFIBSSID:
|
||||||
rule.WIFIBSSID, err = readRuleItemString(reader)
|
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||||
case ruleItemAdGuardDomain:
|
case ruleItemAdGuardDomain:
|
||||||
|
if recover {
|
||||||
|
err = E.New("unable to decompile binary AdGuard rules to rule-set")
|
||||||
|
return
|
||||||
|
}
|
||||||
var matcher *domain.AdGuardMatcher
|
var matcher *domain.AdGuardMatcher
|
||||||
matcher, err = domain.ReadAdGuardMatcher(reader)
|
matcher, err = domain.ReadAdGuardMatcher(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rule.AdGuardDomainMatcher = matcher
|
rule.AdGuardDomainMatcher = matcher
|
||||||
if recover {
|
|
||||||
rule.AdGuardDomain = matcher.Dump()
|
|
||||||
}
|
|
||||||
case ruleItemNetworkType:
|
case ruleItemNetworkType:
|
||||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||||
case ruleItemNetworkIsExpensive:
|
case ruleItemNetworkIsExpensive:
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ package tls
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
|
|
||||||
"github.com/caddyserver/certmagic"
|
"github.com/caddyserver/certmagic"
|
||||||
"github.com/libdns/alidns"
|
"github.com/libdns/alidns"
|
||||||
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type acmeLogWriter struct {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||||
logger logger.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
|
|
||||||
logLine := strings.ReplaceAll(string(p), " ", ": ")
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(logLine, "error: "):
|
|
||||||
w.logger.Error(logLine[7:])
|
|
||||||
case strings.HasPrefix(logLine, "warn: "):
|
|
||||||
w.logger.Warn(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "info: "):
|
|
||||||
w.logger.Info(logLine[6:])
|
|
||||||
case strings.HasPrefix(logLine, "debug: "):
|
|
||||||
w.logger.Debug(logLine[7:])
|
|
||||||
default:
|
|
||||||
w.logger.Debug(logLine)
|
|
||||||
}
|
|
||||||
return len(p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *acmeLogWriter) Sync() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encoderConfig() zapcore.EncoderConfig {
|
|
||||||
config := zap.NewProductionEncoderConfig()
|
|
||||||
config.TimeKey = zapcore.OmitKey
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
|
||||||
var acmeServer string
|
var acmeServer string
|
||||||
switch options.Provider {
|
switch options.Provider {
|
||||||
case "", "letsencrypt":
|
case "", "letsencrypt":
|
||||||
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
} else {
|
} else {
|
||||||
storage = certmagic.Default.Storage
|
storage = certmagic.Default.Storage
|
||||||
}
|
}
|
||||||
zapLogger := zap.New(zapcore.NewCore(
|
|
||||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
|
||||||
&acmeLogWriter{logger: logger},
|
|
||||||
zap.DebugLevel,
|
|
||||||
))
|
|
||||||
config := &certmagic.Config{
|
config := &certmagic.Config{
|
||||||
DefaultServerName: options.DefaultServerName,
|
DefaultServerName: options.DefaultServerName,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Logger: zapLogger,
|
Logger: zap.New(zapcore.NewCore(
|
||||||
|
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||||
|
os.Stderr,
|
||||||
|
zap.InfoLevel,
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
acmeConfig := certmagic.ACMEIssuer{
|
acmeConfig := certmagic.ACMEIssuer{
|
||||||
CA: acmeServer,
|
CA: acmeServer,
|
||||||
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||||
Logger: zapLogger,
|
Logger: config.Logger,
|
||||||
}
|
}
|
||||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||||
var solver certmagic.DNS01Solver
|
var solver certmagic.DNS01Solver
|
||||||
@@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
|
|||||||
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
|
||||||
return config, nil
|
return config, nil
|
||||||
},
|
},
|
||||||
Logger: zapLogger,
|
|
||||||
})
|
})
|
||||||
config = certmagic.New(cache, *config)
|
config = certmagic.New(cache, *config)
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
|
|||||||
@@ -9,9 +9,8 @@ import (
|
|||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
|
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
|
||||||
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import (
|
|||||||
"golang.org/x/crypto/cryptobyte"
|
"golang.org/x/crypto/cryptobyte"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
var echConfig []byte
|
var echConfig []byte
|
||||||
if len(options.ECH.Config) > 0 {
|
if len(options.ECH.Config) > 0 {
|
||||||
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
|
||||||
@@ -45,12 +45,12 @@ func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, op
|
|||||||
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
if block == nil || block.Type != "ECH CONFIGS" || len(rest) > 0 {
|
||||||
return nil, E.New("invalid ECH configs pem")
|
return nil, E.New("invalid ECH configs pem")
|
||||||
}
|
}
|
||||||
clientConfig.SetECHConfigList(block.Bytes)
|
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
|
||||||
return clientConfig, nil
|
return &STDClientConfig{tlsConfig}, nil
|
||||||
} else {
|
} else {
|
||||||
return &ECHClientConfig{
|
return &STDECHClientConfig{
|
||||||
ECHCapableConfig: clientConfig,
|
STDClientConfig: STDClientConfig{tlsConfig},
|
||||||
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,15 +102,15 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ECHClientConfig struct {
|
type STDECHClientConfig struct {
|
||||||
ECHCapableConfig
|
STDClientConfig
|
||||||
access sync.Mutex
|
access sync.Mutex
|
||||||
dnsRouter adapter.DNSRouter
|
dnsRouter adapter.DNSRouter
|
||||||
lastTTL time.Duration
|
lastTTL time.Duration
|
||||||
lastUpdate time.Time
|
lastUpdate time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
tlsConn, err := s.fetchAndHandshake(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -122,17 +122,17 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
|
|||||||
return tlsConn, nil
|
return tlsConn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
func (s *STDECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
|
||||||
s.access.Lock()
|
s.access.Lock()
|
||||||
defer s.access.Unlock()
|
defer s.access.Unlock()
|
||||||
if len(s.ECHConfigList()) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 || s.lastTTL == 0 || time.Now().Sub(s.lastUpdate) > s.lastTTL {
|
||||||
message := &mDNS.Msg{
|
message := &mDNS.Msg{
|
||||||
MsgHdr: mDNS.MsgHdr{
|
MsgHdr: mDNS.MsgHdr{
|
||||||
RecursionDesired: true,
|
RecursionDesired: true,
|
||||||
},
|
},
|
||||||
Question: []mDNS.Question{
|
Question: []mDNS.Question{
|
||||||
{
|
{
|
||||||
Name: mDNS.Fqdn(s.ServerName()),
|
Name: mDNS.Fqdn(s.config.ServerName),
|
||||||
Qtype: mDNS.TypeHTTPS,
|
Qtype: mDNS.TypeHTTPS,
|
||||||
Qclass: mDNS.ClassINET,
|
Qclass: mDNS.ClassINET,
|
||||||
},
|
},
|
||||||
@@ -157,21 +157,21 @@ func (s *ECHClientConfig) fetchAndHandshake(ctx context.Context, conn net.Conn)
|
|||||||
}
|
}
|
||||||
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
s.lastTTL = time.Duration(rr.Header().Ttl) * time.Second
|
||||||
s.lastUpdate = time.Now()
|
s.lastUpdate = time.Now()
|
||||||
s.SetECHConfigList(echConfigList)
|
s.config.EncryptedClientHelloConfigList = echConfigList
|
||||||
break match
|
break match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s.ECHConfigList()) == 0 {
|
if len(s.config.EncryptedClientHelloConfigList) == 0 {
|
||||||
return nil, E.New("no ECH config found in DNS records")
|
return nil, E.New("no ECH config found in DNS records")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.Client(conn)
|
return s.Client(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ECHClientConfig) Clone() Config {
|
func (s *STDECHClientConfig) Clone() Config {
|
||||||
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
|
||||||
}
|
}
|
||||||
|
|
||||||
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
|
||||||
|
|||||||
@@ -11,12 +11,6 @@ import (
|
|||||||
"github.com/cloudflare/circl/kem"
|
"github.com/cloudflare/circl/kem"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ECHCapableConfig interface {
|
|
||||||
Config
|
|
||||||
ECHConfigList() []byte
|
|
||||||
SetECHConfigList([]byte)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
|
||||||
cipherSuites := []echCipherSuite{
|
cipherSuites := []echCipherSuite{
|
||||||
{
|
{
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseECHClientConfig(ctx context.Context, clientConfig ECHCapableConfig, options option.OutboundTLSOptions) (Config, error) {
|
func parseECHClientConfig(ctx context.Context, options option.OutboundTLSOptions, tlsConfig *tls.Config) (Config, error) {
|
||||||
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
return nil, E.New("ECH requires go1.24, please recompile your binary.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
|
|||||||
if decodedLen > 8 {
|
if decodedLen > 8 {
|
||||||
return nil, E.New("invalid short_id")
|
return nil, E.New("invalid short_id")
|
||||||
}
|
}
|
||||||
return &RealityClientConfig{ctx, uClient.(*UTLSClientConfig), publicKey, shortID}, nil
|
return &RealityClientConfig{ctx, uClient, publicKey, shortID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *RealityClientConfig) ServerName() string {
|
func (e *RealityClientConfig) ServerName() string {
|
||||||
|
|||||||
@@ -7,60 +7,43 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
)
|
)
|
||||||
|
|
||||||
type STDClientConfig struct {
|
type STDClientConfig struct {
|
||||||
ctx context.Context
|
config *tls.Config
|
||||||
config *tls.Config
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) ServerName() string {
|
func (s *STDClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return s.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetServerName(serverName string) {
|
func (s *STDClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
s.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) NextProtos() []string {
|
func (s *STDClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return s.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
|
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
|
||||||
c.config.NextProtos = nextProto
|
s.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Config() (*STDConfig, error) {
|
func (s *STDClientConfig) Config() (*STDConfig, error) {
|
||||||
return c.config, nil
|
return s.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return tls.Client(conn, s.config), nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return tls.Client(conn, c.config), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *STDClientConfig) Clone() Config {
|
func (s *STDClientConfig) Clone() Config {
|
||||||
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
|
return &STDClientConfig{s.config.Clone()}
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
||||||
@@ -77,7 +60,9 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
var tlsConfig tls.Config
|
var tlsConfig tls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
if !options.DisableSNI {
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
tlsConfig.ServerName = serverName
|
tlsConfig.ServerName = serverName
|
||||||
}
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
@@ -142,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
|
|||||||
}
|
}
|
||||||
tlsConfig.RootCAs = certPool
|
tlsConfig.RootCAs = certPool
|
||||||
}
|
}
|
||||||
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
if options.ECH != nil && options.ECH.Enabled {
|
||||||
return parseECHClientConfig(ctx, stdConfig, options)
|
return parseECHClientConfig(ctx, options, &tlsConfig)
|
||||||
} else {
|
|
||||||
return stdConfig, nil
|
|
||||||
}
|
}
|
||||||
|
return &STDClientConfig{&tlsConfig}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ var errInsecureUnused = E.New("tls: insecure unused")
|
|||||||
type STDServerConfig struct {
|
type STDServerConfig struct {
|
||||||
config *tls.Config
|
config *tls.Config
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
acmeService adapter.SimpleLifecycle
|
acmeService adapter.Service
|
||||||
certificate []byte
|
certificate []byte
|
||||||
key []byte
|
key []byte
|
||||||
certificatePath string
|
certificatePath string
|
||||||
@@ -165,11 +165,11 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
var acmeService adapter.SimpleLifecycle
|
var acmeService adapter.Service
|
||||||
var err error
|
var err error
|
||||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||||
//nolint:staticcheck
|
//nolint:staticcheck
|
||||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ import (
|
|||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
"github.com/sagernet/sing-box/option"
|
"github.com/sagernet/sing-box/option"
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
"github.com/sagernet/sing/common/ntp"
|
"github.com/sagernet/sing/common/ntp"
|
||||||
@@ -23,62 +22,48 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UTLSClientConfig struct {
|
type UTLSClientConfig struct {
|
||||||
ctx context.Context
|
config *utls.Config
|
||||||
config *utls.Config
|
id utls.ClientHelloID
|
||||||
id utls.ClientHelloID
|
|
||||||
fragment bool
|
|
||||||
fragmentFallbackDelay time.Duration
|
|
||||||
recordFragment bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ServerName() string {
|
func (e *UTLSClientConfig) ServerName() string {
|
||||||
return c.config.ServerName
|
return e.config.ServerName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetServerName(serverName string) {
|
func (e *UTLSClientConfig) SetServerName(serverName string) {
|
||||||
c.config.ServerName = serverName
|
e.config.ServerName = serverName
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) NextProtos() []string {
|
func (e *UTLSClientConfig) NextProtos() []string {
|
||||||
return c.config.NextProtos
|
return e.config.NextProtos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
|
||||||
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
|
||||||
nextProto = append(nextProto, "http/1.1")
|
nextProto = append(nextProto, "http/1.1")
|
||||||
}
|
}
|
||||||
c.config.NextProtos = nextProto
|
e.config.NextProtos = nextProto
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Config() (*STDConfig, error) {
|
func (e *UTLSClientConfig) Config() (*STDConfig, error) {
|
||||||
return nil, E.New("unsupported usage for uTLS")
|
return nil, E.New("unsupported usage for uTLS")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
func (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
|
||||||
if c.recordFragment {
|
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
|
||||||
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
|
|
||||||
}
|
|
||||||
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, c.config.Clone(), c.id)}, c.config.NextProtos}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
|
||||||
c.config.SessionIDGenerator = generator
|
e.config.SessionIDGenerator = generator
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) Clone() Config {
|
func (e *UTLSClientConfig) Clone() Config {
|
||||||
return &UTLSClientConfig{
|
return &UTLSClientConfig{
|
||||||
c.ctx, c.config.Clone(), c.id, c.fragment, c.fragmentFallbackDelay, c.recordFragment,
|
config: e.config.Clone(),
|
||||||
|
id: e.id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UTLSClientConfig) ECHConfigList() []byte {
|
|
||||||
return c.config.EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UTLSClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
|
|
||||||
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
|
|
||||||
}
|
|
||||||
|
|
||||||
type utlsConnWrapper struct {
|
type utlsConnWrapper struct {
|
||||||
*utls.UConn
|
*utls.UConn
|
||||||
}
|
}
|
||||||
@@ -131,12 +116,14 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
|
|||||||
return c.UConn.HandshakeContext(ctx)
|
return c.UConn.HandshakeContext(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
|
func NewUTLSClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (*UTLSClientConfig, error) {
|
||||||
var serverName string
|
var serverName string
|
||||||
if options.ServerName != "" {
|
if options.ServerName != "" {
|
||||||
serverName = options.ServerName
|
serverName = options.ServerName
|
||||||
} else if serverAddress != "" {
|
} else if serverAddress != "" {
|
||||||
serverName = serverAddress
|
if _, err := netip.ParseAddr(serverName); err != nil {
|
||||||
|
serverName = serverAddress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if serverName == "" && !options.Insecure {
|
if serverName == "" && !options.Insecure {
|
||||||
return nil, E.New("missing server_name or insecure=true")
|
return nil, E.New("missing server_name or insecure=true")
|
||||||
@@ -145,7 +132,11 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
var tlsConfig utls.Config
|
var tlsConfig utls.Config
|
||||||
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
|
||||||
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
|
||||||
tlsConfig.ServerName = serverName
|
if options.DisableSNI {
|
||||||
|
tlsConfig.ServerName = "127.0.0.1"
|
||||||
|
} else {
|
||||||
|
tlsConfig.ServerName = serverName
|
||||||
|
}
|
||||||
if options.Insecure {
|
if options.Insecure {
|
||||||
tlsConfig.InsecureSkipVerify = options.Insecure
|
tlsConfig.InsecureSkipVerify = options.Insecure
|
||||||
} else if options.DisableSNI {
|
} else if options.DisableSNI {
|
||||||
@@ -201,15 +192,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
|
return &UTLSClientConfig{&tlsConfig, id}, nil
|
||||||
if options.ECH != nil && options.ECH.Enabled {
|
|
||||||
if options.Reality != nil && options.Reality.Enabled {
|
|
||||||
return nil, E.New("Reality is conflict with ECH")
|
|
||||||
}
|
|
||||||
return parseECHClientConfig(ctx, uConfig, options)
|
|
||||||
} else {
|
|
||||||
return uConfig, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -237,7 +220,7 @@ func init() {
|
|||||||
|
|
||||||
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
|
||||||
switch name {
|
switch name {
|
||||||
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq", "chrome_pq_psk":
|
case "chrome_psk", "chrome_psk_shuffle", "chrome_padding_psk_shuffle", "chrome_pq":
|
||||||
fallthrough
|
fallthrough
|
||||||
case "chrome", "":
|
case "chrome", "":
|
||||||
return utls.HelloChrome_Auto, nil
|
return utls.HelloChrome_Auto, nil
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
package tf
|
package tf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
|
||||||
N "github.com/sagernet/sing/common/network"
|
N "github.com/sagernet/sing/common/network"
|
||||||
|
|
||||||
"golang.org/x/net/publicsuffix"
|
"golang.org/x/net/publicsuffix"
|
||||||
@@ -20,24 +17,17 @@ type Conn struct {
|
|||||||
tcpConn *net.TCPConn
|
tcpConn *net.TCPConn
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
firstPacketWritten bool
|
firstPacketWritten bool
|
||||||
splitPacket bool
|
|
||||||
splitRecord bool
|
|
||||||
fallbackDelay time.Duration
|
fallbackDelay time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
|
func NewConn(conn net.Conn, ctx context.Context, fallbackDelay time.Duration) (*Conn, error) {
|
||||||
if fallbackDelay == 0 {
|
|
||||||
fallbackDelay = C.TLSFragmentFallbackDelay
|
|
||||||
}
|
|
||||||
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
|
||||||
return &Conn{
|
return &Conn{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
tcpConn: tcpConn,
|
tcpConn: tcpConn,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
splitPacket: splitPacket,
|
|
||||||
splitRecord: splitRecord,
|
|
||||||
fallbackDelay: fallbackDelay,
|
fallbackDelay: fallbackDelay,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) Write(b []byte) (n int, err error) {
|
func (c *Conn) Write(b []byte) (n int, err error) {
|
||||||
@@ -47,12 +37,10 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
}()
|
}()
|
||||||
serverName := indexTLSServerName(b)
|
serverName := indexTLSServerName(b)
|
||||||
if serverName != nil {
|
if serverName != nil {
|
||||||
if c.splitPacket {
|
if c.tcpConn != nil {
|
||||||
if c.tcpConn != nil {
|
err = c.tcpConn.SetNoDelay(true)
|
||||||
err = c.tcpConn.SetNoDelay(true)
|
if err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splits := strings.Split(serverName.ServerName, ".")
|
splits := strings.Split(serverName.ServerName, ".")
|
||||||
@@ -73,50 +61,26 @@ func (c *Conn) Write(b []byte) (n int, err error) {
|
|||||||
currentIndex++
|
currentIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var buffer bytes.Buffer
|
|
||||||
for i := 0; i <= len(splitIndexes); i++ {
|
for i := 0; i <= len(splitIndexes); i++ {
|
||||||
var payload []byte
|
var payload []byte
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
payload = b[:splitIndexes[i]]
|
payload = b[:splitIndexes[i]]
|
||||||
if c.splitRecord {
|
|
||||||
payload = payload[recordLayerHeaderLen:]
|
|
||||||
}
|
|
||||||
} else if i == len(splitIndexes) {
|
} else if i == len(splitIndexes) {
|
||||||
payload = b[splitIndexes[i-1]:]
|
payload = b[splitIndexes[i-1]:]
|
||||||
} else {
|
} else {
|
||||||
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
payload = b[splitIndexes[i-1]:splitIndexes[i]]
|
||||||
}
|
}
|
||||||
if c.splitRecord {
|
if c.tcpConn != nil && i != len(splitIndexes) {
|
||||||
if c.splitPacket {
|
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
||||||
buffer.Reset()
|
if err != nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
payloadLen := uint16(len(payload))
|
} else {
|
||||||
buffer.Write(b[:3])
|
_, err = c.Conn.Write(payload)
|
||||||
binary.Write(&buffer, binary.BigEndian, payloadLen)
|
if err != nil {
|
||||||
buffer.Write(payload)
|
return
|
||||||
if c.splitPacket {
|
|
||||||
payload = buffer.Bytes()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if c.splitPacket {
|
|
||||||
if c.tcpConn != nil && i != len(splitIndexes) {
|
|
||||||
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_, err = c.Conn.Write(payload)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if c.splitRecord && !c.splitPacket {
|
|
||||||
_, err = c.Conn.Write(buffer.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if c.tcpConn != nil {
|
if c.tcpConn != nil {
|
||||||
err = c.tcpConn.SetNoDelay(false)
|
err = c.tcpConn.SetNoDelay(false)
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
package tf_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
tf "github.com/sagernet/sing-box/common/tlsfragment"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTLSFragment(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
|
||||||
require.NoError(t, err)
|
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, false, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLSRecordFragment(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
|
||||||
require.NoError(t, err)
|
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTLS2Fragment(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
tcpConn, err := net.Dial("tcp", "1.1.1.1:443")
|
|
||||||
require.NoError(t, err)
|
|
||||||
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, true, 0), &tls.Config{
|
|
||||||
ServerName: "www.cloudflare.com",
|
|
||||||
})
|
|
||||||
require.NoError(t, tlsConn.Handshake())
|
|
||||||
}
|
|
||||||
@@ -25,9 +25,6 @@ const (
|
|||||||
TypeTUIC = "tuic"
|
TypeTUIC = "tuic"
|
||||||
TypeHysteria2 = "hysteria2"
|
TypeHysteria2 = "hysteria2"
|
||||||
TypeTailscale = "tailscale"
|
TypeTailscale = "tailscale"
|
||||||
TypeDERP = "derp"
|
|
||||||
TypeResolved = "resolved"
|
|
||||||
TypeSSMAPI = "ssm-api"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ const (
|
|||||||
TCPTimeout = 15 * time.Second
|
TCPTimeout = 15 * time.Second
|
||||||
ReadPayloadTimeout = 300 * time.Millisecond
|
ReadPayloadTimeout = 300 * time.Millisecond
|
||||||
DNSTimeout = 10 * time.Second
|
DNSTimeout = 10 * time.Second
|
||||||
DirectDNSTimeout = 5 * time.Second
|
|
||||||
UDPTimeout = 5 * time.Minute
|
UDPTimeout = 5 * time.Minute
|
||||||
DefaultURLTestInterval = 3 * time.Minute
|
DefaultURLTestInterval = 3 * time.Minute
|
||||||
DefaultURLTestIdleTimeout = 30 * time.Minute
|
DefaultURLTestIdleTimeout = 30 * time.Minute
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ var (
|
|||||||
var _ adapter.DNSClient = (*Client)(nil)
|
var _ adapter.DNSClient = (*Client)(nil)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
|
timeout time.Duration
|
||||||
disableCache bool
|
disableCache bool
|
||||||
disableExpire bool
|
disableExpire bool
|
||||||
independentCache bool
|
independentCache bool
|
||||||
clientSubnet netip.Prefix
|
|
||||||
rdrc adapter.RDRCStore
|
rdrc adapter.RDRCStore
|
||||||
initRDRCFunc func() adapter.RDRCStore
|
initRDRCFunc func() adapter.RDRCStore
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
@@ -42,24 +42,27 @@ type Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClientOptions struct {
|
type ClientOptions struct {
|
||||||
|
Timeout time.Duration
|
||||||
DisableCache bool
|
DisableCache bool
|
||||||
DisableExpire bool
|
DisableExpire bool
|
||||||
IndependentCache bool
|
IndependentCache bool
|
||||||
CacheCapacity uint32
|
CacheCapacity uint32
|
||||||
ClientSubnet netip.Prefix
|
|
||||||
RDRC func() adapter.RDRCStore
|
RDRC func() adapter.RDRCStore
|
||||||
Logger logger.ContextLogger
|
Logger logger.ContextLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClient(options ClientOptions) *Client {
|
func NewClient(options ClientOptions) *Client {
|
||||||
client := &Client{
|
client := &Client{
|
||||||
|
timeout: options.Timeout,
|
||||||
disableCache: options.DisableCache,
|
disableCache: options.DisableCache,
|
||||||
disableExpire: options.DisableExpire,
|
disableExpire: options.DisableExpire,
|
||||||
independentCache: options.IndependentCache,
|
independentCache: options.IndependentCache,
|
||||||
clientSubnet: options.ClientSubnet,
|
|
||||||
initRDRCFunc: options.RDRC,
|
initRDRCFunc: options.RDRC,
|
||||||
logger: options.Logger,
|
logger: options.Logger,
|
||||||
}
|
}
|
||||||
|
if client.timeout == 0 {
|
||||||
|
client.timeout = C.DNSTimeout
|
||||||
|
}
|
||||||
cacheCapacity := options.CacheCapacity
|
cacheCapacity := options.CacheCapacity
|
||||||
if cacheCapacity < 1024 {
|
if cacheCapacity < 1024 {
|
||||||
cacheCapacity = 1024
|
cacheCapacity = 1024
|
||||||
@@ -101,12 +104,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return &responseMessage, nil
|
return &responseMessage, nil
|
||||||
}
|
}
|
||||||
question := message.Question[0]
|
question := message.Question[0]
|
||||||
clientSubnet := options.ClientSubnet
|
if options.ClientSubnet.IsValid() {
|
||||||
if !clientSubnet.IsValid() {
|
message = SetClientSubnet(message, options.ClientSubnet, true)
|
||||||
clientSubnet = c.clientSubnet
|
|
||||||
}
|
|
||||||
if clientSubnet.IsValid() {
|
|
||||||
message = SetClientSubnet(message, clientSubnet)
|
|
||||||
}
|
}
|
||||||
isSimpleRequest := len(message.Question) == 1 &&
|
isSimpleRequest := len(message.Question) == 1 &&
|
||||||
len(message.Ns) == 0 &&
|
len(message.Ns) == 0 &&
|
||||||
@@ -147,15 +146,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
return nil, ErrResponseRejectedCached
|
return nil, ErrResponseRejectedCached
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timeout := options.Timeout
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
||||||
if timeout == 0 {
|
|
||||||
if transport.HasDetour() {
|
|
||||||
timeout = C.DNSTimeout
|
|
||||||
} else {
|
|
||||||
timeout = C.DirectDNSTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
|
||||||
response, err := transport.Exchange(ctx, message)
|
response, err := transport.Exchange(ctx, message)
|
||||||
cancel()
|
cancel()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -262,15 +253,9 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
|
|||||||
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, domain string, options adapter.DNSQueryOptions, responseChecker func(responseAddrs []netip.Addr) bool) ([]netip.Addr, error) {
|
||||||
domain = FqdnToDomain(domain)
|
domain = FqdnToDomain(domain)
|
||||||
dnsName := dns.Fqdn(domain)
|
dnsName := dns.Fqdn(domain)
|
||||||
var strategy C.DomainStrategy
|
if options.Strategy == C.DomainStrategyIPv4Only {
|
||||||
if options.LookupStrategy != C.DomainStrategyAsIS {
|
|
||||||
strategy = options.LookupStrategy
|
|
||||||
} else {
|
|
||||||
strategy = options.Strategy
|
|
||||||
}
|
|
||||||
if strategy == C.DomainStrategyIPv4Only {
|
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeA, options, responseChecker)
|
||||||
} else if strategy == C.DomainStrategyIPv6Only {
|
} else if options.Strategy == C.DomainStrategyIPv6Only {
|
||||||
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
return c.lookupToExchange(ctx, transport, dnsName, dns.TypeAAAA, options, responseChecker)
|
||||||
}
|
}
|
||||||
var response4 []netip.Addr
|
var response4 []netip.Addr
|
||||||
@@ -296,7 +281,7 @@ func (c *Client) Lookup(ctx context.Context, transport adapter.DNSTransport, dom
|
|||||||
if len(response4) == 0 && len(response6) == 0 {
|
if len(response4) == 0 && len(response6) == 0 {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return sortAddresses(response4, response6, strategy), nil
|
return sortAddresses(response4, response6, options.Strategy), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ClearCache() {
|
func (c *Client) ClearCache() {
|
||||||
@@ -552,26 +537,12 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
|
|||||||
return value, loaded
|
return value, loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
func FixedResponseStatus(message *dns.Msg, rcode int) *dns.Msg {
|
|
||||||
return &dns.Msg{
|
|
||||||
MsgHdr: dns.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: rcode,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: message.Question,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Response: true,
|
Rcode: dns.RcodeSuccess,
|
||||||
Authoritative: true,
|
Response: true,
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
@@ -604,12 +575,9 @@ func FixedResponse(id uint16, question dns.Question, addresses []netip.Addr, tim
|
|||||||
func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {
|
func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Response: true,
|
Rcode: dns.RcodeSuccess,
|
||||||
Authoritative: true,
|
Response: true,
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
@@ -630,12 +598,9 @@ func FixedResponseCNAME(id uint16, question dns.Question, record string, timeToL
|
|||||||
func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {
|
func FixedResponseTXT(id uint16, question dns.Question, records []string, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Response: true,
|
Rcode: dns.RcodeSuccess,
|
||||||
Authoritative: true,
|
Response: true,
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
Answer: []dns.RR{
|
Answer: []dns.RR{
|
||||||
@@ -656,12 +621,9 @@ func FixedResponseTXT(id uint16, question dns.Question, records []string, timeTo
|
|||||||
func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {
|
func FixedResponseMX(id uint16, question dns.Question, records []*net.MX, timeToLive uint32) *dns.Msg {
|
||||||
response := dns.Msg{
|
response := dns.Msg{
|
||||||
MsgHdr: dns.MsgHdr{
|
MsgHdr: dns.MsgHdr{
|
||||||
Id: id,
|
Id: id,
|
||||||
Response: true,
|
Rcode: dns.RcodeSuccess,
|
||||||
Authoritative: true,
|
Response: true,
|
||||||
RecursionDesired: true,
|
|
||||||
RecursionAvailable: true,
|
|
||||||
Rcode: dns.RcodeSuccess,
|
|
||||||
},
|
},
|
||||||
Question: []dns.Question{question},
|
Question: []dns.Question{question},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix) *dns.Msg {
|
func SetClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, override bool) *dns.Msg {
|
||||||
return setClientSubnet(message, clientSubnet, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setClientSubnet(message *dns.Msg, clientSubnet netip.Prefix, clone bool) *dns.Msg {
|
|
||||||
var (
|
var (
|
||||||
optRecord *dns.OPT
|
optRecord *dns.OPT
|
||||||
subnetOption *dns.EDNS0_SUBNET
|
subnetOption *dns.EDNS0_SUBNET
|
||||||
@@ -23,6 +19,9 @@ findExists:
|
|||||||
var isEDNS0Subnet bool
|
var isEDNS0Subnet bool
|
||||||
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
subnetOption, isEDNS0Subnet = option.(*dns.EDNS0_SUBNET)
|
||||||
if isEDNS0Subnet {
|
if isEDNS0Subnet {
|
||||||
|
if !override {
|
||||||
|
return message
|
||||||
|
}
|
||||||
break findExists
|
break findExists
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,14 +37,14 @@ findExists:
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
message.Extra = append(message.Extra, optRecord)
|
message.Extra = append(message.Extra, optRecord)
|
||||||
} else if clone {
|
} else {
|
||||||
return setClientSubnet(message.Copy(), clientSubnet, false)
|
message = message.Copy()
|
||||||
}
|
}
|
||||||
if subnetOption == nil {
|
if subnetOption == nil {
|
||||||
subnetOption = new(dns.EDNS0_SUBNET)
|
subnetOption = new(dns.EDNS0_SUBNET)
|
||||||
subnetOption.Code = dns.EDNS0SUBNET
|
|
||||||
optRecord.Option = append(optRecord.Option, subnetOption)
|
optRecord.Option = append(optRecord.Option, subnetOption)
|
||||||
}
|
}
|
||||||
|
subnetOption.Code = dns.EDNS0SUBNET
|
||||||
if clientSubnet.Addr().Is4() {
|
if clientSubnet.Addr().Is4() {
|
||||||
subnetOption.Family = 1
|
subnetOption.Family = 1
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
|
|||||||
DisableExpire: options.DNSClientOptions.DisableExpire,
|
DisableExpire: options.DNSClientOptions.DisableExpire,
|
||||||
IndependentCache: options.DNSClientOptions.IndependentCache,
|
IndependentCache: options.DNSClientOptions.IndependentCache,
|
||||||
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
CacheCapacity: options.DNSClientOptions.CacheCapacity,
|
||||||
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
|
|
||||||
RDRC: func() adapter.RDRCStore {
|
RDRC: func() adapter.RDRCStore {
|
||||||
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
cacheFile := service.FromContext[adapter.CacheFile](ctx)
|
||||||
if cacheFile == nil {
|
if cacheFile == nil {
|
||||||
@@ -158,9 +157,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
if action.Strategy != C.DomainStrategyAsIS {
|
if action.Strategy != C.DomainStrategyAsIS {
|
||||||
options.Strategy = action.Strategy
|
options.Strategy = action.Strategy
|
||||||
}
|
}
|
||||||
if action.Timeout > 0 {
|
|
||||||
options.Timeout = action.Timeout
|
|
||||||
}
|
|
||||||
if isFakeIP || action.DisableCache {
|
if isFakeIP || action.DisableCache {
|
||||||
options.DisableCache = true
|
options.DisableCache = true
|
||||||
}
|
}
|
||||||
@@ -183,9 +179,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
|
|||||||
if action.Strategy != C.DomainStrategyAsIS {
|
if action.Strategy != C.DomainStrategyAsIS {
|
||||||
options.Strategy = action.Strategy
|
options.Strategy = action.Strategy
|
||||||
}
|
}
|
||||||
if action.Timeout > 0 {
|
|
||||||
options.Timeout = action.Timeout
|
|
||||||
}
|
|
||||||
if action.DisableCache {
|
if action.DisableCache {
|
||||||
options.DisableCache = true
|
options.DisableCache = true
|
||||||
}
|
}
|
||||||
@@ -265,14 +258,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
case *R.RuleActionReject:
|
case *R.RuleActionReject:
|
||||||
switch action.Method {
|
switch action.Method {
|
||||||
case C.RuleActionRejectMethodDefault:
|
case C.RuleActionRejectMethodDefault:
|
||||||
return &mDNS.Msg{
|
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
|
||||||
MsgHdr: mDNS.MsgHdr{
|
|
||||||
Id: message.Id,
|
|
||||||
Rcode: mDNS.RcodeRefused,
|
|
||||||
Response: true,
|
|
||||||
},
|
|
||||||
Question: []mDNS.Question{message.Question[0]},
|
|
||||||
}, nil
|
|
||||||
case C.RuleActionRejectMethodDrop:
|
case C.RuleActionRejectMethodDrop:
|
||||||
return nil, tun.ErrDrop
|
return nil, tun.ErrDrop
|
||||||
}
|
}
|
||||||
@@ -299,12 +285,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
|
|||||||
} else if errors.Is(err, ErrResponseRejected) {
|
} else if errors.Is(err, ErrResponseRejected) {
|
||||||
rejected = true
|
rejected = true
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
||||||
/*} else if responseCheck!= nil && errors.Is(err, RcodeError(mDNS.RcodeNameError)) {
|
|
||||||
rejected = true
|
|
||||||
r.logger.DebugContext(ctx, E.Cause(err, "response rejected for ", FormatQuestion(message.Question[0].String())))
|
|
||||||
*/
|
|
||||||
} else if len(message.Question) > 0 {
|
} else if len(message.Question) > 0 {
|
||||||
rejected = true
|
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
|
||||||
} else {
|
} else {
|
||||||
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ type Transport struct {
|
|||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
hasDetour bool
|
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
networkManager adapter.NetworkManager
|
networkManager adapter.NetworkManager
|
||||||
interfaceName string
|
interfaceName string
|
||||||
@@ -60,7 +59,6 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
|
|||||||
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),
|
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
dialer: transportDialer,
|
dialer: transportDialer,
|
||||||
hasDetour: options.Detour != "",
|
|
||||||
logger: logger,
|
logger: logger,
|
||||||
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
networkManager: service.FromContext[adapter.NetworkManager](ctx),
|
||||||
interfaceName: options.Interface,
|
interfaceName: options.Interface,
|
||||||
@@ -91,10 +89,6 @@ func (t *Transport) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Transport) HasDetour() bool {
|
|
||||||
return t.hasDetour
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
err := t.fetchServers()
|
err := t.fetchServers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,15 +3,11 @@ package transport
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/common/dialer"
|
"github.com/sagernet/sing-box/common/dialer"
|
||||||
@@ -43,13 +39,11 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
|
|||||||
|
|
||||||
type HTTPSTransport struct {
|
type HTTPSTransport struct {
|
||||||
dns.TransportAdapter
|
dns.TransportAdapter
|
||||||
logger logger.ContextLogger
|
logger logger.ContextLogger
|
||||||
dialer N.Dialer
|
dialer N.Dialer
|
||||||
destination *url.URL
|
destination *url.URL
|
||||||
headers http.Header
|
headers http.Header
|
||||||
transportAccess sync.Mutex
|
transport *http.Transport
|
||||||
transport *http.Transport
|
|
||||||
transportResetAt time.Time
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
|
||||||
@@ -167,33 +161,12 @@ func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Close() error {
|
func (t *HTTPSTransport) Close() error {
|
||||||
t.transportAccess.Lock()
|
|
||||||
defer t.transportAccess.Unlock()
|
|
||||||
t.transport.CloseIdleConnections()
|
t.transport.CloseIdleConnections()
|
||||||
t.transport = t.transport.Clone()
|
t.transport = t.transport.Clone()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||||
startAt := time.Now()
|
|
||||||
response, err := t.exchange(ctx, message)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
|
||||||
t.transportAccess.Lock()
|
|
||||||
defer t.transportAccess.Unlock()
|
|
||||||
if t.transportResetAt.After(startAt) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
t.transport.CloseIdleConnections()
|
|
||||||
t.transport = t.transport.Clone()
|
|
||||||
t.transportResetAt = time.Now()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
|
||||||
exMessage := *message
|
exMessage := *message
|
||||||
exMessage.Id = 0
|
exMessage.Id = 0
|
||||||
exMessage.Compress = true
|
exMessage.Compress = true
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||||
var state C.res_state
|
if C.res_init() != 0 {
|
||||||
if C.res_ninit(state) != 0 {
|
|
||||||
return &dnsConfig{
|
return &dnsConfig{
|
||||||
servers: defaultNS,
|
servers: defaultNS,
|
||||||
search: dnsDefaultSearch(),
|
search: dnsDefaultSearch(),
|
||||||
@@ -34,10 +33,10 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|||||||
conf := &dnsConfig{
|
conf := &dnsConfig{
|
||||||
ndots: 1,
|
ndots: 1,
|
||||||
timeout: 5 * time.Second,
|
timeout: 5 * time.Second,
|
||||||
attempts: int(state.retry),
|
attempts: int(C._res.retry),
|
||||||
}
|
}
|
||||||
for i := 0; i < int(state.nscount); i++ {
|
for i := 0; i < int(C._res.nscount); i++ {
|
||||||
ns := state.nsaddr_list[i]
|
ns := C._res.nsaddr_list[i]
|
||||||
addr := C.inet_ntoa(ns.sin_addr)
|
addr := C.inet_ntoa(ns.sin_addr)
|
||||||
if addr == nil {
|
if addr == nil {
|
||||||
continue
|
continue
|
||||||
@@ -45,7 +44,7 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
|||||||
conf.servers = append(conf.servers, C.GoString(addr))
|
conf.servers = append(conf.servers, C.GoString(addr))
|
||||||
}
|
}
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
search := state.dnsrch[i]
|
search := C._res.dnsrch[i]
|
||||||
if search == nil {
|
if search == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,17 +60,13 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
|
|||||||
if !serverAddr.IsValid() {
|
if !serverAddr.IsValid() {
|
||||||
return nil, E.New("invalid server address: ", serverAddr)
|
return nil, E.New("invalid server address: ", serverAddr)
|
||||||
}
|
}
|
||||||
return NewTLSRaw(logger, dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions), transportDialer, serverAddr, tlsConfig), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTLSRaw(logger logger.ContextLogger, adapter dns.TransportAdapter, dialer N.Dialer, serverAddr M.Socksaddr, tlsConfig tls.Config) *TLSTransport {
|
|
||||||
return &TLSTransport{
|
return &TLSTransport{
|
||||||
TransportAdapter: adapter,
|
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
|
||||||
logger: logger,
|
logger: logger,
|
||||||
dialer: dialer,
|
dialer: transportDialer,
|
||||||
serverAddr: serverAddr,
|
serverAddr: serverAddr,
|
||||||
tlsConfig: tlsConfig,
|
tlsConfig: tlsConfig,
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TLSTransport) Start(stage adapter.StartStage) error {
|
func (t *TLSTransport) Start(stage adapter.StartStage) error {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type TransportAdapter struct {
|
|||||||
transportType string
|
transportType string
|
||||||
transportTag string
|
transportTag string
|
||||||
dependencies []string
|
dependencies []string
|
||||||
hasDetour bool
|
|
||||||
strategy C.DomainStrategy
|
strategy C.DomainStrategy
|
||||||
clientSubnet netip.Prefix
|
clientSubnet netip.Prefix
|
||||||
}
|
}
|
||||||
@@ -36,7 +35,6 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
|
|||||||
transportType: transportType,
|
transportType: transportType,
|
||||||
transportTag: transportTag,
|
transportTag: transportTag,
|
||||||
dependencies: dependencies,
|
dependencies: dependencies,
|
||||||
hasDetour: localOptions.Detour != "",
|
|
||||||
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
|
||||||
clientSubnet: localOptions.LegacyClientSubnet,
|
clientSubnet: localOptions.LegacyClientSubnet,
|
||||||
}
|
}
|
||||||
@@ -71,10 +69,6 @@ func (a *TransportAdapter) Dependencies() []string {
|
|||||||
return a.dependencies
|
return a.dependencies
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *TransportAdapter) HasDetour() bool {
|
|
||||||
return a.hasDetour
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
|
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
|
||||||
return a.strategy
|
return a.strategy
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,119 +2,6 @@
|
|||||||
icon: material/alert-decagram
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 1.12.0-beta.30
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
### 1.11.14
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-beta.24
|
|
||||||
|
|
||||||
* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**
|
|
||||||
* Also add fragment options for TLS client configuration **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
For debugging only, it is recommended to disable if record fragmentation works.
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_fragment).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [TLS](/configuration/shared/tls/).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.23
|
|
||||||
|
|
||||||
* Add loopback address support for tun **1**
|
|
||||||
* Add cache support for ssm-api **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
TUN now implements SideStore's StosVPN.
|
|
||||||
|
|
||||||
See [Tun](/configuration/inbound/tun/#loopback_address).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/#cache_path).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.21
|
|
||||||
|
|
||||||
* Fix missing `home` option for DERP service **1**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
You can now choose what the DERP home page shows, just like with derper's `-home` flag.
|
|
||||||
|
|
||||||
See [DERP](/configuration/service/derp/#home).
|
|
||||||
|
|
||||||
### 1.11.13
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-beta.17
|
|
||||||
|
|
||||||
* Update quic-go to v0.52.0
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
#### 1.12.0-beta.15
|
|
||||||
|
|
||||||
* Add DERP service **1**
|
|
||||||
* Add Resolved service and DNS server **2**
|
|
||||||
* Add SSM API service **3**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
|
||||||
|
|
||||||
See [DERP Service](/configuration/service/derp/).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
|
||||||
(e.g. NetworkManager) and provide DNS resolution.
|
|
||||||
|
|
||||||
See [Resolved Service](/configuration/service/resolved/) and [Resolved DNS Server](/configuration/dns/server/resolved/).
|
|
||||||
|
|
||||||
**3**:
|
|
||||||
|
|
||||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|
||||||
|
|
||||||
See [SSM API Service](/configuration/service/ssm-api/).
|
|
||||||
|
|
||||||
### 1.11.11
|
|
||||||
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
|
|
||||||
violated the rules (TestFlight users are not affected)._
|
|
||||||
|
|
||||||
#### 1.12.0-beta.13
|
|
||||||
|
|
||||||
* Add TLS record fragment route options **1**
|
|
||||||
* Add missing `accept_routes` option for Tailscale **2**
|
|
||||||
* Fixes and improvements
|
|
||||||
|
|
||||||
**1**:
|
|
||||||
|
|
||||||
See [Route Action](/configuration/route/rule_action/#tls_record_fragment).
|
|
||||||
|
|
||||||
**2**:
|
|
||||||
|
|
||||||
See [Tailscale](/configuration/endpoint/tailscale/#accept_routes).
|
|
||||||
|
|
||||||
#### 1.12.0-beta.10
|
#### 1.12.0-beta.10
|
||||||
|
|
||||||
* Add control options for listeners **1**
|
* Add control options for listeners **1**
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/alert-decagram
|
icon: material/new-box
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-decagram: [servers](#servers)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [cache_capacity](#cache_capacity)
|
:material-plus: [cache_capacity](#cache_capacity)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
- `default`: Reply with REFUSED.
|
- `default`: Reply with NXDOMAIN.
|
||||||
- `drop`: Drop the request.
|
- `drop`: Drop the request.
|
||||||
|
|
||||||
`default` will be used by default.
|
`default` will be used by default.
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
#### method
|
#### method
|
||||||
|
|
||||||
- `default`: 返回 REFUSED。
|
- `default`: 返回 NXDOMAIN。
|
||||||
- `drop`: 丢弃请求。
|
- `drop`: 丢弃请求。
|
||||||
|
|
||||||
默认使用 `defualt`。
|
默认使用 `defualt`。
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ The type of the DNS server.
|
|||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
| `resolved` | [Resolved](./resolved/) |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ DNS 服务器的类型。
|
|||||||
| `dhcp` | [DHCP](./dhcp/) |
|
| `dhcp` | [DHCP](./dhcp/) |
|
||||||
| `fakeip` | [Fake IP](./fakeip/) |
|
| `fakeip` | [Fake IP](./fakeip/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
| `resolved` | [Resolved](./resolved/) |
|
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|
||||||
|
|||||||
@@ -1,84 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# Resolved
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"tag": "",
|
|
||||||
|
|
||||||
"service": "resolved",
|
|
||||||
"accept_default_resolvers": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### service
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
The tag of the [Resolved Service](/configuration/service/resolved).
|
|
||||||
|
|
||||||
#### accept_default_resolvers
|
|
||||||
|
|
||||||
Indicates whether the default DNS resolvers should be accepted for fallback queries in addition to matching domains.
|
|
||||||
|
|
||||||
Specifically, default DNS resolvers are DNS servers that have `SetLinkDefaultRoute` or `SetLinkDomains ~.` set.
|
|
||||||
|
|
||||||
If not enabled, `NXDOMAIN` will be returned for requests that do not match search or match domains.
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
=== "Split DNS only"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "local",
|
|
||||||
"tag": "local"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"tag": "resolved",
|
|
||||||
"service": "resolved"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"ip_accept_any": true,
|
|
||||||
"server": "resolved"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
=== "Use as global DNS"
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"dns": {
|
|
||||||
"servers": [
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
"service": "resolved",
|
|
||||||
"accept_default_resolvers": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -30,13 +30,13 @@ icon: material/new-box
|
|||||||
|
|
||||||
==Required==
|
==Required==
|
||||||
|
|
||||||
The tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale).
|
The tag of the Tailscale endpoint.
|
||||||
|
|
||||||
#### accept_default_resolvers
|
#### accept_default_resolvers
|
||||||
|
|
||||||
Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。
|
Indicates whether default DNS resolvers should be accepted for fallback queries in addition to MagicDNS。
|
||||||
|
|
||||||
if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
|
if not enabled, NXDOMAIN will be returned for non-Tailscale domain queries.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -80,4 +80,4 @@ if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -6,7 +6,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
# Endpoint
|
# Endpoint
|
||||||
|
|
||||||
An endpoint is a protocol with inbound and outbound behavior.
|
Endpoint is protocols that has both inbound and outbound behavior.
|
||||||
|
|
||||||
### Structure
|
### Structure
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ icon: material/new-box
|
|||||||
|
|
||||||
| 类型 | 格式 |
|
| 类型 | 格式 |
|
||||||
|-------------|---------------------------|
|
|-------------|---------------------------|
|
||||||
| `wireguard` | [WireGuard](./wireguard/) |
|
| `wireguard` | [WireGuard](./wiregaurd/) |
|
||||||
| `tailscale` | [Tailscale](./tailscale/) |
|
| `tailscale` | [Tailscale](./tailscale/) |
|
||||||
|
|
||||||
#### tag
|
#### tag
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ icon: material/new-box
|
|||||||
"control_url": "",
|
"control_url": "",
|
||||||
"ephemeral": false,
|
"ephemeral": false,
|
||||||
"hostname": "",
|
"hostname": "",
|
||||||
"accept_routes": false,
|
|
||||||
"exit_node": "",
|
"exit_node": "",
|
||||||
"exit_node_allow_lan_access": false,
|
"exit_node_allow_lan_access": false,
|
||||||
"advertise_routes": [],
|
"advertise_routes": [],
|
||||||
@@ -63,10 +62,6 @@ System hostname is used by default.
|
|||||||
|
|
||||||
Example: `localhost`
|
Example: `localhost`
|
||||||
|
|
||||||
#### accept_routes
|
|
||||||
|
|
||||||
Indicates whether the node should accept routes advertised by other nodes.
|
|
||||||
|
|
||||||
#### exit_node
|
#### exit_node
|
||||||
|
|
||||||
The exit node name or IP address to use.
|
The exit node name or IP address to use.
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
{
|
{
|
||||||
"external_controller": "0.0.0.0:9090",
|
"external_controller": "0.0.0.0:9090",
|
||||||
"external_ui": "dashboard"
|
"external_ui": "dashboard"
|
||||||
// "external_ui_download_detour": "direct"
|
// external_ui_download_detour: "direct"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
{
|
{
|
||||||
"external_controller": "0.0.0.0:9090",
|
"external_controller": "0.0.0.0:9090",
|
||||||
"external_ui": "dashboard"
|
"external_ui": "dashboard"
|
||||||
// "external_ui_download_detour": "direct"
|
// external_ui_download_detour: "direct"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
|
||||||
|
|
||||||
:material-plus: [loopback_address](#loopback_address)
|
|
||||||
|
|
||||||
!!! quote "Changes in sing-box 1.11.0"
|
!!! quote "Changes in sing-box 1.11.0"
|
||||||
|
|
||||||
:material-delete-alert: [gso](#gso)
|
:material-delete-alert: [gso](#gso)
|
||||||
@@ -60,12 +56,9 @@ icon: material/new-box
|
|||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"iproute2_table_index": 2022,
|
"iproute2_table_index": 2022,
|
||||||
"iproute2_rule_index": 9000,
|
"iproute2_rule_index": 9000,
|
||||||
"auto_redirect": true,
|
"auto_redirect": false,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
"loopback_address": [
|
|
||||||
"10.7.0.1"
|
|
||||||
],
|
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"route_address": [
|
"route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
@@ -73,6 +66,7 @@ icon: material/new-box
|
|||||||
"::/1",
|
"::/1",
|
||||||
"8000::/1"
|
"8000::/1"
|
||||||
],
|
],
|
||||||
|
|
||||||
"route_exclude_address": [
|
"route_exclude_address": [
|
||||||
"192.168.0.0/16",
|
"192.168.0.0/16",
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
@@ -123,6 +117,7 @@ icon: material/new-box
|
|||||||
"match_domain": []
|
"match_domain": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Deprecated
|
// Deprecated
|
||||||
"gso": false,
|
"gso": false,
|
||||||
"inet4_address": [
|
"inet4_address": [
|
||||||
@@ -145,8 +140,8 @@ icon: material/new-box
|
|||||||
"inet6_route_exclude_address": [
|
"inet6_route_exclude_address": [
|
||||||
"fc00::/7"
|
"fc00::/7"
|
||||||
],
|
],
|
||||||
...
|
|
||||||
// Listen Fields
|
... // Listen Fields
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -278,16 +273,6 @@ Connection output mark used by `auto_redirect`.
|
|||||||
|
|
||||||
`0x2024` is used by default.
|
`0x2024` is used by default.
|
||||||
|
|
||||||
#### loopback_address
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
Loopback addresses make TCP connections to the specified address connect to the source address.
|
|
||||||
|
|
||||||
Setting option value to `10.7.0.1` achieves the same behavior as SideStore/StosVPN.
|
|
||||||
|
|
||||||
When `auto_redirect` is enabled, the same behavior can be achieved for LAN devices (not just local) as a gateway.
|
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
Enforce strict routing rules when `auto_route` is enabled:
|
Enforce strict routing rules when `auto_route` is enabled:
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
---
|
---
|
||||||
icon: material/new-box
|
icon: material/alert-decagram
|
||||||
---
|
---
|
||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
|
||||||
|
|
||||||
:material-plus: [loopback_address](#loopback_address)
|
|
||||||
|
|
||||||
!!! quote "sing-box 1.11.0 中的更改"
|
!!! quote "sing-box 1.11.0 中的更改"
|
||||||
|
|
||||||
:material-delete-alert: [gso](#gso)
|
:material-delete-alert: [gso](#gso)
|
||||||
@@ -60,12 +56,9 @@ icon: material/new-box
|
|||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
"iproute2_table_index": 2022,
|
"iproute2_table_index": 2022,
|
||||||
"iproute2_rule_index": 9000,
|
"iproute2_rule_index": 9000,
|
||||||
"auto_redirect": true,
|
"auto_redirect": false,
|
||||||
"auto_redirect_input_mark": "0x2023",
|
"auto_redirect_input_mark": "0x2023",
|
||||||
"auto_redirect_output_mark": "0x2024",
|
"auto_redirect_output_mark": "0x2024",
|
||||||
"loopback_address": [
|
|
||||||
"10.7.0.1"
|
|
||||||
],
|
|
||||||
"strict_route": true,
|
"strict_route": true,
|
||||||
"route_address": [
|
"route_address": [
|
||||||
"0.0.0.0/1",
|
"0.0.0.0/1",
|
||||||
@@ -277,16 +270,6 @@ tun 接口的 IPv6 前缀。
|
|||||||
|
|
||||||
默认使用 `0x2024`。
|
默认使用 `0x2024`。
|
||||||
|
|
||||||
#### loopback_address
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
|
|
||||||
|
|
||||||
将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。
|
|
||||||
|
|
||||||
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。
|
|
||||||
|
|
||||||
#### strict_route
|
#### strict_route
|
||||||
|
|
||||||
当启用 `auto_route` 时,强制执行严格的路由规则:
|
当启用 `auto_route` 时,强制执行严格的路由规则:
|
||||||
@@ -415,11 +398,11 @@ UDP NAT 过期时间。
|
|||||||
|
|
||||||
TCP/IP 栈。
|
TCP/IP 栈。
|
||||||
|
|
||||||
| 栈 | 描述 |
|
| 栈 | 描述 |
|
||||||
|----------|-------------------------------------------------------------------------------------------------------|
|
|--------|------------------------------------------------------------------|
|
||||||
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
|
| system | 基于系统网络栈执行 L3 到 L4 转换 |
|
||||||
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
|
||||||
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
|
||||||
|
|
||||||
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ sing-box uses JSON for configuration files.
|
|||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
"services": [],
|
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -31,7 +30,6 @@ sing-box uses JSON for configuration files.
|
|||||||
| `inbounds` | [Inbound](./inbound/) |
|
| `inbounds` | [Inbound](./inbound/) |
|
||||||
| `outbounds` | [Outbound](./outbound/) |
|
| `outbounds` | [Outbound](./outbound/) |
|
||||||
| `route` | [Route](./route/) |
|
| `route` | [Route](./route/) |
|
||||||
| `services` | [Service](./service/) |
|
|
||||||
| `experimental` | [Experimental](./experimental/) |
|
| `experimental` | [Experimental](./experimental/) |
|
||||||
|
|
||||||
### Check
|
### Check
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
"inbounds": [],
|
"inbounds": [],
|
||||||
"outbounds": [],
|
"outbounds": [],
|
||||||
"route": {},
|
"route": {},
|
||||||
"services": [],
|
|
||||||
"experimental": {}
|
"experimental": {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -31,7 +30,6 @@ sing-box 使用 JSON 作为配置文件格式。
|
|||||||
| `inbounds` | [入站](./inbound/) |
|
| `inbounds` | [入站](./inbound/) |
|
||||||
| `outbounds` | [出站](./outbound/) |
|
| `outbounds` | [出站](./outbound/) |
|
||||||
| `route` | [路由](./route/) |
|
| `route` | [路由](./route/) |
|
||||||
| `services` | [服务](./service/) |
|
|
||||||
| `experimental` | [实验性](./experimental/) |
|
| `experimental` | [实验性](./experimental/) |
|
||||||
|
|
||||||
### 检查
|
### 检查
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ icon: material/new-box
|
|||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
|
||||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
:material-plus: [resolve.disable_cache](#disable_cache)
|
||||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
||||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
:material-plus: [resolve.client_subnet](#client_subnet)
|
||||||
@@ -92,8 +91,7 @@ Not available when `method` is set to drop.
|
|||||||
"udp_connect": false,
|
"udp_connect": false,
|
||||||
"udp_timeout": "",
|
"udp_timeout": "",
|
||||||
"tls_fragment": false,
|
"tls_fragment": false,
|
||||||
"tls_fragment_fallback_delay": "",
|
"tls_fragment_fallback_delay": ""
|
||||||
"tls_record_fragment": ""
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -166,17 +164,13 @@ If no protocol is sniffed, the following ports will be recognized as protocols b
|
|||||||
|
|
||||||
Fragment TLS handshakes to bypass firewalls.
|
Fragment TLS handshakes to bypass firewalls.
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**, and should not be used to circumvent real censorship.
|
||||||
and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Due to poor performance, try `tls_record_fragment` first, and only apply to server names known to be blocked.
|
Since it is not designed for performance, it should not be applied to all connections, but only to server names that are known to be blocked.
|
||||||
|
|
||||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
On Linux, Apple platforms, (administrator privileges required) Windows, the wait time can be automatically detected, otherwise it will fall back to waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
||||||
the wait time can be automatically detected. Otherwise, it will fall back to
|
|
||||||
waiting for a fixed time specified by `tls_fragment_fallback_delay`.
|
|
||||||
|
|
||||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time, because the target is considered to be local or behind a transparent proxy.
|
||||||
because the target is considered to be local or behind a transparent proxy.
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
#### tls_fragment_fallback_delay
|
||||||
|
|
||||||
@@ -186,12 +180,6 @@ The fallback value used when TLS segmentation cannot automatically determine the
|
|||||||
|
|
||||||
`500ms` is used by default.
|
`500ms` is used by default.
|
||||||
|
|
||||||
#### tls_record_fragment
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ icon: material/new-box
|
|||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
:material-plus: [tls_fragment](#tls_fragment)
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
||||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
|
||||||
:material-plus: [resolve.disable_cache](#disable_cache)
|
|
||||||
:material-plus: [resolve.rewrite_ttl](#rewrite_ttl)
|
|
||||||
:material-plus: [resolve.client_subnet](#client_subnet)
|
|
||||||
|
|
||||||
## 最终动作
|
## 最终动作
|
||||||
|
|
||||||
@@ -163,10 +159,9 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
||||||
|
|
||||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
由于它不是为性能设计的,不应被应用于所有连接,而仅应用于已知被阻止的服务器名称。
|
||||||
|
|
||||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
||||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
|
||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
||||||
|
|
||||||
@@ -178,12 +173,6 @@ UDP 连接超时时间。
|
|||||||
|
|
||||||
默认使用 `500ms`。
|
默认使用 `500ms`。
|
||||||
|
|
||||||
#### tls_record_fragment
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
|
||||||
|
|
||||||
### sniff
|
### sniff
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# DERP
|
|
||||||
|
|
||||||
DERP service is a Tailscale DERP server, similar to [derper](https://pkg.go.dev/tailscale.com/cmd/derper).
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "derp",
|
|
||||||
|
|
||||||
... // Listen Fields
|
|
||||||
|
|
||||||
"tls": {},
|
|
||||||
"config_path": "",
|
|
||||||
"verify_client_endpoint": [],
|
|
||||||
"verify_client_url": [],
|
|
||||||
"home": "",
|
|
||||||
"mesh_with": [],
|
|
||||||
"mesh_psk": "",
|
|
||||||
"mesh_psk_file": "",
|
|
||||||
"stun": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### tls
|
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|
||||||
|
|
||||||
#### config_path
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Derper configuration file path.
|
|
||||||
|
|
||||||
Example: `derper.key`
|
|
||||||
|
|
||||||
#### verify_client_endpoint
|
|
||||||
|
|
||||||
Tailscale endpoints tags to verify clients.
|
|
||||||
|
|
||||||
#### verify_client_url
|
|
||||||
|
|
||||||
URL to verify clients.
|
|
||||||
|
|
||||||
Object format:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"url": "https://my-headscale.com/verify",
|
|
||||||
|
|
||||||
... // Dial Fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Setting Array value to a string `__URL__` is equivalent to configuring:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "url": __URL__ }
|
|
||||||
```
|
|
||||||
|
|
||||||
#### home
|
|
||||||
|
|
||||||
What to serve at the root path. It may be left empty (the default, for a default homepage), `blank` for a blank page, or a URL to redirect to
|
|
||||||
|
|
||||||
#### mesh_with
|
|
||||||
|
|
||||||
Mesh with other DERP servers.
|
|
||||||
|
|
||||||
Object format:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"server": "",
|
|
||||||
"server_port": "",
|
|
||||||
"host": "",
|
|
||||||
"tls": {},
|
|
||||||
|
|
||||||
... // Dial Fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Object fields:
|
|
||||||
|
|
||||||
- `server`: **Required** DERP server address.
|
|
||||||
- `server_port`: **Required** DERP server port.
|
|
||||||
- `host`: Custom DERP hostname.
|
|
||||||
- `tls`: [TLS](/configuration/shared/tls/#outbound)
|
|
||||||
- `Dial Fields`: [Dial Fields](/configuration/shared/dial/)
|
|
||||||
|
|
||||||
#### mesh_psk
|
|
||||||
|
|
||||||
Pre-shared key for DERP mesh.
|
|
||||||
|
|
||||||
#### mesh_psk_file
|
|
||||||
|
|
||||||
Pre-shared key file for DERP mesh.
|
|
||||||
|
|
||||||
#### stun
|
|
||||||
|
|
||||||
STUN server listen options.
|
|
||||||
|
|
||||||
Object format:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"enabled": true,
|
|
||||||
|
|
||||||
... // Listen Fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Object fields:
|
|
||||||
|
|
||||||
- `enabled`: **Required** Enable STUN server.
|
|
||||||
- `listen`: **Required** STUN server listen address, default to `::`.
|
|
||||||
- `listen_port`: **Required** STUN server listen port, default to `3478`.
|
|
||||||
- `other Listen Fields`: [Listen Fields](/configuration/shared/listen/)
|
|
||||||
|
|
||||||
Setting `stun` value to a number `__PORT__` is equivalent to configuring:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{ "enabled": true, "listen_port": __PORT__ }
|
|
||||||
```
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# Service
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"services": [
|
|
||||||
{
|
|
||||||
"type": "",
|
|
||||||
"tag": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
| Type | Format |
|
|
||||||
|------------|------------------------|
|
|
||||||
| `derp` | [DERP](./derp) |
|
|
||||||
| `resolved` | [Resolved](./resolved) |
|
|
||||||
| `ssm-api` | [SSM API](./ssm-api) |
|
|
||||||
|
|
||||||
#### tag
|
|
||||||
|
|
||||||
The tag of the endpoint.
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# Resolved
|
|
||||||
|
|
||||||
Resolved service is a fake systemd-resolved DBUS service to receive DNS settings from other programs
|
|
||||||
(e.g. NetworkManager) and provide DNS resolution.
|
|
||||||
|
|
||||||
See also: [Resolved DNS Server](/configuration/dns/server/resolved/)
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "resolved",
|
|
||||||
|
|
||||||
... // Listen Fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### listen
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen address.
|
|
||||||
|
|
||||||
`127.0.0.53` will be used by default.
|
|
||||||
|
|
||||||
#### listen_port
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
Listen port.
|
|
||||||
|
|
||||||
`53` will be used by default.
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
icon: material/new-box
|
|
||||||
---
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
# SSM API
|
|
||||||
|
|
||||||
SSM API service is a RESTful API server for managing Shadowsocks servers.
|
|
||||||
|
|
||||||
See https://github.com/Shadowsocks-NET/shadowsocks-specs/blob/main/2023-1-shadowsocks-server-management-api-v1.md
|
|
||||||
|
|
||||||
### Structure
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"type": "ssm-api",
|
|
||||||
|
|
||||||
... // Listen Fields
|
|
||||||
|
|
||||||
"servers": {},
|
|
||||||
"cache_path": "",
|
|
||||||
"tls": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listen Fields
|
|
||||||
|
|
||||||
See [Listen Fields](/configuration/shared/listen/) for details.
|
|
||||||
|
|
||||||
### Fields
|
|
||||||
|
|
||||||
#### servers
|
|
||||||
|
|
||||||
==Required==
|
|
||||||
|
|
||||||
A mapping Object from HTTP endpoints to [Shadowsocks Inbound](/configuration/inbound/shadowsocks) tags.
|
|
||||||
|
|
||||||
Selected Shadowsocks inbounds must be configured with [managed](/configuration/inbound/shadowsocks#managed) enabled.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"servers": {
|
|
||||||
"/": "ss-in"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### cache_path
|
|
||||||
|
|
||||||
If set, when the server is about to stop, traffic and user state will be saved to the specified JSON file
|
|
||||||
to be restored on the next startup.
|
|
||||||
|
|
||||||
#### tls
|
|
||||||
|
|
||||||
TLS configuration, see [TLS](/configuration/shared/tls/#inbound).
|
|
||||||
@@ -4,9 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "Changes in sing-box 1.12.0"
|
!!! quote "Changes in sing-box 1.12.0"
|
||||||
|
|
||||||
:material-plus: [fragment](#fragment)
|
|
||||||
:material-plus: [fragment_fallback_delay](#fragment_fallback_delay)
|
|
||||||
:material-plus: [record_fragment](#record_fragment)
|
|
||||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||||
|
|
||||||
@@ -85,9 +82,6 @@ icon: material/alert-decagram
|
|||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": "",
|
"certificate": "",
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"fragment": false,
|
|
||||||
"fragment_fallback_delay": "",
|
|
||||||
"record_fragment": false,
|
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"config": [],
|
"config": [],
|
||||||
@@ -319,44 +313,6 @@ The path to ECH configuration, in PEM format.
|
|||||||
|
|
||||||
If empty, load from DNS will be attempted.
|
If empty, load from DNS will be attempted.
|
||||||
|
|
||||||
#### fragment
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
Fragment TLS handshakes to bypass firewalls.
|
|
||||||
|
|
||||||
This feature is intended to circumvent simple firewalls based on **plaintext packet matching**,
|
|
||||||
and should not be used to circumvent real censorship.
|
|
||||||
|
|
||||||
Due to poor performance, try `record_fragment` first, and only apply to server names known to be blocked.
|
|
||||||
|
|
||||||
On Linux, Apple platforms, (administrator privileges required) Windows,
|
|
||||||
the wait time can be automatically detected. Otherwise, it will fall back to
|
|
||||||
waiting for a fixed time specified by `fragment_fallback_delay`.
|
|
||||||
|
|
||||||
In addition, if the actual wait time is less than 20ms, it will also fall back to waiting for a fixed time,
|
|
||||||
because the target is considered to be local or behind a transparent proxy.
|
|
||||||
|
|
||||||
#### fragment_fallback_delay
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
The fallback value used when TLS segmentation cannot automatically determine the wait time.
|
|
||||||
|
|
||||||
`500ms` is used by default.
|
|
||||||
|
|
||||||
#### record_fragment
|
|
||||||
|
|
||||||
!!! question "Since sing-box 1.12.0"
|
|
||||||
|
|
||||||
==Client only==
|
|
||||||
|
|
||||||
Fragment TLS handshake into multiple TLS records to bypass firewalls.
|
|
||||||
|
|
||||||
### ACME Fields
|
### ACME Fields
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ icon: material/alert-decagram
|
|||||||
|
|
||||||
!!! quote "sing-box 1.12.0 中的更改"
|
!!! quote "sing-box 1.12.0 中的更改"
|
||||||
|
|
||||||
:material-plus: [tls_fragment](#tls_fragment)
|
|
||||||
:material-plus: [tls_fragment_fallback_delay](#tls_fragment_fallback_delay)
|
|
||||||
:material-plus: [tls_record_fragment](#tls_record_fragment)
|
|
||||||
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
:material-delete-clock: [ech.pq_signature_schemes_enabled](#pq_signature_schemes_enabled)
|
||||||
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
:material-delete-clock: [ech.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
|
||||||
|
|
||||||
@@ -85,9 +82,6 @@ icon: material/alert-decagram
|
|||||||
"cipher_suites": [],
|
"cipher_suites": [],
|
||||||
"certificate": [],
|
"certificate": [],
|
||||||
"certificate_path": "",
|
"certificate_path": "",
|
||||||
"fragment": false,
|
|
||||||
"fragment_fallback_delay": "",
|
|
||||||
"record_fragment": false,
|
|
||||||
"ech": {
|
"ech": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"pq_signature_schemes_enabled": false,
|
"pq_signature_schemes_enabled": false,
|
||||||
@@ -311,41 +305,6 @@ ECH PEM 配置路径
|
|||||||
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
如果为 true,则始终使用最大可能的 TLS 记录大小。
|
||||||
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
如果为 false,则可能会调整 TLS 记录的大小以尝试改善延迟。
|
||||||
|
|
||||||
#### tls_fragment
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
通过分段 TLS 握手数据包来绕过防火墙检测。
|
|
||||||
|
|
||||||
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
|
|
||||||
|
|
||||||
由于性能不佳,请首先尝试 `tls_record_fragment`,且仅应用于已知被阻止的服务器名称。
|
|
||||||
|
|
||||||
在 Linux、Apple 平台和需要管理员权限的 Windows 系统上,可自动检测等待时间。
|
|
||||||
若无法自动检测,将回退使用 `tls_fragment_fallback_delay` 指定的固定等待时间。
|
|
||||||
|
|
||||||
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
|
|
||||||
|
|
||||||
#### tls_fragment_fallback_delay
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
当 TLS 分片功能无法自动判定等待时间时使用的回退值。
|
|
||||||
|
|
||||||
默认使用 `500ms`。
|
|
||||||
|
|
||||||
#### tls_record_fragment
|
|
||||||
|
|
||||||
==仅客户端==
|
|
||||||
|
|
||||||
!!! question "自 sing-box 1.12.0 起"
|
|
||||||
|
|
||||||
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
|
|
||||||
|
|
||||||
### ACME 字段
|
### ACME 字段
|
||||||
|
|
||||||
#### domain
|
#### domain
|
||||||
|
|||||||
@@ -94,13 +94,18 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "udp",
|
"address": "223.5.5.5",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"strategy": "ipv4_only"
|
"strategy": "ipv4_only"
|
||||||
@@ -110,8 +115,7 @@ flowchart TB
|
|||||||
"type": "tun",
|
"type": "tun",
|
||||||
"inet4_address": "172.19.0.1/30",
|
"inet4_address": "172.19.0.1/30",
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
// "auto_redirect": true, // On linux
|
"strict_route": false
|
||||||
"strict_route": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -119,23 +123,25 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "dns",
|
"protocol": "dns",
|
||||||
"action": "hijack-dns"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,13 +155,18 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "udp",
|
"address": "223.5.5.5",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -165,8 +176,7 @@ flowchart TB
|
|||||||
"inet4_address": "172.19.0.1/30",
|
"inet4_address": "172.19.0.1/30",
|
||||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
// "auto_redirect": true, // On linux
|
"strict_route": false
|
||||||
"strict_route": true
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"outbounds": [
|
"outbounds": [
|
||||||
@@ -174,23 +184,25 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "dns",
|
"protocol": "dns",
|
||||||
"action": "hijack-dns"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,22 +216,23 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "udp",
|
"address": "223.5.5.5",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "remote",
|
"tag": "remote",
|
||||||
"type": "fakeip",
|
"address": "fakeip"
|
||||||
"inet4_range": "198.18.0.0/15",
|
|
||||||
"inet6_range": "fc00::/18"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"query_type": [
|
"query_type": [
|
||||||
"A",
|
"A",
|
||||||
@@ -228,6 +241,11 @@ flowchart TB
|
|||||||
"server": "remote"
|
"server": "remote"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"fakeip": {
|
||||||
|
"enabled": true,
|
||||||
|
"inet4_range": "198.18.0.0/15",
|
||||||
|
"inet6_range": "fc00::/18"
|
||||||
|
},
|
||||||
"independent_cache": true
|
"independent_cache": true
|
||||||
},
|
},
|
||||||
"inbounds": [
|
"inbounds": [
|
||||||
@@ -236,7 +254,6 @@ flowchart TB
|
|||||||
"inet4_address": "172.19.0.1/30",
|
"inet4_address": "172.19.0.1/30",
|
||||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||||
"auto_route": true,
|
"auto_route": true,
|
||||||
// "auto_redirect": true, // On linux
|
|
||||||
"strict_route": true
|
"strict_route": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -245,23 +262,25 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "dns",
|
||||||
|
"tag": "dns-out"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"protocol": "dns",
|
"protocol": "dns",
|
||||||
"action": "hijack-dns"
|
"outbound": "dns-out"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"geoip": [
|
||||||
|
"private"
|
||||||
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"auto_detect_interface": true
|
"auto_detect_interface": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,6 +290,54 @@ flowchart TB
|
|||||||
|
|
||||||
=== ":material-dns: DNS rules"
|
=== ":material-dns: DNS rules"
|
||||||
|
|
||||||
|
```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"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rule_set": "geosite-geolocation-cn",
|
||||||
|
"server": "local"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)"
|
||||||
|
|
||||||
=== ":material-shield-off: With DNS leaks"
|
=== ":material-shield-off: With DNS leaks"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -279,20 +346,35 @@ flowchart TB
|
|||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "https",
|
"address": "https://223.5.5.5/dns-query",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-geolocation-cn",
|
"rule_set": "geosite-geolocation-cn",
|
||||||
"server": "local"
|
"server": "local"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Default",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "and",
|
"mode": "and",
|
||||||
@@ -310,7 +392,6 @@ flowchart TB
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"route": {
|
"route": {
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
{
|
{
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
@@ -344,24 +425,35 @@ flowchart TB
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
=== ":material-security: Without DNS leaks, but slower"
|
=== ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)"
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"dns": {
|
"dns": {
|
||||||
"servers": [
|
"servers": [
|
||||||
{
|
{
|
||||||
"tag": "google",
|
"tag": "google",
|
||||||
"type": "tls",
|
"address": "tls://8.8.8.8"
|
||||||
"server": "8.8.8.8"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tag": "local",
|
"tag": "local",
|
||||||
"type": "https",
|
"address": "https://223.5.5.5/dns-query",
|
||||||
"server": "223.5.5.5"
|
"detour": "direct"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"rules": [
|
"rules": [
|
||||||
|
{
|
||||||
|
"outbound": "any",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"server": "local"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"server": "google"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-geolocation-cn",
|
"rule_set": "geosite-geolocation-cn",
|
||||||
"server": "local"
|
"server": "local"
|
||||||
@@ -384,7 +476,6 @@ flowchart TB
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"route": {
|
"route": {
|
||||||
"default_domain_resolver": "local",
|
|
||||||
"rule_set": [
|
"rule_set": [
|
||||||
{
|
{
|
||||||
"type": "remote",
|
"type": "remote",
|
||||||
@@ -426,13 +517,14 @@ flowchart TB
|
|||||||
{
|
{
|
||||||
"type": "direct",
|
"type": "direct",
|
||||||
"tag": "direct"
|
"tag": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "block",
|
||||||
|
"tag": "block"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"route": {
|
"route": {
|
||||||
"rules": [
|
"rules": [
|
||||||
{
|
|
||||||
"action": "sniff"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "or",
|
"mode": "or",
|
||||||
@@ -444,12 +536,20 @@ flowchart TB
|
|||||||
"port": 53
|
"port": 53
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"action": "hijack-dns"
|
"outbound": "dns"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ip_is_private": true,
|
"ip_is_private": true,
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Direct",
|
||||||
|
"outbound": "direct"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clash_mode": "Global",
|
||||||
|
"outbound": "default"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "logical",
|
"type": "logical",
|
||||||
"mode": "or",
|
"mode": "or",
|
||||||
@@ -465,23 +565,12 @@ flowchart TB
|
|||||||
"protocol": "stun"
|
"protocol": "stun"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"action": "reject"
|
"outbound": "block"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"rule_set": "geosite-geolocation-cn",
|
"rule_set": [
|
||||||
"outbound": "direct"
|
"geoip-cn",
|
||||||
},
|
"geosite-geolocation-cn"
|
||||||
{
|
|
||||||
"type": "logical",
|
|
||||||
"mode": "and",
|
|
||||||
"rules": [
|
|
||||||
{
|
|
||||||
"rule_set": "geoip-cn"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"rule_set": "geosite-geolocation-!cn",
|
|
||||||
"invert": true
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
"outbound": "direct"
|
"outbound": "direct"
|
||||||
}
|
}
|
||||||
@@ -502,4 +591,4 @@ flowchart TB
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -33,7 +33,7 @@ func BaseContext(platformInterface PlatformInterface) context.Context {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry, include.ServiceRegistry())
|
return box.Context(context.Background(), include.InboundRegistry(), include.OutboundRegistry(), include.EndpointRegistry(), dnsRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
func parseConfig(ctx context.Context, configContent string) (option.Options, error) {
|
||||||
|
|||||||
42
go.mod
42
go.mod
@@ -6,20 +6,18 @@ require (
|
|||||||
github.com/anytls/sing-anytls v0.0.8
|
github.com/anytls/sing-anytls v0.0.8
|
||||||
github.com/caddyserver/certmagic v0.23.0
|
github.com/caddyserver/certmagic v0.23.0
|
||||||
github.com/cloudflare/circl v1.6.1
|
github.com/cloudflare/circl v1.6.1
|
||||||
github.com/coder/websocket v1.8.12
|
|
||||||
github.com/cretz/bine v0.2.0
|
github.com/cretz/bine v0.2.0
|
||||||
github.com/go-chi/chi/v5 v5.2.1
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466
|
|
||||||
github.com/gofrs/uuid/v5 v5.3.2
|
github.com/gofrs/uuid/v5 v5.3.2
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
|
||||||
github.com/libdns/alidns v1.0.4-libdns.v1.beta1
|
github.com/libdns/alidns v1.0.4-libdns.v1.beta1
|
||||||
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6
|
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||||
github.com/metacubex/utls v1.7.0-alpha.3
|
github.com/metacubex/utls v1.7.0-alpha.2
|
||||||
github.com/mholt/acmez/v3 v3.1.2
|
github.com/mholt/acmez/v3 v3.1.2
|
||||||
github.com/miekg/dns v1.1.66
|
github.com/miekg/dns v1.1.65
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
|
||||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||||
@@ -27,17 +25,17 @@ require (
|
|||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/gomobile v0.1.6
|
github.com/sagernet/gomobile v0.1.6
|
||||||
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1
|
github.com/sagernet/quic-go v0.51.0-beta.1
|
||||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
|
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f
|
||||||
github.com/sagernet/sing-mux v0.3.2
|
github.com/sagernet/sing-mux v0.3.2
|
||||||
github.com/sagernet/sing-quic v0.5.0-beta.2
|
github.com/sagernet/sing-quic v0.4.1-0.20250505055457-ae141e8be88a
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
github.com/sagernet/sing-shadowsocks2 v0.2.0
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
|
||||||
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935
|
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210
|
||||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88
|
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8
|
||||||
github.com/sagernet/smux v1.5.34-mod.2
|
github.com/sagernet/smux v1.5.34-mod.2
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
github.com/sagernet/tailscale v1.80.3-mod.4
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
github.com/sagernet/wireguard-go v0.0.1-beta.7
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.9.1
|
||||||
@@ -45,11 +43,11 @@ require (
|
|||||||
github.com/vishvananda/netns v0.0.5
|
github.com/vishvananda/netns v0.0.5
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.38.0
|
golang.org/x/crypto v0.37.0
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
|
||||||
golang.org/x/mod v0.24.0
|
golang.org/x/mod v0.24.0
|
||||||
golang.org/x/net v0.40.0
|
golang.org/x/net v0.39.0
|
||||||
golang.org/x/sys v0.33.0
|
golang.org/x/sys v0.32.0
|
||||||
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
|
||||||
google.golang.org/grpc v1.72.0
|
google.golang.org/grpc v1.72.0
|
||||||
google.golang.org/protobuf v1.36.6
|
google.golang.org/protobuf v1.36.6
|
||||||
@@ -67,6 +65,7 @@ require (
|
|||||||
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
github.com/bits-and-blooms/bitset v1.13.0 // indirect
|
||||||
github.com/caddyserver/zerossl v0.1.3 // indirect
|
github.com/caddyserver/zerossl v0.1.3 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/coder/websocket v1.8.12 // indirect
|
||||||
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
github.com/coreos/go-iptables v0.7.1-0.20240112124308-65c67c9f46e6 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
|
||||||
@@ -79,6 +78,7 @@ require (
|
|||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
|
github.com/godbus/dbus/v5 v5.1.1-0.20230522191255-76236955d466 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||||
github.com/google/btree v1.1.3 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
@@ -123,14 +123,16 @@ require (
|
|||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap/exp v0.3.0 // indirect
|
go.uber.org/zap/exp v0.3.0 // indirect
|
||||||
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
|
||||||
golang.org/x/sync v0.14.0 // indirect
|
golang.org/x/sync v0.13.0 // indirect
|
||||||
golang.org/x/term v0.32.0 // indirect
|
golang.org/x/term v0.31.0 // indirect
|
||||||
golang.org/x/text v0.25.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
golang.org/x/time v0.9.0 // indirect
|
golang.org/x/time v0.9.0 // indirect
|
||||||
golang.org/x/tools v0.33.0 // indirect
|
golang.org/x/tools v0.32.0 // indirect
|
||||||
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect
|
||||||
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
golang.zx2c4.com/wireguard/windows v0.5.3 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
lukechampine.com/blake3 v1.3.0 // indirect
|
lukechampine.com/blake3 v1.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//replace github.com/sagernet/sing => ../sing
|
||||||
|
|||||||
72
go.sum
72
go.sum
@@ -125,12 +125,12 @@ github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos
|
|||||||
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
github.com/metacubex/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
|
github.com/metacubex/utls v1.7.0-alpha.2 h1:kLRg6zDV12R1uclL5qW9Tx4RD6ztGIIrTZWY5zrJXCg=
|
||||||
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
github.com/metacubex/utls v1.7.0-alpha.2/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||||
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
github.com/mholt/acmez/v3 v3.1.2 h1:auob8J/0FhmdClQicvJvuDavgd5ezwLBfKuYmynhYzc=
|
||||||
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
github.com/mholt/acmez/v3 v3.1.2/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
|
||||||
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
|
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
|
||||||
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
|
github.com/miekg/dns v1.1.65/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
|
||||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
@@ -165,29 +165,29 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
|
|||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
|
github.com/sagernet/quic-go v0.51.0-beta.1 h1:bDMzfFlUHvMiKYvvPbOTKLWOYJFaACpssQYqsViQknI=
|
||||||
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
github.com/sagernet/quic-go v0.51.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo=
|
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f h1:lttLhNtFuMItQcTD29QP6aBS8kR1UhG7zZ+pwzTYkFM=
|
||||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
|
||||||
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||||
github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
|
github.com/sagernet/sing-quic v0.4.1-0.20250505055457-ae141e8be88a h1:5W8tI4JnKtpDkW23yekxjGA/Blo6oU4frsmOeQu7/9k=
|
||||||
github.com/sagernet/sing-quic v0.5.0-beta.2/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
|
github.com/sagernet/sing-quic v0.4.1-0.20250505055457-ae141e8be88a/go.mod h1:6K3ESuaXFTjz2Dv6/PNQqg5UK0J1ZO49rqrU2ScZBKg=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
|
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
|
||||||
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||||
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935 h1:wha4BG4mrEKaIoouVyiU5BcPfKD1n0LkiL4vqdjaVps=
|
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 h1:6H4BZaTqKI3YcDMyTV3E576LuJM4S4wY99xoq2T1ECw=
|
||||||
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI=
|
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8 h1:zW+zAOCxUIqBCgnZiPovt1uQ3S+zBS+w0NGp+1zITGA=
|
||||||
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||||
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
|
github.com/sagernet/tailscale v1.80.3-mod.4 h1:9UgYq8m9mwX5dbTbueVxbRh+bq7AayxemJGM2PkJQnE=
|
||||||
github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
github.com/sagernet/tailscale v1.80.3-mod.4/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
|
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
|
||||||
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
github.com/sagernet/wireguard-go v0.0.1-beta.7/go.mod h1:jGXij2Gn2wbrWuYNUmmNhf1dwcZtvyAvQoe8Xd8MbUo=
|
||||||
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc=
|
||||||
@@ -263,21 +263,21 @@ go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wus
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
|
||||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
|
||||||
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
|
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
|
||||||
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
|
||||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -286,20 +286,20 @@ golang.org/x/sys v0.0.0-20220817070843-5a390386f1f2/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
||||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
|
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
|
||||||
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
|
golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ package include
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/sagernet/sing-box"
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/inbound"
|
"github.com/sagernet/sing-box/adapter/inbound"
|
||||||
"github.com/sagernet/sing-box/adapter/outbound"
|
"github.com/sagernet/sing-box/adapter/outbound"
|
||||||
"github.com/sagernet/sing-box/adapter/service"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/dns/transport"
|
"github.com/sagernet/sing-box/dns/transport"
|
||||||
@@ -35,15 +33,9 @@ import (
|
|||||||
"github.com/sagernet/sing-box/protocol/tun"
|
"github.com/sagernet/sing-box/protocol/tun"
|
||||||
"github.com/sagernet/sing-box/protocol/vless"
|
"github.com/sagernet/sing-box/protocol/vless"
|
||||||
"github.com/sagernet/sing-box/protocol/vmess"
|
"github.com/sagernet/sing-box/protocol/vmess"
|
||||||
"github.com/sagernet/sing-box/service/resolved"
|
|
||||||
"github.com/sagernet/sing-box/service/ssmapi"
|
|
||||||
E "github.com/sagernet/sing/common/exceptions"
|
E "github.com/sagernet/sing/common/exceptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Context(ctx context.Context) context.Context {
|
|
||||||
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())
|
|
||||||
}
|
|
||||||
|
|
||||||
func InboundRegistry() *inbound.Registry {
|
func InboundRegistry() *inbound.Registry {
|
||||||
registry := inbound.NewRegistry()
|
registry := inbound.NewRegistry()
|
||||||
|
|
||||||
@@ -118,7 +110,6 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
|||||||
hosts.RegisterTransport(registry)
|
hosts.RegisterTransport(registry)
|
||||||
local.RegisterTransport(registry)
|
local.RegisterTransport(registry)
|
||||||
fakeip.RegisterTransport(registry)
|
fakeip.RegisterTransport(registry)
|
||||||
resolved.RegisterTransport(registry)
|
|
||||||
|
|
||||||
registerQUICTransports(registry)
|
registerQUICTransports(registry)
|
||||||
registerDHCPTransport(registry)
|
registerDHCPTransport(registry)
|
||||||
@@ -127,17 +118,6 @@ func DNSTransportRegistry() *dns.TransportRegistry {
|
|||||||
return registry
|
return registry
|
||||||
}
|
}
|
||||||
|
|
||||||
func ServiceRegistry() *service.Registry {
|
|
||||||
registry := service.NewRegistry()
|
|
||||||
|
|
||||||
resolved.RegisterService(registry)
|
|
||||||
ssmapi.RegisterService(registry)
|
|
||||||
|
|
||||||
registerDERPService(registry)
|
|
||||||
|
|
||||||
return registry
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerStubForRemovedInbounds(registry *inbound.Registry) {
|
func registerStubForRemovedInbounds(registry *inbound.Registry) {
|
||||||
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
inbound.Register[option.ShadowsocksInboundOptions](registry, C.TypeShadowsocksR, func(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.ShadowsocksInboundOptions) (adapter.Inbound, error) {
|
||||||
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
|
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ package include
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/service"
|
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/protocol/tailscale"
|
"github.com/sagernet/sing-box/protocol/tailscale"
|
||||||
"github.com/sagernet/sing-box/service/derp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
||||||
@@ -17,7 +15,3 @@ func registerTailscaleEndpoint(registry *endpoint.Registry) {
|
|||||||
func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
||||||
tailscale.RegistryTransport(registry)
|
tailscale.RegistryTransport(registry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerDERPService(registry *service.Registry) {
|
|
||||||
derp.Register(registry)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
|
|
||||||
"github.com/sagernet/sing-box/adapter"
|
"github.com/sagernet/sing-box/adapter"
|
||||||
"github.com/sagernet/sing-box/adapter/endpoint"
|
"github.com/sagernet/sing-box/adapter/endpoint"
|
||||||
"github.com/sagernet/sing-box/adapter/service"
|
|
||||||
C "github.com/sagernet/sing-box/constant"
|
C "github.com/sagernet/sing-box/constant"
|
||||||
"github.com/sagernet/sing-box/dns"
|
"github.com/sagernet/sing-box/dns"
|
||||||
"github.com/sagernet/sing-box/log"
|
"github.com/sagernet/sing-box/log"
|
||||||
@@ -26,9 +25,3 @@ func registerTailscaleTransport(registry *dns.TransportRegistry) {
|
|||||||
return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)
|
return nil, E.New(`Tailscale is not included in this build, rebuild with -tags with_tailscale`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func registerDERPService(registry *service.Registry) {
|
|
||||||
service.Register[option.DERPServiceOptions](registry, C.TypeDERP, func(ctx context.Context, logger log.ContextLogger, tag string, options option.DERPServiceOptions) (adapter.Service, error) {
|
|
||||||
return nil, E.New(`DERP is not included in this build, rebuild with -tags with_tailscale`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ nav:
|
|||||||
- DHCP: configuration/dns/server/dhcp.md
|
- DHCP: configuration/dns/server/dhcp.md
|
||||||
- FakeIP: configuration/dns/server/fakeip.md
|
- FakeIP: configuration/dns/server/fakeip.md
|
||||||
- Tailscale: configuration/dns/server/tailscale.md
|
- Tailscale: configuration/dns/server/tailscale.md
|
||||||
- Resolved: configuration/dns/server/resolved.md
|
|
||||||
- DNS Rule: configuration/dns/rule.md
|
- DNS Rule: configuration/dns/rule.md
|
||||||
- DNS Rule Action: configuration/dns/rule_action.md
|
- DNS Rule Action: configuration/dns/rule_action.md
|
||||||
- FakeIP: configuration/dns/fakeip.md
|
- FakeIP: configuration/dns/fakeip.md
|
||||||
@@ -170,11 +169,6 @@ nav:
|
|||||||
- DNS: configuration/outbound/dns.md
|
- DNS: configuration/outbound/dns.md
|
||||||
- Selector: configuration/outbound/selector.md
|
- Selector: configuration/outbound/selector.md
|
||||||
- URLTest: configuration/outbound/urltest.md
|
- URLTest: configuration/outbound/urltest.md
|
||||||
- Service:
|
|
||||||
- configuration/service/index.md
|
|
||||||
- DERP: configuration/service/derp.md
|
|
||||||
- Resolved: configuration/service/resolved.md
|
|
||||||
- SSM API: configuration/service/ssm-api.md
|
|
||||||
markdown_extensions:
|
markdown_extensions:
|
||||||
- pymdownx.inlinehilite
|
- pymdownx.inlinehilite
|
||||||
- pymdownx.snippets
|
- pymdownx.snippets
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ type LegacyDNSFakeIPOptions struct {
|
|||||||
type DNSTransportOptionsRegistry interface {
|
type DNSTransportOptionsRegistry interface {
|
||||||
CreateOptions(transportType string) (any, bool)
|
CreateOptions(transportType string) (any, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type _DNSServerOptions struct {
|
type _DNSServerOptions struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) err
|
|||||||
}
|
}
|
||||||
registry := service.FromContext[EndpointOptionsRegistry](ctx)
|
registry := service.FromContext[EndpointOptionsRegistry](ctx)
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
return E.New("missing endpoint fields registry in context")
|
return E.New("missing Endpoint fields registry in context")
|
||||||
}
|
}
|
||||||
options, loaded := registry.CreateOptions(h.Type)
|
options, loaded := registry.CreateOptions(h.Type)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
return E.New("unknown endpoint type: ", h.Type)
|
return E.New("unknown inbound type: ", h.Type)
|
||||||
}
|
}
|
||||||
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options)
|
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Endpoint)(h), options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro
|
|||||||
}
|
}
|
||||||
registry := service.FromContext[InboundOptionsRegistry](ctx)
|
registry := service.FromContext[InboundOptionsRegistry](ctx)
|
||||||
if registry == nil {
|
if registry == nil {
|
||||||
return E.New("missing inbound fields registry in context")
|
return E.New("missing Inbound fields registry in context")
|
||||||
}
|
}
|
||||||
options, loaded := registry.CreateOptions(h.Type)
|
options, loaded := registry.CreateOptions(h.Type)
|
||||||
if !loaded {
|
if !loaded {
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ type _Options struct {
|
|||||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||||
Route *RouteOptions `json:"route,omitempty"`
|
Route *RouteOptions `json:"route,omitempty"`
|
||||||
Services []Service `json:"services,omitempty"`
|
|
||||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,6 @@ type DialerOptions struct {
|
|||||||
type _DomainResolveOptions struct {
|
type _DomainResolveOptions struct {
|
||||||
Server string `json:"server"`
|
Server string `json:"server"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Timeout badoption.Duration `json:"timeout,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
@@ -103,7 +102,6 @@ func (o DomainResolveOptions) MarshalJSON() ([]byte, error) {
|
|||||||
if o.Server == "" {
|
if o.Server == "" {
|
||||||
return []byte("{}"), nil
|
return []byte("{}"), nil
|
||||||
} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
|
} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
|
||||||
o.Timeout == 0 &&
|
|
||||||
!o.DisableCache &&
|
!o.DisableCache &&
|
||||||
o.RewriteTTL == nil &&
|
o.RewriteTTL == nil &&
|
||||||
o.ClientSubnet == nil {
|
o.ClientSubnet == nil {
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package option
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/netip"
|
|
||||||
|
|
||||||
"github.com/sagernet/sing/common"
|
|
||||||
"github.com/sagernet/sing/common/json"
|
|
||||||
"github.com/sagernet/sing/common/json/badoption"
|
|
||||||
)
|
|
||||||
|
|
||||||
type _ResolvedServiceOptions struct {
|
|
||||||
ListenOptions
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResolvedServiceOptions _ResolvedServiceOptions
|
|
||||||
|
|
||||||
func (r ResolvedServiceOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
|
||||||
if r.Listen != nil && netip.Addr(*r.Listen) == (netip.AddrFrom4([4]byte{127, 0, 0, 53})) {
|
|
||||||
r.Listen = nil
|
|
||||||
}
|
|
||||||
if r.ListenPort == 53 {
|
|
||||||
r.ListenPort = 0
|
|
||||||
}
|
|
||||||
return json.MarshalContext(ctx, (*_ResolvedServiceOptions)(&r))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ResolvedServiceOptions) UnmarshalJSONContext(ctx context.Context, bytes []byte) error {
|
|
||||||
err := json.UnmarshalContextDisallowUnknownFields(ctx, bytes, (*_ResolvedServiceOptions)(r))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if r.Listen == nil {
|
|
||||||
r.Listen = (*badoption.Addr)(common.Ptr(netip.AddrFrom4([4]byte{127, 0, 0, 53})))
|
|
||||||
}
|
|
||||||
if r.ListenPort == 0 {
|
|
||||||
r.ListenPort = 53
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResolvedDNSServerOptions struct {
|
|
||||||
Service string `json:"service"`
|
|
||||||
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
|
||||||
// NDots int `json:"ndots,omitempty"`
|
|
||||||
// Timeout badoption.Duration `json:"timeout,omitempty"`
|
|
||||||
// Attempts int `json:"attempts,omitempty"`
|
|
||||||
// Rotate bool `json:"rotate,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -158,7 +158,6 @@ type RawRouteOptionsActionOptions struct {
|
|||||||
|
|
||||||
TLSFragment bool `json:"tls_fragment,omitempty"`
|
TLSFragment bool `json:"tls_fragment,omitempty"`
|
||||||
TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"`
|
TLSFragmentFallbackDelay badoption.Duration `json:"tls_fragment_fallback_delay,omitempty"`
|
||||||
TLSRecordFragment bool `json:"tls_record_fragment,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type RouteOptionsActionOptions RawRouteOptionsActionOptions
|
type RouteOptionsActionOptions RawRouteOptionsActionOptions
|
||||||
@@ -171,16 +170,12 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
|
|||||||
if *r == (RouteOptionsActionOptions{}) {
|
if *r == (RouteOptionsActionOptions{}) {
|
||||||
return E.New("empty route option action")
|
return E.New("empty route option action")
|
||||||
}
|
}
|
||||||
if r.TLSFragment && r.TLSRecordFragment {
|
|
||||||
return E.New("`tls_fragment` and `tls_record_fragment` are mutually exclusive")
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DNSRouteActionOptions struct {
|
type DNSRouteActionOptions struct {
|
||||||
Server string `json:"server,omitempty"`
|
Server string `json:"server,omitempty"`
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Timeout badoption.Duration `json:"timeout,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
@@ -188,7 +183,6 @@ type DNSRouteActionOptions struct {
|
|||||||
|
|
||||||
type _DNSRouteOptionsActionOptions struct {
|
type _DNSRouteOptionsActionOptions struct {
|
||||||
Strategy DomainStrategy `json:"strategy,omitempty"`
|
Strategy DomainStrategy `json:"strategy,omitempty"`
|
||||||
Timeout badoption.Duration `json:"timeout,omitempty"`
|
|
||||||
DisableCache bool `json:"disable_cache,omitempty"`
|
DisableCache bool `json:"disable_cache,omitempty"`
|
||||||
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
|
||||||
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user