Compare commits

..

84 Commits

Author SHA1 Message Date
世界
1c36072120 documentation: Bump version 2025-05-12 18:12:57 +08:00
世界
6e9258bede Add missing accept_routes option for Tailscale 2025-05-12 18:12:57 +08:00
世界
b0f4008071 Add TLS record fragment support 2025-05-12 17:43:54 +08:00
世界
3f26b7b4e9 release: Update Go to 1.24.3 2025-05-12 17:43:54 +08:00
世界
dbab3a2178 Fix set edns0 client subnet 2025-05-12 17:43:53 +08:00
世界
dbf17cca71 Update minor dependencies 2025-05-12 17:43:53 +08:00
世界
4a0fe88217 Update certmagic and providers 2025-05-12 17:43:53 +08:00
世界
4a62401f9e Update protobuf and grpc 2025-05-12 17:43:52 +08:00
世界
09edc643cb Add control options for listeners 2025-05-12 17:43:52 +08:00
世界
4be4220f20 Update quic-go to v0.51.0 2025-05-12 17:43:52 +08:00
世界
d1b0c967aa Update utls to v1.7.0 2025-05-12 17:43:52 +08:00
世界
7608266ea7 Handle EDNS version downgrade 2025-05-12 17:43:22 +08:00
世界
081dad8f57 documentation: Fix anytls padding scheme description 2025-05-12 17:43:21 +08:00
安容
3ff990a6a3 Report invalid DNS address early 2025-05-12 17:43:21 +08:00
世界
9ff3da2bdb Fix wireguard listen_port 2025-05-12 17:43:20 +08:00
世界
92c20a545d clash-api: Add more meta api 2025-05-12 17:43:20 +08:00
世界
2e75cb3c22 Fix DNS lookup 2025-05-12 17:43:20 +08:00
世界
4414dc6bee Fix fetch ECH configs 2025-05-12 17:43:19 +08:00
reletor
7d11245a90 documentation: Minor fixes 2025-05-12 17:43:19 +08:00
caelansar
744e9b4577 Fix callback deletion in UDP transport 2025-05-12 17:43:18 +08:00
世界
153a262f35 documentation: Try to make the play review happy 2025-05-12 17:43:18 +08:00
世界
39d2fb5467 Fix missing handling of legacy domain_strategy options 2025-05-12 17:43:18 +08:00
世界
85a2a5c169 Improve local DNS server 2025-05-12 17:43:18 +08:00
anytls
575b5dfb10 Update anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:17 +08:00
世界
f19c500b4f Fix DNS dialer 2025-05-12 17:43:17 +08:00
世界
8e5958a7e4 release: Skip override version for iOS 2025-05-12 17:43:17 +08:00
iikira
dc5e6ed488 Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-05-12 17:43:16 +08:00
ReleTor
049c18377c Fix fetch ECH configs 2025-05-12 17:43:16 +08:00
世界
870f76a91e Allow direct outbounds without domain_resolver 2025-05-12 17:43:15 +08:00
世界
d1a945f180 Fix Tailscale dialer 2025-05-12 17:43:15 +08:00
dyhkwong
6d56bc1528 Fix DNS over QUIC stream close 2025-05-12 17:43:14 +08:00
anytls
25e2d670e7 Update anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:14 +08:00
Rambling2076
be8419fa84 Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-05-12 17:43:14 +08:00
世界
27acc99c35 Fail when default DNS server not found 2025-05-12 17:43:14 +08:00
世界
6053a3bad1 Update gVisor to 20250319.0 2025-05-12 17:43:14 +08:00
世界
c63a58c0fb Explicitly reject detour to empty direct outbounds 2025-05-12 17:43:13 +08:00
世界
cc4354b18a Add netns support 2025-05-12 17:43:13 +08:00
世界
8344653443 Add wildcard name support for predefined records 2025-05-12 17:43:13 +08:00
世界
26f185aa7c Remove map usage in options 2025-05-12 17:43:13 +08:00
世界
a69007f1a2 Fix unhandled DNS loop 2025-05-12 17:43:13 +08:00
世界
20729defd6 Add wildcard-sni support for shadow-tls inbound 2025-05-12 17:43:12 +08:00
k9982874
ff751f8504 Add ntp protocol sniffing 2025-05-12 17:43:11 +08:00
世界
689c229fe6 option: Fix marshal legacy DNS options 2025-05-12 17:43:11 +08:00
世界
c4cdb68d10 Make domain_resolver optional when only one DNS server is configured 2025-05-12 17:43:11 +08:00
世界
dd99a45df6 Fix DNS lookup context pollution 2025-05-12 17:43:11 +08:00
世界
cbe077eaa9 Fix http3 DNS server connecting to wrong address 2025-05-12 17:43:10 +08:00
Restia-Ashbell
47fd88f450 documentation: Fix typo 2025-05-12 17:43:10 +08:00
anytls
5fdfba6816 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:10 +08:00
k9982874
e38de3bd8f Fix hosts DNS server 2025-05-12 17:43:10 +08:00
世界
6f9d507ab3 Fix UDP DNS server crash 2025-05-12 17:43:09 +08:00
世界
567de6aa88 documentation: Fix missing ip_accept_any DNS rule option 2025-05-12 17:43:09 +08:00
世界
5fd7fba75f Fix anytls dialer usage 2025-05-12 17:43:09 +08:00
世界
d80f81157c Move predefined DNS server to rule action 2025-05-12 17:43:09 +08:00
世界
133e57e85f Fix domain resolver on direct outbound 2025-05-12 17:43:08 +08:00
Zephyruso
f854586ac8 Fix missing AnyTLS display name 2025-05-12 17:43:08 +08:00
anytls
a3a54b6e91 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-05-12 17:43:08 +08:00
Estel
fa5dbfcca8 documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-05-12 17:43:07 +08:00
TargetLocked
35d5a1f6a6 Fix parsing legacy DNS options 2025-05-12 17:43:07 +08:00
世界
9f26fea1fc Fix DNS fallback 2025-05-12 17:43:07 +08:00
世界
5c71baa23c documentation: Fix missing hosts DNS server 2025-05-12 17:43:06 +08:00
anytls
a75772d01e Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-05-12 17:43:06 +08:00
ReleTor
ae263e2da3 documentation: Minor fixes 2025-05-12 17:43:05 +08:00
libtry486
031b6de8b5 documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-05-12 17:43:05 +08:00
Alireza Ahmadi
6aef4ec1c6 Fix Outbound deadlock 2025-05-12 17:43:05 +08:00
世界
61b7666fd5 documentation: Fix AnyTLS doc 2025-05-12 17:43:04 +08:00
anytls
ea5339285e Add AnyTLS protocol 2025-05-12 17:43:04 +08:00
世界
d38d58ff6e Migrate to stdlib ECH support 2025-05-12 17:43:03 +08:00
世界
60570b966b Add fallback local DNS server for iOS 2025-05-12 17:43:03 +08:00
世界
195e859bf7 Get darwin local DNS server from libresolv 2025-05-12 17:43:02 +08:00
世界
0ce1f21794 Improve resolve action 2025-05-12 17:43:02 +08:00
世界
ec5eba1d25 Fix toolchain version 2025-05-12 17:43:02 +08:00
世界
682a95c55e Add back port hopping to hysteria 1 2025-05-12 17:43:01 +08:00
xchacha20-poly1305
34283f914a Remove single quotes of raw Moziila certs 2025-05-12 17:43:01 +08:00
世界
fcce280a50 Add Tailscale endpoint 2025-05-12 17:42:41 +08:00
世界
16e541f50e Build legacy binaries with latest Go 2025-05-07 15:13:00 +08:00
世界
a22849171a documentation: Remove outdated icons 2025-05-07 15:13:00 +08:00
世界
0597a0a8d7 documentation: Certificate store 2025-05-07 15:12:59 +08:00
世界
791d9581bc documentation: TLS fragment 2025-05-07 15:12:59 +08:00
世界
b4d9e7417a documentation: Outbound domain resolver 2025-05-07 15:12:59 +08:00
世界
e429db2ca5 documentation: Refactor DNS 2025-05-07 15:12:59 +08:00
世界
a1b717ecea Add certificate store 2025-05-07 15:12:59 +08:00
世界
c37b74730d Add TLS fragment support 2025-05-07 15:12:59 +08:00
世界
6bd0a372c1 refactor: Outbound domain resolver 2025-05-07 15:12:58 +08:00
世界
6752ade2a0 refactor: DNS 2025-05-07 15:12:50 +08:00
138 changed files with 726 additions and 4914 deletions

View File

@@ -8,15 +8,11 @@
--deb-field "Bug: https://github.com/SagerNet/sing-box/issues"
--no-deb-generate-changes
--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/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.fish=/usr/share/fish/vendor_completions.d/sing-box.fish

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -109,7 +109,7 @@ jobs:
if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Cache Legacy Go
if: matrix.require_legacy_go
id: cache-legacy-go
@@ -294,7 +294,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -374,7 +374,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Setup Android NDK
id: setup-ndk
uses: nttld/setup-ndk@v1
@@ -472,15 +472,15 @@ jobs:
if: matrix.if
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app
sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Set tag
if: matrix.if
run: |-
@@ -615,7 +615,7 @@ jobs:
path: 'dist'
upload:
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
needs:
- calculate_version

View File

@@ -28,7 +28,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: golangci-lint
uses: golangci/golangci-lint-action@v6
with:

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Check input version
if: github.event_name == 'workflow_dispatch'
run: |-
@@ -66,7 +66,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: ^1.24
go-version: ^1.24.3
- name: Setup Android NDK
if: matrix.os == 'android'
uses: nttld/setup-ndk@v1
@@ -80,7 +80,7 @@ jobs:
- name: Set build tags
run: |
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}"
- name: Build
run: |

View File

@@ -55,12 +55,6 @@ nfpms:
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/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
dst: /usr/share/bash-completion/completions/sing-box.bash

View File

@@ -136,12 +136,6 @@ nfpms:
dst: /usr/lib/systemd/system/sing-box.service
- src: release/config/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
dst: /usr/share/bash-completion/completions/sing-box.bash

View File

@@ -1,10 +1,11 @@
NAME = sing-box
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)
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="
MAIN_PARAMS = $(PARAMS) -tags "$(TAGS)"
@@ -108,16 +109,6 @@ upload_ios_app_store:
cd ../sing-box-for-apple && \
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
build_macos:
@@ -185,16 +176,6 @@ upload_tvos_app_store:
cd ../sing-box-for-apple && \
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
update_apple_version:

View File

@@ -3,14 +3,11 @@ package adapter
import (
"context"
"net/netip"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/sagernet/sing/service"
"github.com/miekg/dns"
)
@@ -34,32 +31,11 @@ type DNSClient interface {
}
type DNSQueryOptions struct {
Transport DNSTransport
Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy
Timeout time.Duration
DisableCache bool
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
Transport DNSTransport
Strategy C.DomainStrategy
DisableCache bool
RewriteTTL *uint32
ClientSubnet netip.Prefix
}
type RDRCStore interface {
@@ -73,7 +49,6 @@ type DNSTransport interface {
Type() string
Tag() string
Dependencies() []string
HasDetour() bool
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
}

View File

@@ -7,7 +7,7 @@ import (
)
type FakeIPStore interface {
SimpleLifecycle
Service
Contains(address netip.Addr) bool
Create(domain string, isIPv6 bool) (netip.Addr, error)
Lookup(address netip.Addr) (string, bool)

View File

@@ -53,11 +53,11 @@ type InboundContext struct {
// sniffer
Protocol string
Domain string
Client string
SniffContext any
SniffError error
Protocol string
Domain string
Client string
SniffContext any
PacketSniffError error
// cache

View File

@@ -37,14 +37,13 @@ func NewManager(logger log.ContextLogger, registry adapter.InboundRegistry, endp
func (m *Manager) Start(stage adapter.StartStage) error {
m.access.Lock()
defer m.access.Unlock()
if m.started && m.stage >= stage {
panic("already started")
}
m.started = true
m.stage = stage
inbounds := m.inbounds
m.access.Unlock()
for _, inbound := range inbounds {
for _, inbound := range m.inbounds {
err := adapter.LegacyStart(inbound, stage)
if err != nil {
return E.Cause(err, stage, " inbound/", inbound.Type(), "[", inbound.Tag(), "]")

View File

@@ -2,11 +2,6 @@ package adapter
import E "github.com/sagernet/sing/common/exceptions"
type SimpleLifecycle interface {
Start() error
Close() error
}
type StartStage uint8
const (

View File

@@ -28,14 +28,14 @@ func LegacyStart(starter any, stage StartStage) error {
}
type lifecycleServiceWrapper struct {
SimpleLifecycle
Service
name string
}
func NewLifecycleService(service SimpleLifecycle, name string) LifecycleService {
func NewLifecycleService(service Service, name string) LifecycleService {
return &lifecycleServiceWrapper{
SimpleLifecycle: service,
name: name,
Service: service,
name: name,
}
}
@@ -44,9 +44,9 @@ func (l *lifecycleServiceWrapper) Name() string {
}
func (l *lifecycleServiceWrapper) Start(stage StartStage) error {
return LegacyStart(l.SimpleLifecycle, stage)
return LegacyStart(l.Service, stage)
}
func (l *lifecycleServiceWrapper) Close() error {
return l.SimpleLifecycle.Close()
return l.Service.Close()
}

View File

@@ -11,7 +11,7 @@ type HeadlessRule interface {
type Rule interface {
HeadlessRule
SimpleLifecycle
Service
Type() string
Action() RuleAction
}

View File

@@ -1,27 +1,6 @@
package adapter
import (
"context"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
)
type Service interface {
Lifecycle
Type() string
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
Start() error
Close() error
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -3,6 +3,6 @@ package adapter
import "time"
type TimeService interface {
SimpleLifecycle
Service
TimeFunc() func() time.Time
}

125
box.go
View File

@@ -12,7 +12,6 @@ import (
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"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/dialer"
"github.com/sagernet/sing-box/common/taskmonitor"
@@ -35,23 +34,22 @@ import (
"github.com/sagernet/sing/service/pause"
)
var _ adapter.SimpleLifecycle = (*Box)(nil)
var _ adapter.Service = (*Box)(nil)
type Box struct {
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
service *boxService.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
internalService []adapter.LifecycleService
done chan struct{}
createdAt time.Time
logFactory log.Factory
logger log.ContextLogger
network *route.NetworkManager
endpoint *endpoint.Manager
inbound *inbound.Manager
outbound *outbound.Manager
dnsTransport *dns.TransportManager
dnsRouter *dns.Router
connection *route.ConnectionManager
router *route.Router
services []adapter.LifecycleService
done chan struct{}
}
type Options struct {
@@ -66,7 +64,6 @@ func Context(
outboundRegistry adapter.OutboundRegistry,
endpointRegistry adapter.EndpointRegistry,
dnsTransportRegistry adapter.DNSTransportRegistry,
serviceRegistry adapter.ServiceRegistry,
) context.Context {
if service.FromContext[option.InboundOptionsRegistry](ctx) == nil ||
service.FromContext[adapter.InboundRegistry](ctx) == nil {
@@ -87,10 +84,6 @@ func Context(
ctx = service.ContextWith[option.DNSTransportOptionsRegistry](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
}
@@ -106,7 +99,6 @@ func New(options Options) (*Box, error) {
inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx)
outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx)
dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx)
serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx)
if endpointRegistry == nil {
return nil, E.New("missing endpoint registry in context")
@@ -117,12 +109,6 @@ func New(options Options) (*Box, error) {
if outboundRegistry == nil {
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)
experimentalOptions := common.PtrValueOrDefault(options.Experimental)
@@ -156,7 +142,7 @@ func New(options Options) (*Box, error) {
return nil, E.Cause(err, "create log factory")
}
var internalServices []adapter.LifecycleService
var services []adapter.LifecycleService
certificateOptions := common.PtrValueOrDefault(options.Certificate)
if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem ||
len(certificateOptions.Certificate) > 0 ||
@@ -167,7 +153,7 @@ func New(options Options) (*Box, error) {
return nil, err
}
service.MustRegister[adapter.CertificateStore](ctx, certificateStore)
internalServices = append(internalServices, certificateStore)
services = append(services, certificateStore)
}
routeOptions := common.PtrValueOrDefault(options.Route)
@@ -176,12 +162,10 @@ func New(options Options) (*Box, error) {
inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager)
outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.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.InboundManager](ctx, inboundManager)
service.MustRegister[adapter.OutboundManager](ctx, outboundManager)
service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager)
service.MustRegister[adapter.ServiceManager](ctx, serviceManager)
dnsRouter := dns.NewRouter(ctx, logFactory, dnsOptions)
service.MustRegister[adapter.DNSRouter](ctx, dnsRouter)
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, "]")
}
}
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(
direct.NewOutbound(
ctx,
@@ -339,7 +305,7 @@ func New(options Options) (*Box, error) {
if needCacheFile {
cacheFile := cachefile.New(ctx, common.PtrValueOrDefault(experimentalOptions.CacheFile))
service.MustRegister[adapter.CacheFile](ctx, cacheFile)
internalServices = append(internalServices, cacheFile)
services = append(services, cacheFile)
}
if needClashAPI {
clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI)
@@ -350,7 +316,7 @@ func New(options Options) (*Box, error) {
}
router.AppendTracker(clashServer)
service.MustRegister[adapter.ClashServer](ctx, clashServer)
internalServices = append(internalServices, clashServer)
services = append(services, clashServer)
}
if needV2RayAPI {
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 {
router.AppendTracker(v2rayServer.StatsService())
internalServices = append(internalServices, v2rayServer)
services = append(services, v2rayServer)
service.MustRegister[adapter.V2RayServer](ctx, v2rayServer)
}
}
@@ -377,23 +343,22 @@ func New(options Options) (*Box, error) {
WriteToSystem: ntpOptions.WriteToSystem,
})
timeService.TimeService = ntpService
internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service"))
services = append(services, adapter.NewLifecycleService(ntpService, "ntp service"))
}
return &Box{
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
service: serviceManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
internalService: internalServices,
done: make(chan struct{}),
network: networkManager,
endpoint: endpointManager,
inbound: inboundManager,
outbound: outboundManager,
dnsTransport: dnsTransportManager,
dnsRouter: dnsRouter,
connection: connectionManager,
router: router,
createdAt: createdAt,
logFactory: logFactory,
logger: logFactory.Logger(),
services: services,
done: make(chan struct{}),
}, nil
}
@@ -443,11 +408,11 @@ func (s *Box) preStart() error {
if err != nil {
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 {
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 {
return err
}
@@ -463,27 +428,31 @@ func (s *Box) start() error {
if err != nil {
return err
}
err = adapter.StartNamed(adapter.StartStateStart, s.internalService)
err = adapter.StartNamed(adapter.StartStateStart, s.services)
if err != nil {
return err
}
err = adapter.Start(adapter.StartStateStart, s.inbound, s.endpoint, s.service)
err = s.inbound.Start(adapter.StartStateStart)
if err != nil {
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 {
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 {
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 {
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 {
return err
}
@@ -498,9 +467,9 @@ func (s *Box) Close() error {
close(s.done)
}
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 {
return E.Cause(err, "close ", lifecycleService.Name())
})

View File

@@ -105,7 +105,7 @@ func publishTestflight(ctx context.Context) error {
return err
}
tag := tagVersion.VersionString()
client := createClient(20 * time.Minute)
client := createClient(10 * time.Minute)
log.Info(tag, " list build IDs")
buildIDsResponse, _, err := client.TestFlight.ListBuildIDsForBetaGroup(ctx, groupID, nil)
@@ -145,7 +145,7 @@ func publishTestflight(ctx context.Context) error {
return err
}
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")
time.Sleep(15 * time.Second)
continue

View File

@@ -16,17 +16,15 @@ import (
)
var (
debugEnabled bool
target string
platform string
withTailscale bool
debugEnabled bool
target string
platform string
)
func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform")
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
}
func main() {
@@ -61,8 +59,8 @@ func init() {
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)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api")
iosTags = append(iosTags, "with_dhcp", "with_low_memory", "with_conntrack")
memcTags = append(memcTags, "with_tailscale")
debugTags = append(debugTags, "debug")
}
@@ -153,9 +151,7 @@ func buildApple() {
"-v",
"-target", bindTarget,
"-libname=box",
}
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
"-tags-macos=" + strings.Join(memcTags, ","),
}
if !debugEnabled {
@@ -165,9 +161,6 @@ func buildApple() {
}
tags := append(sharedTags, iosTags...)
if withTailscale {
tags = append(tags, memcTags...)
}
if debugEnabled {
tags = append(tags, debugTags...)
}

View File

@@ -7,6 +7,7 @@ import (
"strconv"
"time"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
@@ -67,5 +68,6 @@ func preRun(cmd *cobra.Command, args []string) {
if len(configPaths) == 0 && len(configDirectories) == 0 {
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())
}

View File

@@ -5,7 +5,7 @@ import (
"os"
"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"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
@@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
var rules []option.HeadlessRule
switch flagRuleSetConvertType {
case "adguard":
rules, err = adguard.ToOptions(reader, log.StdLogger())
rules, err = adguard.Convert(reader)
case "":
return E.New("source type is required")
default:

View File

@@ -6,10 +6,7 @@ import (
"strings"
"github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/spf13/cobra"
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
if err != nil {
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
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".srs") {
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
outputFile.Close()
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
}

View File

@@ -2,7 +2,6 @@ package adguard
import (
"bufio"
"bytes"
"io"
"net/netip"
"os"
@@ -10,10 +9,10 @@ import (
"strings"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata"
)
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
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)
var (
ruleLines []agdguardRuleLine
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
parseLine:
for scanner.Scan() {
ruleLine := scanner.Text()
if ruleLine == "" {
continue
}
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
continue
}
originRuleLine := ruleLine
@@ -96,7 +92,7 @@ parseLine:
}
if !ignored {
ignoredLines++
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
continue parseLine
}
}
@@ -124,35 +120,27 @@ parseLine:
ruleLine = ruleLine[1 : len(ruleLine)-1]
if ignoreIPCIDRRegexp(ruleLine) {
ignoredLines++
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
continue
}
isRegexp = true
} else {
if strings.Contains(ruleLine, "://") {
ruleLine = common.SubstringAfter(ruleLine, "://")
isSuffix = true
}
if strings.Contains(ruleLine, "/") {
ignoredLines++
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
log.Debug("ignored unsupported rule with path: ", ruleLine)
continue
}
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
if strings.Contains(ruleLine, "##") {
ignoredLines++
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
continue
}
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") ||
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
if strings.Contains(ruleLine, "#$#") {
ignoredLines++
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine)
continue
}
if strings.Contains(ruleLine, "~") {
ignoredLines++
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
continue
}
var domainCheck string
@@ -163,7 +151,7 @@ parseLine:
}
if ruleLine == "" {
ignoredLines++
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
continue
} else {
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
@@ -171,13 +159,13 @@ parseLine:
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
if ipErr == nil {
ignoredLines++
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
continue
}
if M.ParseSocksaddr(domainCheck).Port != 0 {
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
log.Debug("ignored unsupported rule with port: ", ruleLine)
} else {
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
}
ignoredLines++
continue
@@ -295,112 +283,10 @@ parseLine:
},
}
}
if ignoredLines > 0 {
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
}
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
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 {
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
ruleLine = ruleLine[12:]
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
ruleLine = ruleLine[13:]
} else if strings.HasPrefix(ruleLine, "^") {
ruleLine = ruleLine[1:]
} else {
return false
}
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
return parseErr == nil
}
func parseAdGuardHostLine(ruleLine string) (string, error) {

View File

@@ -7,15 +7,13 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing/common/logger"
"github.com/stretchr/testify/require"
)
func TestConverter(t *testing.T) {
t.Parallel()
ruleString := `||sagernet.org^$important
@@|sing-box.sagernet.org^$important
rules, err := Convert(strings.NewReader(`
||example.org^
|example.com^
example.net^
@@ -23,9 +21,10 @@ example.net^
||example.edu.tw^
|example.gov
example.arpa
@@|sagernet.example.org^
`
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
@@|sagernet.example.org|
||sagernet.org^$important
@@|sing-box.sagernet.org^$important
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
@@ -76,18 +75,15 @@ example.arpa
Domain: domain,
}), domain)
}
ruleFromOptions, err := FromOptions(rules)
require.NoError(t, err)
require.Equal(t, ruleString, string(ruleFromOptions))
}
func TestHosts(t *testing.T) {
t.Parallel()
rules, err := ToOptions(strings.NewReader(`
rules, err := Convert(strings.NewReader(`
127.0.0.1 localhost
::1 localhost #[IPv6]
0.0.0.0 google.com
`), logger.NOP())
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
@@ -114,10 +110,10 @@ func TestHosts(t *testing.T) {
func TestSimpleHosts(t *testing.T) {
t.Parallel()
rules, err := ToOptions(strings.NewReader(`
rules, err := Convert(strings.NewReader(`
example.com
www.example.org
`), logger.NOP())
`))
require.NoError(t, err)
require.Len(t, rules, 1)
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])

View File

@@ -66,17 +66,11 @@ func NewDefault(ctx context.Context, options option.DialerOptions) (*DefaultDial
interfaceFinder = control.NewDefaultInterfaceFinder()
}
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)
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)
}
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))
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() {
if platformInterface != nil {
networkStrategy = (*C.NetworkStrategy)(options.NetworkStrategy)
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
networkType = common.Map(options.NetworkType, option.InterfaceType.Build)
fallbackNetworkType = common.Map(options.FallbackNetworkType, option.InterfaceType.Build)
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 {
networkFallbackDelay = defaultOptions.FallbackDelay
}
if networkStrategy == nil {
networkStrategy = common.Ptr(C.NetworkStrategyDefault)
defaultNetworkStrategy = true
}
bindFunc := networkManager.ProtectFunc()
dialer.Control = control.Append(dialer.Control, bindFunc)
listener.Control = control.Append(listener.Control, bindFunc)

View File

@@ -89,7 +89,6 @@ func NewWithOptions(options Options) (N.Dialer, error) {
dnsQueryOptions = adapter.DNSQueryOptions{
Transport: transport,
Strategy: strategy,
Timeout: time.Duration(dialOptions.DomainResolver.Timeout),
DisableCache: dialOptions.DomainResolver.DisableCache,
RewriteTTL: dialOptions.DomainResolver.RewriteTTL,
ClientSubnet: dialOptions.DomainResolver.ClientSubnet.Build(netip.Prefix{}),

View File

@@ -10,7 +10,9 @@ import (
"sync"
"time"
"github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/bufio"
E "github.com/sagernet/sing/common/exceptions"
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
@@ -24,9 +26,7 @@ type slowOpenConn struct {
destination M.Socksaddr
conn net.Conn
create chan struct{}
done chan struct{}
access sync.Mutex
closeOnce sync.Once
err error
}
@@ -45,7 +45,6 @@ func DialSlowContext(dialer *tcpDialer, ctx context.Context, network string, des
network: network,
destination: destination,
create: make(chan struct{}),
done: make(chan struct{}),
}, nil
}
@@ -56,8 +55,8 @@ func (c *slowOpenConn) Read(b []byte) (n int, err error) {
if c.err != nil {
return 0, c.err
}
case <-c.done:
return 0, os.ErrClosed
case <-c.ctx.Done():
return 0, c.ctx.Err()
}
}
return c.conn.Read(b)
@@ -75,15 +74,12 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
return 0, c.err
}
return c.conn.Write(b)
case <-c.done:
return 0, os.ErrClosed
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 {
c.err = err
} else {
c.conn = conn
c.conn = nil
c.err = E.Cause(err, "dial tcp fast open")
}
n = len(b)
close(c.create)
@@ -91,13 +87,7 @@ func (c *slowOpenConn) Write(b []byte) (n int, err error) {
}
func (c *slowOpenConn) Close() error {
c.closeOnce.Do(func() {
close(c.done)
if c.conn != nil {
c.conn.Close()
}
})
return nil
return common.Close(c.conn)
}
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 {
return 0, c.err
}
case <-c.done:
return 0, c.err
case <-c.ctx.Done():
return 0, c.ctx.Err()
}
}
return bufio.Copy(w, c.conn)

View File

@@ -32,7 +32,6 @@ type Listener struct {
disablePacketOutput bool
setSystemProxy bool
systemProxySOCKS bool
tproxy bool
tcpListener net.Listener
systemProxy settings.SystemProxy
@@ -55,7 +54,6 @@ type Options struct {
DisablePacketOutput bool
SetSystemProxy bool
SystemProxySOCKS bool
TProxy bool
}
func New(
@@ -73,7 +71,6 @@ func New(
disablePacketOutput: options.DisablePacketOutput,
setSystemProxy: options.SetSystemProxy,
systemProxySOCKS: options.SystemProxySOCKS,
tproxy: options.TProxy,
}
}

View File

@@ -3,11 +3,9 @@ package listener
import (
"net"
"net/netip"
"syscall"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/control"
@@ -53,13 +51,6 @@ func (l *Listener) ListenTCP() (net.Listener, error) {
}
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) {
if l.listenOptions.TCPFastOpen {
var tfoConfig tfo.ListenConfig

View File

@@ -5,10 +5,8 @@ import (
"net"
"net/netip"
"os"
"syscall"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/redir"
"github.com/sagernet/sing/common/buf"
"github.com/sagernet/sing/common/control"
E "github.com/sagernet/sing/common/exceptions"
@@ -38,13 +36,6 @@ func (l *Listener) ListenUDP() (net.PacketConn, error) {
if !udpFragment {
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) {
return listenConfig.ListenPacket(l.ctx, M.NetworkFromNetAddr(N.NetworkUDP, bindAddr.Addr), bindAddr.String())
})

View File

@@ -76,8 +76,6 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
// rup8(sizeof(xtcpcb_n))
itemSize += 208
}
var fallbackUDPProcess string
// skip the first xinpgen(24 bytes) block
for i := 24; i+itemSize <= len(buf); i += itemSize {
// 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]
var srcIP netip.Addr
srcIsIPv4 := false
switch {
case flag&0x1 > 0 && isIPv4:
// ipv4
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
srcIsIPv4 = true
case flag&0x2 > 0 && !isIPv4:
// ipv6
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
}
if ip == srcIP {
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
if ip != srcIP {
continue
}
// udp packet connection may be not equal with srcIP
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
pid := readNativeUint32(buf[so+68 : so+72])
fallbackUDPProcess, _ = getExecPathFromPID(pid)
}
}
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
return fallbackUDPProcess, nil
// xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
}
return "", ErrNotFound

View File

@@ -12,7 +12,7 @@ import (
"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)
if err == nil {
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 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_TRANSPARENT, 1)
}
if isUDP {
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
}
if err == nil {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IP, syscall.IP_RECVORIGDSTADDR, 1)
}
if err == nil && isIPv6 {
err = syscall.SetsockoptInt(int(fd), syscall.SOL_IPV6, unix.IPV6_RECVORIGDSTADDR, 1)
}
return err
}

View File

@@ -9,7 +9,7 @@ import (
"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
}

View File

@@ -215,15 +215,16 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
case ruleItemWIFIBSSID:
rule.WIFIBSSID, err = readRuleItemString(reader)
case ruleItemAdGuardDomain:
if recover {
err = E.New("unable to decompile binary AdGuard rules to rule-set")
return
}
var matcher *domain.AdGuardMatcher
matcher, err = domain.ReadAdGuardMatcher(reader)
if err != nil {
return
}
rule.AdGuardDomainMatcher = matcher
if recover {
rule.AdGuardDomain = matcher.Dump()
}
case ruleItemNetworkType:
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
case ruleItemNetworkIsExpensive:

View File

@@ -5,13 +5,13 @@ package tls
import (
"context"
"crypto/tls"
"os"
"strings"
"github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/caddyserver/certmagic"
"github.com/libdns/alidns"
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
return nil
}
type acmeLogWriter struct {
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) {
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.Service, error) {
var acmeServer string
switch options.Provider {
case "", "letsencrypt":
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
} else {
storage = certmagic.Default.Storage
}
zapLogger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig()),
&acmeLogWriter{logger: logger},
zap.DebugLevel,
))
config := &certmagic.Config{
DefaultServerName: options.DefaultServerName,
Storage: storage,
Logger: zapLogger,
Logger: zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
)),
}
acmeConfig := certmagic.ACMEIssuer{
CA: acmeServer,
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: zapLogger,
Logger: config.Logger,
}
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
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) {
return config, nil
},
Logger: zapLogger,
})
config = certmagic.New(cache, *config)
var tlsConfig *tls.Config

View File

@@ -9,9 +9,8 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option"
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`)
}

View File

@@ -25,7 +25,7 @@ import (
"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
if len(options.ECH.Config) > 0 {
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 {
return nil, E.New("invalid ECH configs pem")
}
clientConfig.SetECHConfigList(block.Bytes)
return clientConfig, nil
tlsConfig.EncryptedClientHelloConfigList = block.Bytes
return &STDClientConfig{tlsConfig}, nil
} else {
return &ECHClientConfig{
ECHCapableConfig: clientConfig,
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
return &STDECHClientConfig{
STDClientConfig: STDClientConfig{tlsConfig},
dnsRouter: service.FromContext[adapter.DNSRouter](ctx),
}, nil
}
}
@@ -102,15 +102,15 @@ func reloadECHKeys(echKeyPath string, tlsConfig *tls.Config) error {
return nil
}
type ECHClientConfig struct {
ECHCapableConfig
type STDECHClientConfig struct {
STDClientConfig
access sync.Mutex
dnsRouter adapter.DNSRouter
lastTTL time.Duration
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)
if err != nil {
return nil, err
@@ -122,17 +122,17 @@ func (s *ECHClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (a
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()
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{
MsgHdr: mDNS.MsgHdr{
RecursionDesired: true,
},
Question: []mDNS.Question{
{
Name: mDNS.Fqdn(s.ServerName()),
Name: mDNS.Fqdn(s.config.ServerName),
Qtype: mDNS.TypeHTTPS,
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.lastUpdate = time.Now()
s.SetECHConfigList(echConfigList)
s.config.EncryptedClientHelloConfigList = echConfigList
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 s.Client(conn)
}
func (s *ECHClientConfig) Clone() Config {
return &ECHClientConfig{ECHCapableConfig: s.ECHCapableConfig.Clone().(ECHCapableConfig), dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
func (s *STDECHClientConfig) Clone() Config {
return &STDECHClientConfig{STDClientConfig: STDClientConfig{s.config.Clone()}, dnsRouter: s.dnsRouter, lastUpdate: s.lastUpdate}
}
func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {

View File

@@ -11,12 +11,6 @@ import (
"github.com/cloudflare/circl/kem"
)
type ECHCapableConfig interface {
Config
ECHConfigList() []byte
SetECHConfigList([]byte)
}
func ECHKeygenDefault(serverName string) (configPem string, keyPem string, err error) {
cipherSuites := []echCipherSuite{
{

View File

@@ -10,7 +10,7 @@ import (
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.")
}

View File

@@ -74,7 +74,7 @@ func NewRealityClient(ctx context.Context, serverAddress string, options option.
if decodedLen > 8 {
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 {

View File

@@ -7,60 +7,43 @@ import (
"net"
"os"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tlsfragment"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
)
type STDClientConfig struct {
ctx context.Context
config *tls.Config
fragment bool
fragmentFallbackDelay time.Duration
recordFragment bool
config *tls.Config
}
func (c *STDClientConfig) ServerName() string {
return c.config.ServerName
func (s *STDClientConfig) ServerName() string {
return s.config.ServerName
}
func (c *STDClientConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
func (s *STDClientConfig) SetServerName(serverName string) {
s.config.ServerName = serverName
}
func (c *STDClientConfig) NextProtos() []string {
return c.config.NextProtos
func (s *STDClientConfig) NextProtos() []string {
return s.config.NextProtos
}
func (c *STDClientConfig) SetNextProtos(nextProto []string) {
c.config.NextProtos = nextProto
func (s *STDClientConfig) SetNextProtos(nextProto []string) {
s.config.NextProtos = nextProto
}
func (c *STDClientConfig) Config() (*STDConfig, error) {
return c.config, nil
func (s *STDClientConfig) Config() (*STDConfig, error) {
return s.config, nil
}
func (c *STDClientConfig) Client(conn net.Conn) (Conn, error) {
if c.recordFragment {
conn = tf.NewConn(conn, c.ctx, c.fragment, c.recordFragment, c.fragmentFallbackDelay)
}
return tls.Client(conn, c.config), nil
func (s *STDClientConfig) Client(conn net.Conn) (Conn, error) {
return tls.Client(conn, s.config), nil
}
func (c *STDClientConfig) Clone() Config {
return &STDClientConfig{c.ctx, c.config.Clone(), c.fragment, c.fragmentFallbackDelay, c.recordFragment}
}
func (c *STDClientConfig) ECHConfigList() []byte {
return c.config.EncryptedClientHelloConfigList
}
func (c *STDClientConfig) SetECHConfigList(EncryptedClientHelloConfigList []byte) {
c.config.EncryptedClientHelloConfigList = EncryptedClientHelloConfigList
func (s *STDClientConfig) Clone() Config {
return &STDClientConfig{s.config.Clone()}
}
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
tlsConfig.Time = ntp.TimeFuncFromContext(ctx)
tlsConfig.RootCAs = adapter.RootPoolFromContext(ctx)
if !options.DisableSNI {
if options.DisableSNI {
tlsConfig.ServerName = "127.0.0.1"
} else {
tlsConfig.ServerName = serverName
}
if options.Insecure {
@@ -142,10 +127,8 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
}
tlsConfig.RootCAs = certPool
}
stdConfig := &STDClientConfig{ctx, &tlsConfig, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
if options.ECH != nil && options.ECH.Enabled {
return parseECHClientConfig(ctx, stdConfig, options)
} else {
return stdConfig, nil
return parseECHClientConfig(ctx, options, &tlsConfig)
}
return &STDClientConfig{&tlsConfig}, nil
}

View File

@@ -22,7 +22,7 @@ var errInsecureUnused = E.New("tls: insecure unused")
type STDServerConfig struct {
config *tls.Config
logger log.Logger
acmeService adapter.SimpleLifecycle
acmeService adapter.Service
certificate []byte
key []byte
certificatePath string
@@ -165,11 +165,11 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
return nil, nil
}
var tlsConfig *tls.Config
var acmeService adapter.SimpleLifecycle
var acmeService adapter.Service
var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 {
//nolint:staticcheck
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
if err != nil {
return nil, err
}

View File

@@ -8,12 +8,11 @@ import (
"crypto/x509"
"math/rand"
"net"
"net/netip"
"os"
"strings"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/tlsfragment"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/ntp"
@@ -23,62 +22,48 @@ import (
)
type UTLSClientConfig struct {
ctx context.Context
config *utls.Config
id utls.ClientHelloID
fragment bool
fragmentFallbackDelay time.Duration
recordFragment bool
config *utls.Config
id utls.ClientHelloID
}
func (c *UTLSClientConfig) ServerName() string {
return c.config.ServerName
func (e *UTLSClientConfig) ServerName() string {
return e.config.ServerName
}
func (c *UTLSClientConfig) SetServerName(serverName string) {
c.config.ServerName = serverName
func (e *UTLSClientConfig) SetServerName(serverName string) {
e.config.ServerName = serverName
}
func (c *UTLSClientConfig) NextProtos() []string {
return c.config.NextProtos
func (e *UTLSClientConfig) NextProtos() []string {
return e.config.NextProtos
}
func (c *UTLSClientConfig) SetNextProtos(nextProto []string) {
func (e *UTLSClientConfig) SetNextProtos(nextProto []string) {
if len(nextProto) == 1 && nextProto[0] == http2.NextProtoTLS {
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")
}
func (c *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
if c.recordFragment {
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 (e *UTLSClientConfig) Client(conn net.Conn) (Conn, error) {
return &utlsALPNWrapper{utlsConnWrapper{utls.UClient(conn, e.config.Clone(), e.id)}, e.config.NextProtos}, nil
}
func (c *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
c.config.SessionIDGenerator = generator
func (e *UTLSClientConfig) SetSessionIDGenerator(generator func(clientHello []byte, sessionID []byte) error) {
e.config.SessionIDGenerator = generator
}
func (c *UTLSClientConfig) Clone() Config {
func (e *UTLSClientConfig) Clone() Config {
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 {
*utls.UConn
}
@@ -131,12 +116,14 @@ func (c *utlsALPNWrapper) HandshakeContext(ctx context.Context) error {
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
if options.ServerName != "" {
serverName = options.ServerName
} else if serverAddress != "" {
serverName = serverAddress
if _, err := netip.ParseAddr(serverName); err != nil {
serverName = serverAddress
}
}
if serverName == "" && !options.Insecure {
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
tlsConfig.Time = ntp.TimeFuncFromContext(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 {
tlsConfig.InsecureSkipVerify = options.Insecure
} else if options.DisableSNI {
@@ -201,15 +192,7 @@ func NewUTLSClient(ctx context.Context, serverAddress string, options option.Out
if err != nil {
return nil, err
}
uConfig := &UTLSClientConfig{ctx, &tlsConfig, id, options.Fragment, time.Duration(options.FragmentFallbackDelay), options.RecordFragment}
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
}
return &UTLSClientConfig{&tlsConfig, id}, nil
}
var (
@@ -237,7 +220,7 @@ func init() {
func uTLSClientHelloID(name string) (utls.ClientHelloID, error) {
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
case "chrome", "":
return utls.HelloChrome_Auto, nil

View File

@@ -9,7 +9,6 @@ import (
"strings"
"time"
C "github.com/sagernet/sing-box/constant"
N "github.com/sagernet/sing/common/network"
"golang.org/x/net/publicsuffix"
@@ -20,21 +19,16 @@ type Conn struct {
tcpConn *net.TCPConn
ctx context.Context
firstPacketWritten bool
splitPacket bool
splitRecord bool
fallbackDelay time.Duration
}
func NewConn(conn net.Conn, ctx context.Context, splitPacket bool, splitRecord bool, fallbackDelay time.Duration) *Conn {
if fallbackDelay == 0 {
fallbackDelay = C.TLSFragmentFallbackDelay
}
func NewConn(conn net.Conn, ctx context.Context, splitRecord bool, fallbackDelay time.Duration) *Conn {
tcpConn, _ := N.UnwrapReader(conn).(*net.TCPConn)
return &Conn{
Conn: conn,
tcpConn: tcpConn,
ctx: ctx,
splitPacket: splitPacket,
splitRecord: splitRecord,
fallbackDelay: fallbackDelay,
}
@@ -47,7 +41,7 @@ func (c *Conn) Write(b []byte) (n int, err error) {
}()
serverName := indexTLSServerName(b)
if serverName != nil {
if c.splitPacket {
if !c.splitRecord {
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(true)
if err != nil {
@@ -87,41 +81,33 @@ func (c *Conn) Write(b []byte) (n int, err error) {
payload = b[splitIndexes[i-1]:splitIndexes[i]]
}
if c.splitRecord {
if c.splitPacket {
buffer.Reset()
}
payloadLen := uint16(len(payload))
buffer.Write(b[:3])
binary.Write(&buffer, binary.BigEndian, payloadLen)
buffer.Write(payload)
if c.splitPacket {
payload = buffer.Bytes()
} else if c.tcpConn != nil && i != len(splitIndexes) {
err = writeAndWaitAck(c.ctx, c.tcpConn, payload, c.fallbackDelay)
if err != nil {
return
}
}
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
}
} else {
_, err = c.Conn.Write(payload)
if err != nil {
return
}
}
}
if c.splitRecord && !c.splitPacket {
_, err = c.Conn.Write(buffer.Bytes())
if c.splitRecord {
_, err = c.tcpConn.Write(buffer.Bytes())
if err != nil {
return
}
}
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(false)
if err != nil {
return
} else {
if c.tcpConn != nil {
err = c.tcpConn.SetNoDelay(false)
if err != nil {
return
}
}
}
return len(b), nil

View File

@@ -15,7 +15,7 @@ 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{
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), false, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())
@@ -25,17 +25,7 @@ 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{
tlsConn := tls.Client(tf.NewConn(tcpConn, context.Background(), true, 0), &tls.Config{
ServerName: "www.cloudflare.com",
})
require.NoError(t, tlsConn.Handshake())

View File

@@ -25,9 +25,6 @@ const (
TypeTUIC = "tuic"
TypeHysteria2 = "hysteria2"
TypeTailscale = "tailscale"
TypeDERP = "derp"
TypeResolved = "resolved"
TypeSSMAPI = "ssm-api"
)
const (

View File

@@ -9,7 +9,6 @@ const (
TCPTimeout = 15 * time.Second
ReadPayloadTimeout = 300 * time.Millisecond
DNSTimeout = 10 * time.Second
DirectDNSTimeout = 5 * time.Second
UDPTimeout = 5 * time.Minute
DefaultURLTestInterval = 3 * time.Minute
DefaultURLTestIdleTimeout = 30 * time.Minute

View File

@@ -30,10 +30,10 @@ var (
var _ adapter.DNSClient = (*Client)(nil)
type Client struct {
timeout time.Duration
disableCache bool
disableExpire bool
independentCache bool
clientSubnet netip.Prefix
rdrc adapter.RDRCStore
initRDRCFunc func() adapter.RDRCStore
logger logger.ContextLogger
@@ -42,24 +42,27 @@ type Client struct {
}
type ClientOptions struct {
Timeout time.Duration
DisableCache bool
DisableExpire bool
IndependentCache bool
CacheCapacity uint32
ClientSubnet netip.Prefix
RDRC func() adapter.RDRCStore
Logger logger.ContextLogger
}
func NewClient(options ClientOptions) *Client {
client := &Client{
timeout: options.Timeout,
disableCache: options.DisableCache,
disableExpire: options.DisableExpire,
independentCache: options.IndependentCache,
clientSubnet: options.ClientSubnet,
initRDRCFunc: options.RDRC,
logger: options.Logger,
}
if client.timeout == 0 {
client.timeout = C.DNSTimeout
}
cacheCapacity := options.CacheCapacity
if cacheCapacity < 1024 {
cacheCapacity = 1024
@@ -101,12 +104,8 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return &responseMessage, nil
}
question := message.Question[0]
clientSubnet := options.ClientSubnet
if !clientSubnet.IsValid() {
clientSubnet = c.clientSubnet
}
if clientSubnet.IsValid() {
message = SetClientSubnet(message, clientSubnet)
if options.ClientSubnet.IsValid() {
message = SetClientSubnet(message, options.ClientSubnet)
}
isSimpleRequest := len(message.Question) == 1 &&
len(message.Ns) == 0 &&
@@ -147,15 +146,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return nil, ErrResponseRejectedCached
}
}
timeout := options.Timeout
if timeout == 0 {
if transport.HasDetour() {
timeout = C.DNSTimeout
} else {
timeout = C.DirectDNSTimeout
}
}
ctx, cancel := context.WithTimeout(ctx, timeout)
ctx, cancel := context.WithTimeout(ctx, c.timeout)
response, err := transport.Exchange(ctx, message)
cancel()
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) {
domain = FqdnToDomain(domain)
dnsName := dns.Fqdn(domain)
var strategy C.DomainStrategy
if options.LookupStrategy != C.DomainStrategyAsIS {
strategy = options.LookupStrategy
} else {
strategy = options.Strategy
}
if strategy == C.DomainStrategyIPv4Only {
if options.Strategy == C.DomainStrategyIPv4Only {
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)
}
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 {
return nil, err
}
return sortAddresses(response4, response6, strategy), nil
return sortAddresses(response4, response6, options.Strategy), nil
}
func (c *Client) ClearCache() {
@@ -552,26 +537,12 @@ func transportTagFromContext(ctx context.Context) (string, bool) {
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 {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
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 {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
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 {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
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 {
response := dns.Msg{
MsgHdr: dns.MsgHdr{
Id: id,
Response: true,
Authoritative: true,
RecursionDesired: true,
RecursionAvailable: true,
Rcode: dns.RcodeSuccess,
Id: id,
Rcode: dns.RcodeSuccess,
Response: true,
},
Question: []dns.Question{question},
}

View File

@@ -55,7 +55,6 @@ func NewRouter(ctx context.Context, logFactory log.Factory, options option.DNSOp
DisableExpire: options.DNSClientOptions.DisableExpire,
IndependentCache: options.DNSClientOptions.IndependentCache,
CacheCapacity: options.DNSClientOptions.CacheCapacity,
ClientSubnet: options.DNSClientOptions.ClientSubnet.Build(netip.Prefix{}),
RDRC: func() adapter.RDRCStore {
cacheFile := service.FromContext[adapter.CacheFile](ctx)
if cacheFile == nil {
@@ -158,9 +157,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
if action.Strategy != C.DomainStrategyAsIS {
options.Strategy = action.Strategy
}
if action.Timeout > 0 {
options.Timeout = action.Timeout
}
if isFakeIP || action.DisableCache {
options.DisableCache = true
}
@@ -183,9 +179,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
if action.Strategy != C.DomainStrategyAsIS {
options.Strategy = action.Strategy
}
if action.Timeout > 0 {
options.Timeout = action.Timeout
}
if action.DisableCache {
options.DisableCache = true
}
@@ -265,14 +258,7 @@ func (r *Router) Exchange(ctx context.Context, message *mDNS.Msg, options adapte
case *R.RuleActionReject:
switch action.Method {
case C.RuleActionRejectMethodDefault:
return &mDNS.Msg{
MsgHdr: mDNS.MsgHdr{
Id: message.Id,
Rcode: mDNS.RcodeRefused,
Response: true,
},
Question: []mDNS.Question{message.Question[0]},
}, nil
return FixedResponse(message.Id, message.Question[0], nil, 0), nil
case C.RuleActionRejectMethodDrop:
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) {
rejected = true
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 {
rejected = true
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for ", FormatQuestion(message.Question[0].String())))
} else {
r.logger.ErrorContext(ctx, E.Cause(err, "exchange failed for <empty query>"))

View File

@@ -41,7 +41,6 @@ type Transport struct {
dns.TransportAdapter
ctx context.Context
dialer N.Dialer
hasDetour bool
logger logger.ContextLogger
networkManager adapter.NetworkManager
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),
ctx: ctx,
dialer: transportDialer,
hasDetour: options.Detour != "",
logger: logger,
networkManager: service.FromContext[adapter.NetworkManager](ctx),
interfaceName: options.Interface,
@@ -91,10 +89,6 @@ func (t *Transport) Close() error {
return nil
}
func (t *Transport) HasDetour() bool {
return t.hasDetour
}
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
err := t.fetchServers()
if err != nil {

View File

@@ -3,15 +3,11 @@ package transport
import (
"bytes"
"context"
"errors"
"io"
"net"
"net/http"
"net/url"
"os"
"strconv"
"sync"
"time"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer"
@@ -43,13 +39,11 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
type HTTPSTransport struct {
dns.TransportAdapter
logger logger.ContextLogger
dialer N.Dialer
destination *url.URL
headers http.Header
transportAccess sync.Mutex
transport *http.Transport
transportResetAt time.Time
logger logger.ContextLogger
dialer N.Dialer
destination *url.URL
headers http.Header
transport *http.Transport
}
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 {
t.transportAccess.Lock()
defer t.transportAccess.Unlock()
t.transport.CloseIdleConnections()
t.transport = t.transport.Clone()
return nil
}
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.Id = 0
exMessage.Compress = true

View File

@@ -20,8 +20,7 @@ import (
)
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
var state C.res_state
if C.res_ninit(state) != 0 {
if C.res_init() != 0 {
return &dnsConfig{
servers: defaultNS,
search: dnsDefaultSearch(),
@@ -34,10 +33,10 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
conf := &dnsConfig{
ndots: 1,
timeout: 5 * time.Second,
attempts: int(state.retry),
attempts: int(C._res.retry),
}
for i := 0; i < int(state.nscount); i++ {
ns := state.nsaddr_list[i]
for i := 0; i < int(C._res.nscount); i++ {
ns := C._res.nsaddr_list[i]
addr := C.inet_ntoa(ns.sin_addr)
if addr == nil {
continue
@@ -45,7 +44,7 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
conf.servers = append(conf.servers, C.GoString(addr))
}
for i := 0; ; i++ {
search := state.dnsrch[i]
search := C._res.dnsrch[i]
if search == nil {
break
}

View File

@@ -60,17 +60,13 @@ func NewTLS(ctx context.Context, logger log.ContextLogger, tag string, options o
if !serverAddr.IsValid() {
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{
TransportAdapter: adapter,
TransportAdapter: dns.NewTransportAdapterWithRemoteOptions(C.DNSTypeTLS, tag, options.RemoteDNSServerOptions),
logger: logger,
dialer: dialer,
dialer: transportDialer,
serverAddr: serverAddr,
tlsConfig: tlsConfig,
}
}, nil
}
func (t *TLSTransport) Start(stage adapter.StartStage) error {

View File

@@ -14,7 +14,6 @@ type TransportAdapter struct {
transportType string
transportTag string
dependencies []string
hasDetour bool
strategy C.DomainStrategy
clientSubnet netip.Prefix
}
@@ -36,7 +35,6 @@ func NewTransportAdapterWithLocalOptions(transportType string, transportTag stri
transportType: transportType,
transportTag: transportTag,
dependencies: dependencies,
hasDetour: localOptions.Detour != "",
strategy: C.DomainStrategy(localOptions.LegacyStrategy),
clientSubnet: localOptions.LegacyClientSubnet,
}
@@ -71,10 +69,6 @@ func (a *TransportAdapter) Dependencies() []string {
return a.dependencies
}
func (a *TransportAdapter) HasDetour() bool {
return a.hasDetour
}
func (a *TransportAdapter) LegacyStrategy() C.DomainStrategy {
return a.strategy
}

View File

@@ -2,105 +2,6 @@
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**

View File

@@ -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"
:material-plus: [cache_capacity](#cache_capacity)

View File

@@ -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 中的更改"
:material-plus: [cache_capacity](#cache_capacity)

View File

@@ -81,7 +81,7 @@ Will overrides `dns.client_subnet`.
#### method
- `default`: Reply with REFUSED.
- `default`: Reply with NXDOMAIN.
- `drop`: Drop the request.
`default` will be used by default.

View File

@@ -81,7 +81,7 @@ icon: material/new-box
#### method
- `default`: 返回 REFUSED
- `default`: 返回 NXDOMAIN
- `drop`: 丢弃请求。
默认使用 `defualt`

View File

@@ -41,7 +41,6 @@ The type of the DNS server.
| `dhcp` | [DHCP](./dhcp/) |
| `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
| `resolved` | [Resolved](./resolved/) |
#### tag

View File

@@ -41,7 +41,6 @@ DNS 服务器的类型。
| `dhcp` | [DHCP](./dhcp/) |
| `fakeip` | [Fake IP](./fakeip/) |
| `tailscale` | [Tailscale](./tailscale/) |
| `resolved` | [Resolved](./resolved/) |
#### tag

View File

@@ -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
}
]
}
}
```

View File

@@ -30,13 +30,13 @@ icon: material/new-box
==Required==
The tag of the [Tailscale Endpoint](/configuration/endpoint/tailscale).
The tag of the Tailscale endpoint.
#### accept_default_resolvers
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
@@ -80,4 +80,4 @@ if not enabled, `NXDOMAIN` will be returned for non-Tailscale domain queries.
]
}
}
```
```

View File

@@ -6,7 +6,7 @@ icon: material/new-box
# Endpoint
An endpoint is a protocol with inbound and outbound behavior.
Endpoint is protocols that has both inbound and outbound behavior.
### Structure

View File

@@ -25,7 +25,7 @@ icon: material/new-box
| 类型 | 格式 |
|-------------|---------------------------|
| `wireguard` | [WireGuard](./wireguard/) |
| `wireguard` | [WireGuard](./wiregaurd/) |
| `tailscale` | [Tailscale](./tailscale/) |
#### tag

View File

@@ -59,7 +59,7 @@
{
"external_controller": "0.0.0.0:9090",
"external_ui": "dashboard"
// "external_ui_download_detour": "direct"
// external_ui_download_detour: "direct"
}
```

View File

@@ -59,7 +59,7 @@
{
"external_controller": "0.0.0.0:9090",
"external_ui": "dashboard"
// "external_ui_download_detour": "direct"
// external_ui_download_detour: "direct"
}
```

View File

@@ -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"
:material-delete-alert: [gso](#gso)
@@ -60,12 +56,9 @@ icon: material/new-box
"auto_route": true,
"iproute2_table_index": 2022,
"iproute2_rule_index": 9000,
"auto_redirect": true,
"auto_redirect": false,
"auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024",
"loopback_address": [
"10.7.0.1"
],
"strict_route": true,
"route_address": [
"0.0.0.0/1",
@@ -73,6 +66,7 @@ icon: material/new-box
"::/1",
"8000::/1"
],
"route_exclude_address": [
"192.168.0.0/16",
"fc00::/7"
@@ -123,6 +117,7 @@ icon: material/new-box
"match_domain": []
}
},
// Deprecated
"gso": false,
"inet4_address": [
@@ -145,8 +140,8 @@ icon: material/new-box
"inet6_route_exclude_address": [
"fc00::/7"
],
...
// Listen Fields
... // Listen Fields
}
```
@@ -278,16 +273,6 @@ Connection output mark used by `auto_redirect`.
`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
Enforce strict routing rules when `auto_route` is enabled:

View File

@@ -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 中的更改"
:material-delete-alert: [gso](#gso)
@@ -60,12 +56,9 @@ icon: material/new-box
"auto_route": true,
"iproute2_table_index": 2022,
"iproute2_rule_index": 9000,
"auto_redirect": true,
"auto_redirect": false,
"auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024",
"loopback_address": [
"10.7.0.1"
],
"strict_route": true,
"route_address": [
"0.0.0.0/1",
@@ -277,16 +270,6 @@ tun 接口的 IPv6 前缀。
默认使用 `0x2024`
#### loopback_address
!!! question "自 sing-box 1.12.0 起"
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。
#### strict_route
当启用 `auto_route` 时,强制执行严格的路由规则:
@@ -415,11 +398,11 @@ UDP NAT 过期时间。
TCP/IP 栈。
| 栈 | 描述 |
|----------|-------------------------------------------------------------------------------------------------------|
| `system` | 基于系统网络栈执行 L3 到 L4 转换 |
| `gvisor` | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
| `mixed` | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
| 栈 | 描述 |
|--------|------------------------------------------------------------------|
| system | 基于系统网络栈执行 L3 到 L4 转换 |
| gVisor | 基于 [gVisor](https://github.com/google/gvisor) 虚拟网络栈执行 L3 到 L4 转换 |
| mixed | 混合 `system` TCP 栈与 `gvisor` UDP 栈 |
默认使用 `mixed` 栈如果 gVisor 构建标记已启用,否则默认使用 `system` 栈。

View File

@@ -14,7 +14,6 @@ sing-box uses JSON for configuration files.
"inbounds": [],
"outbounds": [],
"route": {},
"services": [],
"experimental": {}
}
```
@@ -31,7 +30,6 @@ sing-box uses JSON for configuration files.
| `inbounds` | [Inbound](./inbound/) |
| `outbounds` | [Outbound](./outbound/) |
| `route` | [Route](./route/) |
| `services` | [Service](./service/) |
| `experimental` | [Experimental](./experimental/) |
### Check

View File

@@ -14,7 +14,6 @@ sing-box 使用 JSON 作为配置文件格式。
"inbounds": [],
"outbounds": [],
"route": {},
"services": [],
"experimental": {}
}
```
@@ -31,7 +30,6 @@ sing-box 使用 JSON 作为配置文件格式。
| `inbounds` | [入站](./inbound/) |
| `outbounds` | [出站](./outbound/) |
| `route` | [路由](./route/) |
| `services` | [服务](./service/) |
| `experimental` | [实验性](./experimental/) |
### 检查

View File

@@ -172,12 +172,14 @@ 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.
On Linux, Apple platforms, (administrator privileges required) Windows,
the wait time can be automatically detected. Otherwise, it will fall back to
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,
because the target is considered to be local or behind a transparent proxy.
Conflict with `tls_record_fragment`.
#### tls_fragment_fallback_delay
!!! question "Since sing-box 1.12.0"
@@ -192,6 +194,11 @@ The fallback value used when TLS segmentation cannot automatically determine the
Fragment TLS handshake into multiple TLS records 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.
Conflict with `tls_fragment`.
### sniff
```json

View File

@@ -170,6 +170,8 @@ UDP 连接超时时间。
此外,若实际等待时间小于 20 毫秒,同样会回退至固定等待时间模式,因为此时判定目标处于本地或透明代理之后。
`tls_record_fragment` 冲突。
#### tls_fragment_fallback_delay
!!! question "自 sing-box 1.12.0 起"
@@ -184,6 +186,10 @@ UDP 连接超时时间。
通过分段 TLS 握手数据包到多个 TLS 记录来绕过防火墙检测。
此功能旨在规避基于**明文数据包匹配**的简单防火墙,不应该用于规避真的审查。
`tls_fragment` 冲突。
### sniff
```json

View File

@@ -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__ }
```

View File

@@ -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.

View File

@@ -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.

View File

@@ -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).

View File

@@ -4,9 +4,6 @@ icon: material/alert-decagram
!!! 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.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
@@ -85,9 +82,6 @@ icon: material/alert-decagram
"cipher_suites": [],
"certificate": "",
"certificate_path": "",
"fragment": false,
"fragment_fallback_delay": "",
"record_fragment": false,
"ech": {
"enabled": false,
"config": [],
@@ -319,44 +313,6 @@ The path to ECH configuration, in PEM format.
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
#### domain

View File

@@ -4,9 +4,6 @@ icon: material/alert-decagram
!!! 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.dynamic_record_sizing_disabled](#dynamic_record_sizing_disabled)
@@ -85,9 +82,6 @@ icon: material/alert-decagram
"cipher_suites": [],
"certificate": [],
"certificate_path": "",
"fragment": false,
"fragment_fallback_delay": "",
"record_fragment": false,
"ech": {
"enabled": false,
"pq_signature_schemes_enabled": false,
@@ -311,41 +305,6 @@ ECH PEM 配置路径
如果为 true则始终使用最大可能的 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 字段
#### domain

View File

@@ -94,13 +94,18 @@ flowchart TB
"servers": [
{
"tag": "google",
"type": "tls",
"server": "8.8.8.8"
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"type": "udp",
"server": "223.5.5.5"
"address": "223.5.5.5",
"detour": "direct"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
}
],
"strategy": "ipv4_only"
@@ -110,8 +115,7 @@ flowchart TB
"type": "tun",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
// "auto_redirect": true, // On linux
"strict_route": true
"strict_route": false
}
],
"outbounds": [
@@ -119,23 +123,25 @@ flowchart TB
{
"type": "direct",
"tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"protocol": "dns",
"action": "hijack-dns"
"outbound": "dns-out"
},
{
"ip_is_private": true,
"geoip": [
"private"
],
"outbound": "direct"
}
],
"default_domain_resolver": "local",
"auto_detect_interface": true
}
}
@@ -149,13 +155,18 @@ flowchart TB
"servers": [
{
"tag": "google",
"type": "tls",
"server": "8.8.8.8"
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"type": "udp",
"server": "223.5.5.5"
"address": "223.5.5.5",
"detour": "direct"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
}
]
},
@@ -165,8 +176,7 @@ flowchart TB
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126",
"auto_route": true,
// "auto_redirect": true, // On linux
"strict_route": true
"strict_route": false
}
],
"outbounds": [
@@ -174,23 +184,25 @@ flowchart TB
{
"type": "direct",
"tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"protocol": "dns",
"action": "hijack-dns"
"outbound": "dns-out"
},
{
"ip_is_private": true,
"geoip": [
"private"
],
"outbound": "direct"
}
],
"default_domain_resolver": "local",
"auto_detect_interface": true
}
}
@@ -204,22 +216,23 @@ flowchart TB
"servers": [
{
"tag": "google",
"type": "tls",
"server": "8.8.8.8"
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"type": "udp",
"server": "223.5.5.5"
"address": "223.5.5.5",
"detour": "direct"
},
{
"tag": "remote",
"type": "fakeip",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
"address": "fakeip"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
},
{
"query_type": [
"A",
@@ -228,6 +241,11 @@ flowchart TB
"server": "remote"
}
],
"fakeip": {
"enabled": true,
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
},
"independent_cache": true
},
"inbounds": [
@@ -236,7 +254,6 @@ flowchart TB
"inet4_address": "172.19.0.1/30",
"inet6_address": "fdfe:dcba:9876::1/126",
"auto_route": true,
// "auto_redirect": true, // On linux
"strict_route": true
}
],
@@ -245,23 +262,25 @@ flowchart TB
{
"type": "direct",
"tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"protocol": "dns",
"action": "hijack-dns"
"outbound": "dns-out"
},
{
"ip_is_private": true,
"geoip": [
"private"
],
"outbound": "direct"
}
],
"default_domain_resolver": "local",
"auto_detect_interface": true
}
}
@@ -271,6 +290,54 @@ flowchart TB
=== ":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"
```json
@@ -279,20 +346,35 @@ flowchart TB
"servers": [
{
"tag": "google",
"type": "tls",
"server": "8.8.8.8"
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"type": "https",
"server": "223.5.5.5"
"address": "https://223.5.5.5/dns-query",
"detour": "direct"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
},
{
"clash_mode": "Direct",
"server": "local"
},
{
"clash_mode": "Global",
"server": "google"
},
{
"rule_set": "geosite-geolocation-cn",
"server": "local"
},
{
"clash_mode": "Default",
"server": "google"
},
{
"type": "logical",
"mode": "and",
@@ -310,7 +392,6 @@ flowchart TB
]
},
"route": {
"default_domain_resolver": "local",
"rule_set": [
{
"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
{
"dns": {
"servers": [
{
"tag": "google",
"type": "tls",
"server": "8.8.8.8"
"address": "tls://8.8.8.8"
},
{
"tag": "local",
"type": "https",
"server": "223.5.5.5"
"address": "https://223.5.5.5/dns-query",
"detour": "direct"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
},
{
"clash_mode": "Direct",
"server": "local"
},
{
"clash_mode": "Global",
"server": "google"
},
{
"rule_set": "geosite-geolocation-cn",
"server": "local"
@@ -384,7 +476,6 @@ flowchart TB
]
},
"route": {
"default_domain_resolver": "local",
"rule_set": [
{
"type": "remote",
@@ -426,13 +517,14 @@ flowchart TB
{
"type": "direct",
"tag": "direct"
},
{
"type": "block",
"tag": "block"
}
],
"route": {
"rules": [
{
"action": "sniff"
},
{
"type": "logical",
"mode": "or",
@@ -444,12 +536,20 @@ flowchart TB
"port": 53
}
],
"action": "hijack-dns"
"outbound": "dns"
},
{
"ip_is_private": true,
"outbound": "direct"
},
{
"clash_mode": "Direct",
"outbound": "direct"
},
{
"clash_mode": "Global",
"outbound": "default"
},
{
"type": "logical",
"mode": "or",
@@ -465,23 +565,12 @@ flowchart TB
"protocol": "stun"
}
],
"action": "reject"
"outbound": "block"
},
{
"rule_set": "geosite-geolocation-cn",
"outbound": "direct"
},
{
"type": "logical",
"mode": "and",
"rules": [
{
"rule_set": "geoip-cn"
},
{
"rule_set": "geosite-geolocation-!cn",
"invert": true
}
"rule_set": [
"geoip-cn",
"geosite-geolocation-cn"
],
"outbound": "direct"
}
@@ -502,4 +591,4 @@ flowchart TB
]
}
}
```
```

View File

@@ -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) {

41
go.mod
View File

@@ -6,20 +6,18 @@ require (
github.com/anytls/sing-anytls v0.0.8
github.com/caddyserver/certmagic v0.23.0
github.com/cloudflare/circl v1.6.1
github.com/coder/websocket v1.8.12
github.com/cretz/bine v0.2.0
github.com/go-chi/chi/v5 v5.2.1
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/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
github.com/libdns/alidns v1.0.4-libdns.v1.beta1
github.com/libdns/cloudflare v0.2.2-0.20250430151523-b46a2b0885f6
github.com/logrusorgru/aurora v2.0.3+incompatible
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/miekg/dns v1.1.66
github.com/miekg/dns v1.1.65
github.com/oschwald/maxminddb-golang v1.13.1
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
@@ -27,17 +25,18 @@ require (
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.6
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
github.com/sagernet/quic-go v0.51.0-beta.5
github.com/sagernet/sing v0.6.10-0.20250505040842-ba62fee9470f
github.com/sagernet/sing-mux v0.3.2
github.com/sagernet/sing-quic v0.5.0-beta.2
github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3
github.com/sagernet/sing-shadowsocks v0.2.7
github.com/sagernet/sing-shadowsocks2 v0.2.0
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-vmess v0.2.4-0.20250605032146-38cc72672c88
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8
github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-mod.5
github.com/sagernet/utls v1.6.7
github.com/sagernet/wireguard-go v0.0.1-beta.7
github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854
github.com/spf13/cobra v1.9.1
@@ -45,11 +44,11 @@ require (
github.com/vishvananda/netns v0.0.5
go.uber.org/zap v1.27.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.38.0
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6
golang.org/x/crypto v0.37.0
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0
golang.org/x/mod v0.24.0
golang.org/x/net v0.40.0
golang.org/x/sys v0.33.0
golang.org/x/net v0.39.0
golang.org/x/sys v0.32.0
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10
google.golang.org/grpc v1.72.0
google.golang.org/protobuf v1.36.6
@@ -67,6 +66,7 @@ require (
github.com/bits-and-blooms/bitset v1.13.0 // indirect
github.com/caddyserver/zerossl v0.1.3 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa // indirect
@@ -79,6 +79,7 @@ require (
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gobwas/httphead v0.1.0 // 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/google/btree v1.1.3 // indirect
github.com/google/go-cmp v0.6.0 // indirect
@@ -123,14 +124,16 @@ require (
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap/exp v0.3.0 // indirect
go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect
golang.org/x/sync v0.14.0 // indirect
golang.org/x/term v0.32.0 // indirect
golang.org/x/text v0.25.0 // indirect
golang.org/x/sync v0.13.0 // indirect
golang.org/x/term v0.31.0 // indirect
golang.org/x/text v0.24.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/wireguard/windows v0.5.3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/blake3 v1.3.0 // indirect
)
//replace github.com/sagernet/sing => ../sing

70
go.sum
View File

@@ -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/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/utls v1.7.0-alpha.3 h1:cp1cEMUnoifiWrGHRzo+nCwPRveN9yPD8QaRFmfcYxA=
github.com/metacubex/utls v1.7.0-alpha.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/utls v1.7.0-alpha.2 h1:kLRg6zDV12R1uclL5qW9Tx4RD6ztGIIrTZWY5zrJXCg=
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/go.mod h1:L1wOU06KKvq7tswuMDwKdcHeKpFFgkppZy/y0DFxagQ=
github.com/miekg/dns v1.1.66 h1:FeZXOS3VCVsKnEAd+wBkjMC3D2K+ww66Cq3VnCINuJE=
github.com/miekg/dns v1.1.66/go.mod h1:jGFzBsSNbJw6z1HYut1RKBKHA9PBdxeHrZG8J+gC2WE=
github.com/miekg/dns v1.1.65 h1:0+tIPHzUW0GCge7IiK3guGP57VAw7hoPDfApjkMD1Fc=
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/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
@@ -165,27 +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/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/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs=
github.com/sagernet/quic-go v0.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/quic-go v0.51.0-beta.5 h1:/mME3sJvQ8k/JKP0oC/9XoWrm0znO7hWXviB5yiipJY=
github.com/sagernet/quic-go v0.51.0-beta.5/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.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo=
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 h1:lttLhNtFuMItQcTD29QP6aBS8kR1UhG7zZ+pwzTYkFM=
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/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
github.com/sagernet/sing-quic v0.5.0-beta.2/go.mod h1:SAv/qdeDN+75msGG5U5ZIwG+3Ua50jVIKNrRSY8pkx0=
github.com/sagernet/sing-shadowsocks v0.2.8 h1:PURj5PRoAkqeHh2ZW205RWzN9E9RtKCVCzByXruQWfE=
github.com/sagernet/sing-shadowsocks v0.2.8/go.mod h1:lo7TWEMDcN5/h5B8S0ew+r78ZODn6SwVaFhvB6H+PTI=
github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnqqs2gQ2/Qioo=
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3 h1:1J+s1yyZ8+YAYaClI+az8YuFgV9NGXUUCZnriKmos6w=
github.com/sagernet/sing-quic v0.4.1-0.20250511050139-d459f561c9c3/go.mod h1:Mv7CdSyLepmqoLT8rd88Qn3QMv5AbsgjEm3DvEhDVNE=
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE=
github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg=
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/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935 h1:wha4BG4mrEKaIoouVyiU5BcPfKD1n0LkiL4vqdjaVps=
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935/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.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210 h1:6H4BZaTqKI3YcDMyTV3E576LuJM4S4wY99xoq2T1ECw=
github.com/sagernet/sing-tun v0.6.6-0.20250428031943-0686f8c4f210/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
github.com/sagernet/sing-vmess v0.2.2-0.20250503051933-9b4cf17393f8 h1:zW+zAOCxUIqBCgnZiPovt1uQ3S+zBS+w0NGp+1zITGA=
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/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc=
github.com/sagernet/tailscale v1.80.3-mod.4.0.20250512093633-e1bc1888c814 h1:B6ejgOuM1BrX4TzWvm1h/LQAOZW1T1jP4PSZe8b/49o=
github.com/sagernet/tailscale v1.80.3-mod.4.0.20250512093633-e1bc1888c814/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/tailscale v1.80.3-mod.5 h1:7V7z+p2C//TGtff20pPnDCt3qP6uFyY62peJoKF9z/A=
github.com/sagernet/tailscale v1.80.3-mod.5/go.mod h1:EBxXsWu4OH2ELbQLq32WoBeIubG8KgDrg4/Oaxjs6lI=
github.com/sagernet/wireguard-go v0.0.1-beta.7 h1:ltgBwYHfr+9Wz1eG59NiWnHrYEkDKHG7otNZvu85DXI=
@@ -263,21 +265,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/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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
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/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
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/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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
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-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -286,20 +288,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.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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
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.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
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/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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU=
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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -3,12 +3,10 @@ package include
import (
"context"
"github.com/sagernet/sing-box"
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/inbound"
"github.com/sagernet/sing-box/adapter/outbound"
"github.com/sagernet/sing-box/adapter/service"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"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/vless"
"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"
)
func Context(ctx context.Context) context.Context {
return box.Context(ctx, InboundRegistry(), OutboundRegistry(), EndpointRegistry(), DNSTransportRegistry(), ServiceRegistry())
}
func InboundRegistry() *inbound.Registry {
registry := inbound.NewRegistry()
@@ -118,7 +110,6 @@ func DNSTransportRegistry() *dns.TransportRegistry {
hosts.RegisterTransport(registry)
local.RegisterTransport(registry)
fakeip.RegisterTransport(registry)
resolved.RegisterTransport(registry)
registerQUICTransports(registry)
registerDHCPTransport(registry)
@@ -127,17 +118,6 @@ func DNSTransportRegistry() *dns.TransportRegistry {
return registry
}
func ServiceRegistry() *service.Registry {
registry := service.NewRegistry()
resolved.RegisterService(registry)
ssmapi.RegisterService(registry)
registerDERPService(registry)
return 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) {
return nil, E.New("ShadowsocksR is deprecated and removed in sing-box 1.6.0")

View File

@@ -4,10 +4,8 @@ package include
import (
"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/protocol/tailscale"
"github.com/sagernet/sing-box/service/derp"
)
func registerTailscaleEndpoint(registry *endpoint.Registry) {
@@ -17,7 +15,3 @@ func registerTailscaleEndpoint(registry *endpoint.Registry) {
func registerTailscaleTransport(registry *dns.TransportRegistry) {
tailscale.RegistryTransport(registry)
}
func registerDERPService(registry *service.Registry) {
derp.Register(registry)
}

View File

@@ -7,7 +7,6 @@ import (
"github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/adapter/endpoint"
"github.com/sagernet/sing-box/adapter/service"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns"
"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`)
})
}
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`)
})
}

View File

@@ -94,7 +94,6 @@ nav:
- DHCP: configuration/dns/server/dhcp.md
- FakeIP: configuration/dns/server/fakeip.md
- Tailscale: configuration/dns/server/tailscale.md
- Resolved: configuration/dns/server/resolved.md
- DNS Rule: configuration/dns/rule.md
- DNS Rule Action: configuration/dns/rule_action.md
- FakeIP: configuration/dns/fakeip.md
@@ -170,11 +169,6 @@ nav:
- DNS: configuration/outbound/dns.md
- Selector: configuration/outbound/selector.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:
- pymdownx.inlinehilite
- pymdownx.snippets

View File

@@ -121,6 +121,7 @@ type LegacyDNSFakeIPOptions struct {
type DNSTransportOptionsRegistry interface {
CreateOptions(transportType string) (any, bool)
}
type _DNSServerOptions struct {
Type string `json:"type,omitempty"`
Tag string `json:"tag,omitempty"`

View File

@@ -32,11 +32,11 @@ func (h *Endpoint) UnmarshalJSONContext(ctx context.Context, content []byte) err
}
registry := service.FromContext[EndpointOptionsRegistry](ctx)
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)
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)
if err != nil {

View File

@@ -34,7 +34,7 @@ func (h *Inbound) UnmarshalJSONContext(ctx context.Context, content []byte) erro
}
registry := service.FromContext[InboundOptionsRegistry](ctx)
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)
if !loaded {

View File

@@ -19,7 +19,6 @@ type _Options struct {
Inbounds []Inbound `json:"inbounds,omitempty"`
Outbounds []Outbound `json:"outbounds,omitempty"`
Route *RouteOptions `json:"route,omitempty"`
Services []Service `json:"services,omitempty"`
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
}

View File

@@ -91,7 +91,6 @@ type DialerOptions struct {
type _DomainResolveOptions struct {
Server string `json:"server"`
Strategy DomainStrategy `json:"strategy,omitempty"`
Timeout badoption.Duration `json:"timeout,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
@@ -103,7 +102,6 @@ func (o DomainResolveOptions) MarshalJSON() ([]byte, error) {
if o.Server == "" {
return []byte("{}"), nil
} else if o.Strategy == DomainStrategy(C.DomainStrategyAsIS) &&
o.Timeout == 0 &&
!o.DisableCache &&
o.RewriteTTL == nil &&
o.ClientSubnet == nil {

View File

@@ -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"`
}

View File

@@ -180,7 +180,6 @@ func (r *RouteOptionsActionOptions) UnmarshalJSON(data []byte) error {
type DNSRouteActionOptions struct {
Server string `json:"server,omitempty"`
Strategy DomainStrategy `json:"strategy,omitempty"`
Timeout badoption.Duration `json:"timeout,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`
@@ -188,7 +187,6 @@ type DNSRouteActionOptions struct {
type _DNSRouteOptionsActionOptions struct {
Strategy DomainStrategy `json:"strategy,omitempty"`
Timeout badoption.Duration `json:"timeout,omitempty"`
DisableCache bool `json:"disable_cache,omitempty"`
RewriteTTL *uint32 `json:"rewrite_ttl,omitempty"`
ClientSubnet *badoption.Prefixable `json:"client_subnet,omitempty"`

View File

@@ -1,47 +0,0 @@
package option
import (
"context"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
"github.com/sagernet/sing/common/json/badjson"
"github.com/sagernet/sing/service"
)
type ServiceOptionsRegistry interface {
CreateOptions(serviceType string) (any, bool)
}
type _Service struct {
Type string `json:"type"`
Tag string `json:"tag,omitempty"`
Options any `json:"-"`
}
type Service _Service
func (h *Service) MarshalJSONContext(ctx context.Context) ([]byte, error) {
return badjson.MarshallObjectsContext(ctx, (*_Service)(h), h.Options)
}
func (h *Service) UnmarshalJSONContext(ctx context.Context, content []byte) error {
err := json.UnmarshalContext(ctx, content, (*_Service)(h))
if err != nil {
return err
}
registry := service.FromContext[ServiceOptionsRegistry](ctx)
if registry == nil {
return E.New("missing service fields registry in context")
}
options, loaded := registry.CreateOptions(h.Type)
if !loaded {
return E.New("unknown inbound type: ", h.Type)
}
err = badjson.UnmarshallExcludedContext(ctx, content, (*_Service)(h), options)
if err != nil {
return err
}
h.Options = options
return nil
}

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