Compare commits

..

106 Commits

Author SHA1 Message Date
世界
0e5f1814cc documentation: Bump version 2025-06-12 11:37:03 +08:00
Restia-Ashbell
d8e1cd0d51 Add ECH support for uTLS 2025-06-12 10:28:44 +08:00
世界
0a5f09f147 Improve TLS fragments 2025-06-12 08:58:20 +08:00
世界
e7ae3ddf31 Add cache support for ssm-api 2025-06-12 08:11:10 +08:00
世界
8b15576289 Fix service will not be closed 2025-06-12 08:11:10 +08:00
世界
c86f47bdf8 Add loopback address support for tun 2025-06-12 08:11:10 +08:00
世界
ce013ceabb documentation: Bump version 2025-06-12 08:11:09 +08:00
世界
d0824ea7f6 release: Update Go to 1.24.4 2025-06-12 08:11:09 +08:00
世界
7291d7e802 Fix tproxy listener 2025-06-12 08:11:09 +08:00
世界
3f38520f5e Fix systemd package 2025-06-12 08:11:09 +08:00
世界
de7e0364e8 Fix missing home for derp service 2025-06-12 08:11:08 +08:00
Zero Clover
867c267bef documentation: Fix services 2025-06-12 08:11:08 +08:00
世界
2dfe93c99c Fix dns.client_subnet ignored 2025-06-12 08:11:08 +08:00
世界
656301bb9e documentation: Minor fixes 2025-06-12 08:11:08 +08:00
世界
301549f453 Fix tailscale forward 2025-06-12 08:11:08 +08:00
世界
cd7229f39b Minor fixes 2025-06-12 08:11:07 +08:00
世界
f0e42f8b8f Add SSM API service 2025-06-12 08:11:07 +08:00
世界
65a629cbb5 Add resolved service and DNS server 2025-06-12 08:11:06 +08:00
世界
7774f16552 Add DERP service 2025-06-12 08:11:06 +08:00
世界
2ded81f1f7 Add service component type 2025-06-12 08:11:06 +08:00
世界
e68036ea62 Fix tproxy tcp control 2025-06-12 08:11:05 +08:00
愚者
f5dd883fb5 release: Fix build tags for android
Signed-off-by: 愚者 <11926619+FansChou@users.noreply.github.com>
2025-06-12 08:11:05 +08:00
世界
aa2a4ea2bb prevent creation of bind and mark controls on unsupported platforms 2025-06-12 08:11:04 +08:00
PuerNya
aeb340170d documentation: Fix description of reject DNS action behavior 2025-06-12 08:11:04 +08:00
Restia-Ashbell
009b224de6 Fix TLS record fragment 2025-06-12 08:11:04 +08:00
世界
f6898a7806 Add missing accept_routes option for Tailscale 2025-06-12 08:11:04 +08:00
世界
44ada21eb2 Add TLS record fragment support 2025-06-12 08:11:03 +08:00
世界
d276db1ccc Fix set edns0 client subnet 2025-06-12 08:11:03 +08:00
世界
1016ba6cef Update minor dependencies 2025-06-12 08:11:02 +08:00
世界
ecfd010ac9 Update certmagic and providers 2025-06-12 08:11:02 +08:00
世界
3c07a9dbf5 Update protobuf and grpc 2025-06-12 08:11:01 +08:00
世界
115eb9e872 Add control options for listeners 2025-06-12 08:11:01 +08:00
世界
26f0c5dd93 Update quic-go to v0.52.0 2025-06-12 08:11:00 +08:00
世界
0d9883b2c8 Update utls to v1.7.2 2025-06-12 08:11:00 +08:00
世界
b026e78f79 Handle EDNS version downgrade 2025-06-12 08:11:00 +08:00
世界
2e20110f02 documentation: Fix anytls padding scheme description 2025-06-12 08:10:59 +08:00
安容
9fc424498d Report invalid DNS address early 2025-06-12 08:10:59 +08:00
世界
886ea0c893 Fix wireguard listen_port 2025-06-12 08:10:59 +08:00
世界
fbd79337e5 clash-api: Add more meta api 2025-06-12 08:10:58 +08:00
世界
de31c316a0 Fix DNS lookup 2025-06-12 08:10:58 +08:00
世界
ebaaee7b75 Fix fetch ECH configs 2025-06-12 08:10:58 +08:00
reletor
1f35f25ada documentation: Minor fixes 2025-06-12 08:10:58 +08:00
caelansar
5a1d726242 Fix callback deletion in UDP transport 2025-06-12 08:10:57 +08:00
世界
2f3ebe6f68 documentation: Try to make the play review happy 2025-06-12 08:10:57 +08:00
世界
c2e962f9da Fix missing handling of legacy domain_strategy options 2025-06-12 08:10:56 +08:00
世界
d0052e3803 Improve local DNS server 2025-06-12 08:10:56 +08:00
anytls
f2cb235305 Update anytls
Co-authored-by: anytls <anytls>
2025-06-12 08:10:55 +08:00
世界
d6bd251e2d Fix DNS dialer 2025-06-12 08:10:55 +08:00
世界
1f07157ab5 release: Skip override version for iOS 2025-06-12 08:10:55 +08:00
iikira
10afe58231 Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-06-12 08:10:54 +08:00
ReleTor
23a99c2af1 Fix fetch ECH configs 2025-06-12 08:10:54 +08:00
世界
8337505d0d Allow direct outbounds without domain_resolver 2025-06-12 08:10:53 +08:00
世界
9bd951264f Fix Tailscale dialer 2025-06-12 08:10:53 +08:00
dyhkwong
7ef938ed05 Fix DNS over QUIC stream close 2025-06-12 08:10:53 +08:00
anytls
45d20963c4 Update anytls
Co-authored-by: anytls <anytls>
2025-06-12 08:10:52 +08:00
Rambling2076
e53428481f Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-06-12 08:10:52 +08:00
世界
9d3c47b612 Fail when default DNS server not found 2025-06-12 08:10:52 +08:00
世界
029783522c Update gVisor to 20250319.0 2025-06-12 08:10:51 +08:00
世界
b7f55e931e Explicitly reject detour to empty direct outbounds 2025-06-12 08:10:51 +08:00
世界
e3405c656e Add netns support 2025-06-12 08:10:51 +08:00
世界
264c67f755 Add wildcard name support for predefined records 2025-06-12 08:10:50 +08:00
世界
b5f5e94fdb Remove map usage in options 2025-06-12 08:10:50 +08:00
世界
3b53ca6e94 Fix unhandled DNS loop 2025-06-12 08:10:50 +08:00
世界
6cb3030457 Add wildcard-sni support for shadow-tls inbound 2025-06-12 08:10:49 +08:00
k9982874
47a717c6c1 Add ntp protocol sniffing 2025-06-12 08:10:49 +08:00
世界
2167079ac6 option: Fix marshal legacy DNS options 2025-06-12 08:10:49 +08:00
世界
cf77712dc1 Make domain_resolver optional when only one DNS server is configured 2025-06-12 08:10:49 +08:00
世界
883e9dac43 Fix DNS lookup context pollution 2025-06-12 08:10:48 +08:00
世界
d1ebc72b9d Fix http3 DNS server connecting to wrong address 2025-06-12 08:10:48 +08:00
Restia-Ashbell
27efb76ac0 documentation: Fix typo 2025-06-12 08:10:47 +08:00
anytls
ada4633f49 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-06-12 08:10:47 +08:00
k9982874
f4b6f828d6 Fix hosts DNS server 2025-06-12 08:10:47 +08:00
世界
0ddef18bce Fix UDP DNS server crash 2025-06-12 08:10:47 +08:00
世界
2e196ebba6 documentation: Fix missing ip_accept_any DNS rule option 2025-06-12 08:10:46 +08:00
世界
15e41980f3 Fix anytls dialer usage 2025-06-12 08:10:46 +08:00
世界
6e8eff3296 Move predefined DNS server to rule action 2025-06-12 08:10:46 +08:00
世界
243a40ae1a Fix domain resolver on direct outbound 2025-06-12 08:10:45 +08:00
Zephyruso
0d300cd5f8 Fix missing AnyTLS display name 2025-06-12 08:10:45 +08:00
anytls
4879dd6992 Update sing-anytls
Co-authored-by: anytls <anytls>
2025-06-12 08:10:44 +08:00
Estel
ad2ff84166 documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-06-12 08:10:44 +08:00
TargetLocked
753f036748 Fix parsing legacy DNS options 2025-06-12 08:10:44 +08:00
世界
27fa1a730d Fix DNS fallback 2025-06-12 08:10:43 +08:00
世界
afdf750da4 documentation: Fix missing hosts DNS server 2025-06-12 08:10:43 +08:00
anytls
75e0ce993f Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-06-12 08:10:42 +08:00
ReleTor
88b4a8ae8e documentation: Minor fixes 2025-06-12 08:10:42 +08:00
libtry486
92c639040c documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-06-12 08:10:41 +08:00
Alireza Ahmadi
f329e859dd Fix Outbound deadlock 2025-06-12 08:10:41 +08:00
世界
4918580820 documentation: Fix AnyTLS doc 2025-06-12 08:10:41 +08:00
anytls
9dbd482971 Add AnyTLS protocol 2025-06-12 08:10:40 +08:00
世界
7cc6e1c0ae Migrate to stdlib ECH support 2025-06-12 08:10:40 +08:00
世界
2a03668d15 Add fallback local DNS server for iOS 2025-06-12 08:10:40 +08:00
世界
095721e0e5 Get darwin local DNS server from libresolv 2025-06-12 08:10:39 +08:00
世界
7d0d59c9ee Improve resolve action 2025-06-12 08:10:39 +08:00
世界
18e9eaa244 Add back port hopping to hysteria 1 2025-06-12 08:10:38 +08:00
xchacha20-poly1305
3439b75e41 Remove single quotes of raw Moziila certs 2025-06-12 08:10:38 +08:00
世界
7b095b7719 Add Tailscale endpoint 2025-06-12 08:10:37 +08:00
世界
5dd1a09663 Build legacy binaries with latest Go 2025-06-12 08:10:37 +08:00
世界
750a9be4a9 documentation: Remove outdated icons 2025-06-12 08:10:37 +08:00
世界
d21d724345 documentation: Certificate store 2025-06-12 08:10:36 +08:00
世界
74f1c3aa09 documentation: TLS fragment 2025-06-12 08:10:36 +08:00
世界
aef0d477d2 documentation: Outbound domain resolver 2025-06-12 08:10:35 +08:00
世界
24cf4e9062 documentation: Refactor DNS 2025-06-12 08:10:35 +08:00
世界
dd89730ee7 Add certificate store 2025-06-12 08:10:35 +08:00
世界
912bd00f1c Add TLS fragment support 2025-06-12 08:10:35 +08:00
世界
3ab6e6c0b0 refactor: Outbound domain resolver 2025-06-12 08:10:34 +08:00
世界
81ff3eef20 refactor: DNS 2025-06-12 08:10:34 +08:00
42 changed files with 296 additions and 528 deletions

View File

@@ -46,7 +46,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@@ -109,7 +109,7 @@ jobs:
if: ${{ ! matrix.legacy_go }} if: ${{ ! matrix.legacy_go }}
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Cache Legacy Go - name: Cache Legacy Go
if: matrix.require_legacy_go if: matrix.require_legacy_go
id: cache-legacy-go id: cache-legacy-go
@@ -294,7 +294,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@@ -374,7 +374,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Setup Android NDK - name: Setup Android NDK
id: setup-ndk id: setup-ndk
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1
@@ -472,15 +472,15 @@ jobs:
if: matrix.if if: matrix.if
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Setup Xcode stable - name: Setup Xcode stable
if: matrix.if && github.ref == 'refs/heads/main-next' if: matrix.if && github.ref == 'refs/heads/main-next'
run: |- run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Setup Xcode beta - name: Setup Xcode beta
if: matrix.if && github.ref == 'refs/heads/dev-next' if: matrix.if && github.ref == 'refs/heads/dev-next'
run: |- run: |-
sudo xcode-select -s /Applications/Xcode_16.4.app sudo xcode-select -s /Applications/Xcode_16.2.app
- name: Set tag - name: Set tag
if: matrix.if if: matrix.if
run: |- run: |-
@@ -615,7 +615,7 @@ jobs:
path: 'dist' path: 'dist'
upload: upload:
name: Upload builds name: Upload builds
if: "!failure() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')" if: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone')
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: needs:
- calculate_version - calculate_version

View File

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

View File

@@ -25,7 +25,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Check input version - name: Check input version
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
run: |- run: |-
@@ -66,7 +66,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v5
with: with:
go-version: ^1.24 go-version: ^1.24.4
- name: Setup Android NDK - name: Setup Android NDK
if: matrix.os == 'android' if: matrix.os == 'android'
uses: nttld/setup-ndk@v1 uses: nttld/setup-ndk@v1

View File

@@ -108,16 +108,6 @@ upload_ios_app_store:
cd ../sing-box-for-apple && \ cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
export_ios_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
cp build/SFI/sing-box.ipa dist/SFI.ipa
upload_ios_ipa:
cd dist && \
cp SFI.ipa "SFI-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
release_ios: build_ios upload_ios_app_store release_ios: build_ios upload_ios_app_store
build_macos: build_macos:
@@ -185,16 +175,6 @@ upload_tvos_app_store:
cd ../sing-box-for-apple && \ cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
export_tvos_ipa:
cd ../sing-box-for-apple && \
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
cp build/SFT/sing-box.ipa dist/SFT.ipa
upload_tvos_ipa:
cd dist && \
cp SFT.ipa "SFT-${VERSION}.ipa" && \
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
release_tvos: build_tvos upload_tvos_app_store release_tvos: build_tvos upload_tvos_app_store
update_apple_version: update_apple_version:

View File

@@ -3,7 +3,6 @@ package adapter
import ( import (
"context" "context"
"net/netip" "net/netip"
"time"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
@@ -37,7 +36,6 @@ type DNSQueryOptions struct {
Transport DNSTransport Transport DNSTransport
Strategy C.DomainStrategy Strategy C.DomainStrategy
LookupStrategy C.DomainStrategy LookupStrategy C.DomainStrategy
Timeout time.Duration
DisableCache bool DisableCache bool
RewriteTTL *uint32 RewriteTTL *uint32
ClientSubnet netip.Prefix ClientSubnet netip.Prefix
@@ -55,7 +53,6 @@ func DNSQueryOptionsFrom(ctx context.Context, options *option.DomainResolveOptio
return &DNSQueryOptions{ return &DNSQueryOptions{
Transport: transport, Transport: transport,
Strategy: C.DomainStrategy(options.Strategy), Strategy: C.DomainStrategy(options.Strategy),
Timeout: time.Duration(options.Timeout),
DisableCache: options.DisableCache, DisableCache: options.DisableCache,
RewriteTTL: options.RewriteTTL, RewriteTTL: options.RewriteTTL,
ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}), ClientSubnet: options.ClientSubnet.Build(netip.Prefix{}),
@@ -73,7 +70,6 @@ type DNSTransport interface {
Type() string Type() string
Tag() string Tag() string
Dependencies() []string Dependencies() []string
HasDetour() bool
Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error) Exchange(ctx context.Context, message *dns.Msg) (*dns.Msg, error)
} }

View File

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

View File

@@ -16,17 +16,15 @@ import (
) )
var ( var (
debugEnabled bool debugEnabled bool
target string target string
platform string platform string
withTailscale bool
) )
func init() { func init() {
flag.BoolVar(&debugEnabled, "debug", false, "enable debug") flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
flag.StringVar(&target, "target", "android", "target platform") flag.StringVar(&target, "target", "android", "target platform")
flag.StringVar(&platform, "platform", "", "specify platform") flag.StringVar(&platform, "platform", "", "specify platform")
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
} }
func main() { func main() {
@@ -153,9 +151,7 @@ func buildApple() {
"-v", "-v",
"-target", bindTarget, "-target", bindTarget,
"-libname=box", "-libname=box",
} "-tags-macos=" + strings.Join(memcTags, ","),
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
} }
if !debugEnabled { if !debugEnabled {
@@ -165,9 +161,6 @@ func buildApple() {
} }
tags := append(sharedTags, iosTags...) tags := append(sharedTags, iosTags...)
if withTailscale {
tags = append(tags, memcTags...)
}
if debugEnabled { if debugEnabled {
tags = append(tags, debugTags...) tags = append(tags, debugTags...)
} }

View File

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

View File

@@ -6,10 +6,7 @@ import (
"strings" "strings"
"github.com/sagernet/sing-box/common/srs" "github.com/sagernet/sing-box/common/srs"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json" "github.com/sagernet/sing/common/json"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -53,11 +50,6 @@ func decompileRuleSet(sourcePath string) error {
if err != nil { if err != nil {
return err return err
} }
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
return len(rule.AdGuardDomain) > 0
}) {
return E.New("unable to decompile binary AdGuard rules to rule-set.")
}
var outputPath string var outputPath string
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput { if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
if strings.HasSuffix(sourcePath, ".srs") { if strings.HasSuffix(sourcePath, ".srs") {
@@ -83,19 +75,3 @@ func decompileRuleSet(sourcePath string) error {
outputFile.Close() outputFile.Close()
return nil return nil
} }
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
for _, rule := range rules {
switch rule.Type {
case C.RuleTypeDefault:
if cond(rule.DefaultOptions) {
return true
}
case C.RuleTypeLogical:
if hasRule(rule.LogicalOptions.Rules, cond) {
return true
}
}
}
return false
}

View File

@@ -2,7 +2,6 @@ package adguard
import ( import (
"bufio" "bufio"
"bytes"
"io" "io"
"net/netip" "net/netip"
"os" "os"
@@ -10,10 +9,10 @@ import (
"strings" "strings"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
) )
@@ -28,7 +27,7 @@ type agdguardRuleLine struct {
isImportant bool isImportant bool
} }
func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, error) { func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
scanner := bufio.NewScanner(reader) scanner := bufio.NewScanner(reader)
var ( var (
ruleLines []agdguardRuleLine ruleLines []agdguardRuleLine
@@ -37,10 +36,7 @@ func ToOptions(reader io.Reader, logger logger.Logger) ([]option.HeadlessRule, e
parseLine: parseLine:
for scanner.Scan() { for scanner.Scan() {
ruleLine := scanner.Text() ruleLine := scanner.Text()
if ruleLine == "" { if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
continue
}
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
continue continue
} }
originRuleLine := ruleLine originRuleLine := ruleLine
@@ -96,7 +92,7 @@ parseLine:
} }
if !ignored { if !ignored {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine) log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
continue parseLine continue parseLine
} }
} }
@@ -124,35 +120,27 @@ parseLine:
ruleLine = ruleLine[1 : len(ruleLine)-1] ruleLine = ruleLine[1 : len(ruleLine)-1]
if ignoreIPCIDRRegexp(ruleLine) { if ignoreIPCIDRRegexp(ruleLine) {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine) log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
continue continue
} }
isRegexp = true isRegexp = true
} else { } else {
if strings.Contains(ruleLine, "://") { if strings.Contains(ruleLine, "://") {
ruleLine = common.SubstringAfter(ruleLine, "://") ruleLine = common.SubstringAfter(ruleLine, "://")
isSuffix = true
} }
if strings.Contains(ruleLine, "/") { if strings.Contains(ruleLine, "/") {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported rule with path: ", originRuleLine) log.Debug("ignored unsupported rule with path: ", ruleLine)
continue continue
} }
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") { if strings.Contains(ruleLine, "##") {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported rule with query: ", originRuleLine) log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
continue continue
} }
if strings.Contains(ruleLine, "[") || strings.Contains(ruleLine, "]") || if strings.Contains(ruleLine, "#$#") {
strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") ||
strings.Contains(ruleLine, "!") || strings.Contains(ruleLine, "#") {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported cosmetic filter: ", originRuleLine) log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
continue
}
if strings.Contains(ruleLine, "~") {
ignoredLines++
logger.Debug("ignored unsupported rule modifier: ", originRuleLine)
continue continue
} }
var domainCheck string var domainCheck string
@@ -163,7 +151,7 @@ parseLine:
} }
if ruleLine == "" { if ruleLine == "" {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported rule with empty domain", originRuleLine) log.Debug("ignored unsupported rule with empty domain", originRuleLine)
continue continue
} else { } else {
domainCheck = strings.ReplaceAll(domainCheck, "*", "x") domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
@@ -171,13 +159,13 @@ parseLine:
_, ipErr := parseADGuardIPCIDRLine(ruleLine) _, ipErr := parseADGuardIPCIDRLine(ruleLine)
if ipErr == nil { if ipErr == nil {
ignoredLines++ ignoredLines++
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine) log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
continue continue
} }
if M.ParseSocksaddr(domainCheck).Port != 0 { if M.ParseSocksaddr(domainCheck).Port != 0 {
logger.Debug("ignored unsupported rule with port: ", originRuleLine) log.Debug("ignored unsupported rule with port: ", ruleLine)
} else { } else {
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine) log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
} }
ignoredLines++ ignoredLines++
continue continue
@@ -295,112 +283,10 @@ parseLine:
}, },
} }
} }
if ignoredLines > 0 { log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
logger.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
}
return []option.HeadlessRule{currentRule}, nil return []option.HeadlessRule{currentRule}, nil
} }
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
if len(rules) != 1 {
return nil, ErrInvalid
}
rule := rules[0]
var (
importantDomain []string
importantDomainRegex []string
importantExcludeDomain []string
importantExcludeDomainRegex []string
domain []string
domainRegex []string
excludeDomain []string
excludeDomainRegex []string
)
parse:
for {
switch rule.Type {
case C.RuleTypeLogical:
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
return nil, ErrInvalid
}
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
return nil, ErrInvalid
}
} else {
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
return nil, ErrInvalid
}
}
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
if len(importantDomain)+len(importantDomainRegex) == 0 {
return nil, ErrInvalid
}
} else {
return nil, ErrInvalid
}
rule = rule.LogicalOptions.Rules[1]
case C.RuleTypeDefault:
domain = rule.DefaultOptions.AdGuardDomain
domainRegex = rule.DefaultOptions.DomainRegex
if len(domain)+len(domainRegex) == 0 {
return nil, ErrInvalid
}
break parse
}
}
var output bytes.Buffer
for _, ruleLine := range importantDomain {
output.WriteString(ruleLine)
output.WriteString("$important\n")
}
for _, ruleLine := range importantDomainRegex {
output.WriteString("/")
output.WriteString(ruleLine)
output.WriteString("/$important\n")
}
for _, ruleLine := range importantExcludeDomain {
output.WriteString("@@")
output.WriteString(ruleLine)
output.WriteString("$important\n")
}
for _, ruleLine := range importantExcludeDomainRegex {
output.WriteString("@@/")
output.WriteString(ruleLine)
output.WriteString("/$important\n")
}
for _, ruleLine := range domain {
output.WriteString(ruleLine)
output.WriteString("\n")
}
for _, ruleLine := range domainRegex {
output.WriteString("/")
output.WriteString(ruleLine)
output.WriteString("/\n")
}
for _, ruleLine := range excludeDomain {
output.WriteString("@@")
output.WriteString(ruleLine)
output.WriteString("\n")
}
for _, ruleLine := range excludeDomainRegex {
output.WriteString("@@/")
output.WriteString(ruleLine)
output.WriteString("/\n")
}
return output.Bytes(), nil
}
func ignoreIPCIDRRegexp(ruleLine string) bool { func ignoreIPCIDRRegexp(ruleLine string) bool {
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") { if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
ruleLine = ruleLine[12:] ruleLine = ruleLine[12:]
@@ -408,9 +294,11 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
ruleLine = ruleLine[13:] ruleLine = ruleLine[13:]
} else if strings.HasPrefix(ruleLine, "^") { } else if strings.HasPrefix(ruleLine, "^") {
ruleLine = ruleLine[1:] ruleLine = ruleLine[1:]
} else {
return false
} }
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil || _, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil return parseErr == nil
} }
func parseAdGuardHostLine(ruleLine string) (string, error) { func parseAdGuardHostLine(ruleLine string) (string, error) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,13 +5,13 @@ package tls
import ( import (
"context" "context"
"crypto/tls" "crypto/tls"
"os"
"strings" "strings"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
"github.com/caddyserver/certmagic" "github.com/caddyserver/certmagic"
"github.com/libdns/alidns" "github.com/libdns/alidns"
@@ -37,38 +37,7 @@ func (w *acmeWrapper) Close() error {
return nil return nil
} }
type acmeLogWriter struct { func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
logger logger.Logger
}
func (w *acmeLogWriter) Write(p []byte) (n int, err error) {
logLine := strings.ReplaceAll(string(p), " ", ": ")
switch {
case strings.HasPrefix(logLine, "error: "):
w.logger.Error(logLine[7:])
case strings.HasPrefix(logLine, "warn: "):
w.logger.Warn(logLine[6:])
case strings.HasPrefix(logLine, "info: "):
w.logger.Info(logLine[6:])
case strings.HasPrefix(logLine, "debug: "):
w.logger.Debug(logLine[7:])
default:
w.logger.Debug(logLine)
}
return len(p), nil
}
func (w *acmeLogWriter) Sync() error {
return nil
}
func encoderConfig() zapcore.EncoderConfig {
config := zap.NewProductionEncoderConfig()
config.TimeKey = zapcore.OmitKey
return config
}
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
var acmeServer string var acmeServer string
switch options.Provider { switch options.Provider {
case "", "letsencrypt": case "", "letsencrypt":
@@ -89,15 +58,14 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
} else { } else {
storage = certmagic.Default.Storage storage = certmagic.Default.Storage
} }
zapLogger := zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(encoderConfig()),
&acmeLogWriter{logger: logger},
zap.DebugLevel,
))
config := &certmagic.Config{ config := &certmagic.Config{
DefaultServerName: options.DefaultServerName, DefaultServerName: options.DefaultServerName,
Storage: storage, Storage: storage,
Logger: zapLogger, Logger: zap.New(zapcore.NewCore(
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
)),
} }
acmeConfig := certmagic.ACMEIssuer{ acmeConfig := certmagic.ACMEIssuer{
CA: acmeServer, CA: acmeServer,
@@ -107,7 +75,7 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort), AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort), AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: zapLogger, Logger: config.Logger,
} }
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" { if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
var solver certmagic.DNS01Solver var solver certmagic.DNS01Solver
@@ -135,7 +103,6 @@ func startACME(ctx context.Context, logger logger.Logger, options option.Inbound
GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) { GetConfigForCert: func(certificate certmagic.Certificate) (*certmagic.Config, error) {
return config, nil return config, nil
}, },
Logger: zapLogger,
}) })
config = certmagic.New(cache, *config) config = certmagic.New(cache, *config)
var tlsConfig *tls.Config var tlsConfig *tls.Config

View File

@@ -9,9 +9,8 @@ import (
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger"
) )
func startACME(ctx context.Context, logger logger.Logger, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) {
return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`) return nil, nil, E.New(`ACME is not included in this build, rebuild with -tags with_acme`)
} }

View File

@@ -169,7 +169,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
var err error var err error
if options.ACME != nil && len(options.ACME.Domain) > 0 { if options.ACME != nil && len(options.ACME.Domain) > 0 {
//nolint:staticcheck //nolint:staticcheck
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME)) tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

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

View File

@@ -30,6 +30,7 @@ var (
var _ adapter.DNSClient = (*Client)(nil) var _ adapter.DNSClient = (*Client)(nil)
type Client struct { type Client struct {
timeout time.Duration
disableCache bool disableCache bool
disableExpire bool disableExpire bool
independentCache bool independentCache bool
@@ -42,6 +43,7 @@ type Client struct {
} }
type ClientOptions struct { type ClientOptions struct {
Timeout time.Duration
DisableCache bool DisableCache bool
DisableExpire bool DisableExpire bool
IndependentCache bool IndependentCache bool
@@ -53,6 +55,7 @@ type ClientOptions struct {
func NewClient(options ClientOptions) *Client { func NewClient(options ClientOptions) *Client {
client := &Client{ client := &Client{
timeout: options.Timeout,
disableCache: options.DisableCache, disableCache: options.DisableCache,
disableExpire: options.DisableExpire, disableExpire: options.DisableExpire,
independentCache: options.IndependentCache, independentCache: options.IndependentCache,
@@ -60,6 +63,9 @@ func NewClient(options ClientOptions) *Client {
initRDRCFunc: options.RDRC, initRDRCFunc: options.RDRC,
logger: options.Logger, logger: options.Logger,
} }
if client.timeout == 0 {
client.timeout = C.DNSTimeout
}
cacheCapacity := options.CacheCapacity cacheCapacity := options.CacheCapacity
if cacheCapacity < 1024 { if cacheCapacity < 1024 {
cacheCapacity = 1024 cacheCapacity = 1024
@@ -147,15 +153,7 @@ func (c *Client) Exchange(ctx context.Context, transport adapter.DNSTransport, m
return nil, ErrResponseRejectedCached return nil, ErrResponseRejectedCached
} }
} }
timeout := options.Timeout ctx, cancel := context.WithTimeout(ctx, c.timeout)
if timeout == 0 {
if transport.HasDetour() {
timeout = C.DNSTimeout
} else {
timeout = C.DirectDNSTimeout
}
}
ctx, cancel := context.WithTimeout(ctx, timeout)
response, err := transport.Exchange(ctx, message) response, err := transport.Exchange(ctx, message)
cancel() cancel()
if err != nil { if err != nil {

View File

@@ -158,9 +158,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
if action.Strategy != C.DomainStrategyAsIS { if action.Strategy != C.DomainStrategyAsIS {
options.Strategy = action.Strategy options.Strategy = action.Strategy
} }
if action.Timeout > 0 {
options.Timeout = action.Timeout
}
if isFakeIP || action.DisableCache { if isFakeIP || action.DisableCache {
options.DisableCache = true options.DisableCache = true
} }
@@ -183,9 +180,6 @@ func (r *Router) matchDNS(ctx context.Context, allowFakeIP bool, ruleIndex int,
if action.Strategy != C.DomainStrategyAsIS { if action.Strategy != C.DomainStrategyAsIS {
options.Strategy = action.Strategy options.Strategy = action.Strategy
} }
if action.Timeout > 0 {
options.Timeout = action.Timeout
}
if action.DisableCache { if action.DisableCache {
options.DisableCache = true options.DisableCache = true
} }

View File

@@ -41,7 +41,6 @@ type Transport struct {
dns.TransportAdapter dns.TransportAdapter
ctx context.Context ctx context.Context
dialer N.Dialer dialer N.Dialer
hasDetour bool
logger logger.ContextLogger logger logger.ContextLogger
networkManager adapter.NetworkManager networkManager adapter.NetworkManager
interfaceName string interfaceName string
@@ -60,7 +59,6 @@ func NewTransport(ctx context.Context, logger log.ContextLogger, tag string, opt
TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions), TransportAdapter: dns.NewTransportAdapterWithLocalOptions(C.DNSTypeDHCP, tag, options.LocalDNSServerOptions),
ctx: ctx, ctx: ctx,
dialer: transportDialer, dialer: transportDialer,
hasDetour: options.Detour != "",
logger: logger, logger: logger,
networkManager: service.FromContext[adapter.NetworkManager](ctx), networkManager: service.FromContext[adapter.NetworkManager](ctx),
interfaceName: options.Interface, interfaceName: options.Interface,
@@ -91,10 +89,6 @@ func (t *Transport) Close() error {
return nil return nil
} }
func (t *Transport) HasDetour() bool {
return t.hasDetour
}
func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *Transport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
err := t.fetchServers() err := t.fetchServers()
if err != nil { if err != nil {

View File

@@ -3,15 +3,11 @@ package transport
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"io" "io"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os"
"strconv" "strconv"
"sync"
"time"
"github.com/sagernet/sing-box/adapter" "github.com/sagernet/sing-box/adapter"
"github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dialer"
@@ -43,13 +39,11 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
type HTTPSTransport struct { type HTTPSTransport struct {
dns.TransportAdapter dns.TransportAdapter
logger logger.ContextLogger logger logger.ContextLogger
dialer N.Dialer dialer N.Dialer
destination *url.URL destination *url.URL
headers http.Header headers http.Header
transportAccess sync.Mutex transport *http.Transport
transport *http.Transport
transportResetAt time.Time
} }
func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) { func NewHTTPS(ctx context.Context, logger log.ContextLogger, tag string, options option.RemoteHTTPSDNSServerOptions) (adapter.DNSTransport, error) {
@@ -167,33 +161,12 @@ func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
} }
func (t *HTTPSTransport) Close() error { func (t *HTTPSTransport) Close() error {
t.transportAccess.Lock()
defer t.transportAccess.Unlock()
t.transport.CloseIdleConnections() t.transport.CloseIdleConnections()
t.transport = t.transport.Clone() t.transport = t.transport.Clone()
return nil return nil
} }
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) { func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
startAt := time.Now()
response, err := t.exchange(ctx, message)
if err != nil {
if errors.Is(err, os.ErrDeadlineExceeded) {
t.transportAccess.Lock()
defer t.transportAccess.Unlock()
if t.transportResetAt.After(startAt) {
return nil, err
}
t.transport.CloseIdleConnections()
t.transport = t.transport.Clone()
t.transportResetAt = time.Now()
}
return nil, err
}
return response, nil
}
func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
exMessage := *message exMessage := *message
exMessage.Id = 0 exMessage.Id = 0
exMessage.Compress = true exMessage.Compress = true

View File

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

View File

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

View File

@@ -2,17 +2,6 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.12.0-beta.30
* Fixes and improvements
### 1.11.14
* Fixes and improvements
_We are temporarily unable to update sing-box apps on the App Store because the reviewer mistakenly found that we
violated the rules (TestFlight users are not affected)._
#### 1.12.0-beta.24 #### 1.12.0-beta.24
* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1** * Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**

View File

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

View File

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

View File

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

View File

@@ -64,7 +64,7 @@ icon: material/new-box
"auto_redirect_input_mark": "0x2023", "auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024", "auto_redirect_output_mark": "0x2024",
"loopback_address": [ "loopback_address": [
"10.7.0.1" "10.0.7.1"
], ],
"strict_route": true, "strict_route": true,
"route_address": [ "route_address": [
@@ -284,7 +284,7 @@ Connection output mark used by `auto_redirect`.
Loopback addresses make TCP connections to the specified address connect to the source address. 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. Setting option value to `10.0.7.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. When `auto_redirect` is enabled, the same behavior can be achieved for LAN devices (not just local) as a gateway.

View File

@@ -64,7 +64,7 @@ icon: material/new-box
"auto_redirect_input_mark": "0x2023", "auto_redirect_input_mark": "0x2023",
"auto_redirect_output_mark": "0x2024", "auto_redirect_output_mark": "0x2024",
"loopback_address": [ "loopback_address": [
"10.7.0.1" "10.0.7.1"
], ],
"strict_route": true, "strict_route": true,
"route_address": [ "route_address": [
@@ -283,7 +283,7 @@ tun 接口的 IPv6 前缀。
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。 环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。 将选项值设置为 `10.0.7.1` 可实现与 SideStore/StosVPN 相同的行为。
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。 当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。

View File

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

2
go.mod
View File

@@ -34,7 +34,7 @@ require (
github.com/sagernet/sing-shadowsocks v0.2.8 github.com/sagernet/sing-shadowsocks v0.2.8
github.com/sagernet/sing-shadowsocks2 v0.2.1 github.com/sagernet/sing-shadowsocks2 v0.2.1
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935 github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88 github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88
github.com/sagernet/smux v1.5.34-mod.2 github.com/sagernet/smux v1.5.34-mod.2
github.com/sagernet/tailscale v1.80.3-mod.5 github.com/sagernet/tailscale v1.80.3-mod.5

4
go.sum
View File

@@ -180,8 +180,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.1 h1:dWV9OXCeFPuYGHb6IRqlSptVnSzOelnq
github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowsocks2 v0.2.1/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA= github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935 h1:wha4BG4mrEKaIoouVyiU5BcPfKD1n0LkiL4vqdjaVps= github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5 h1:zlcioVa11g8VLz5L0yPG7PbvQrw7mrxkDDdlMPEgqDk=
github.com/sagernet/sing-tun v0.6.10-0.20250630100036-8763c24e4935/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5/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 h1:0pVm8sPOel+BoiCddW3pV3cKDKEaSioVTYDdTSKjyFI=
github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w= github.com/sagernet/sing-vmess v0.2.4-0.20250605032146-38cc72672c88/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=

View File

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

View File

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

View File

@@ -205,10 +205,6 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
} }

View File

@@ -219,10 +219,6 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
var metadata adapter.InboundContext var metadata adapter.InboundContext
metadata.Source = source metadata.Source = source
metadata.Destination = destination metadata.Destination = destination
//nolint:staticcheck
metadata.InboundDetour = h.listener.ListenOptions().Detour
//nolint:staticcheck
metadata.InboundOptions = h.listener.ListenOptions().InboundOptions
h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source) h.logger.InfoContext(ctx, "inbound connection from ", metadata.Source)
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose) (*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
} }

View File

@@ -2,6 +2,7 @@ package route
import ( import (
"context" "context"
"errors"
"net" "net"
"time" "time"
@@ -9,7 +10,7 @@ import (
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/dns"
dnsOutbound "github.com/sagernet/sing-box/protocol/dns" dnsOutbound "github.com/sagernet/sing-box/protocol/dns"
R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-tun"
"github.com/sagernet/sing/common/buf" "github.com/sagernet/sing/common/buf"
E "github.com/sagernet/sing/common/exceptions" E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/logger" "github.com/sagernet/sing/common/logger"
@@ -64,7 +65,7 @@ func (r *Router) hijackDNSPacket(ctx context.Context, conn N.PacketConn, packetB
func ExchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, logger logger.ContextLogger, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) { func ExchangeDNSPacket(ctx context.Context, router adapter.DNSRouter, logger logger.ContextLogger, conn N.PacketConn, buffer *buf.Buffer, metadata adapter.InboundContext, destination M.Socksaddr) {
err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination) err := exchangeDNSPacket(ctx, router, conn, buffer, metadata, destination)
if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) { if err != nil && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) {
logger.ErrorContext(ctx, E.Cause(err, "process DNS packet")) logger.ErrorContext(ctx, E.Cause(err, "process DNS packet"))
} }
} }

View File

@@ -76,7 +76,6 @@ func NewNetworkManager(ctx context.Context, logger logger.ContextLogger, routeOp
DomainResolver: defaultDomainResolver.Server, DomainResolver: defaultDomainResolver.Server,
DomainResolveOptions: adapter.DNSQueryOptions{ DomainResolveOptions: adapter.DNSQueryOptions{
Strategy: C.DomainStrategy(defaultDomainResolver.Strategy), Strategy: C.DomainStrategy(defaultDomainResolver.Strategy),
Timeout: time.Duration(defaultDomainResolver.Timeout),
DisableCache: defaultDomainResolver.DisableCache, DisableCache: defaultDomainResolver.DisableCache,
RewriteTTL: defaultDomainResolver.RewriteTTL, RewriteTTL: defaultDomainResolver.RewriteTTL,
ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}), ClientSubnet: defaultDomainResolver.ClientSubnet.Build(netip.Prefix{}),

View File

@@ -15,7 +15,7 @@ import (
"github.com/sagernet/sing-box/common/sniff" "github.com/sagernet/sing-box/common/sniff"
C "github.com/sagernet/sing-box/constant" C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/option"
R "github.com/sagernet/sing-box/route/rule" "github.com/sagernet/sing-box/route/rule"
"github.com/sagernet/sing-mux" "github.com/sagernet/sing-mux"
"github.com/sagernet/sing-vmess" "github.com/sagernet/sing-vmess"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
@@ -49,7 +49,7 @@ func (r *Router) RouteConnectionEx(ctx context.Context, conn net.Conn, metadata
err := r.routeConnection(ctx, conn, metadata, onClose) err := r.routeConnection(ctx, conn, metadata, onClose)
if err != nil { if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
if E.IsClosedOrCanceled(err) || R.IsRejected(err) { if E.IsClosedOrCanceled(err) {
r.logger.DebugContext(ctx, "connection closed: ", err) r.logger.DebugContext(ctx, "connection closed: ", err)
} else { } else {
r.logger.ErrorContext(ctx, err) r.logger.ErrorContext(ctx, err)
@@ -99,7 +99,7 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
var selectedOutbound adapter.Outbound var selectedOutbound adapter.Outbound
if selectedRule != nil { if selectedRule != nil {
switch action := selectedRule.Action().(type) { switch action := selectedRule.Action().(type) {
case *R.RuleActionRoute: case *rule.RuleActionRoute:
var loaded bool var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded { if !loaded {
@@ -110,10 +110,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
buf.ReleaseMulti(buffers) buf.ReleaseMulti(buffers)
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag()) return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
} }
case *R.RuleActionReject: case *rule.RuleActionReject:
buf.ReleaseMulti(buffers) buf.ReleaseMulti(buffers)
return action.Error(ctx) return action.Error(ctx)
case *R.RuleActionHijackDNS: case *rule.RuleActionHijackDNS:
for _, buffer := range buffers { for _, buffer := range buffers {
conn = bufio.NewCachedConn(conn, buffer) conn = bufio.NewCachedConn(conn, buffer)
} }
@@ -151,7 +151,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
})) }))
if err != nil { if err != nil {
conn.Close() conn.Close()
if E.IsClosedOrCanceled(err) || R.IsRejected(err) { if E.IsClosedOrCanceled(err) {
r.logger.DebugContext(ctx, "connection closed: ", err) r.logger.DebugContext(ctx, "connection closed: ", err)
} else { } else {
r.logger.ErrorContext(ctx, err) r.logger.ErrorContext(ctx, err)
@@ -168,7 +168,7 @@ func (r *Router) RoutePacketConnectionEx(ctx context.Context, conn N.PacketConn,
err := r.routePacketConnection(ctx, conn, metadata, onClose) err := r.routePacketConnection(ctx, conn, metadata, onClose)
if err != nil { if err != nil {
N.CloseOnHandshakeFailure(conn, onClose, err) N.CloseOnHandshakeFailure(conn, onClose, err)
if E.IsClosedOrCanceled(err) || R.IsRejected(err) { if E.IsClosedOrCanceled(err) {
r.logger.DebugContext(ctx, "connection closed: ", err) r.logger.DebugContext(ctx, "connection closed: ", err)
} else { } else {
r.logger.ErrorContext(ctx, err) r.logger.ErrorContext(ctx, err)
@@ -214,7 +214,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
var selectReturn bool var selectReturn bool
if selectedRule != nil { if selectedRule != nil {
switch action := selectedRule.Action().(type) { switch action := selectedRule.Action().(type) {
case *R.RuleActionRoute: case *rule.RuleActionRoute:
var loaded bool var loaded bool
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound) selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
if !loaded { if !loaded {
@@ -225,10 +225,10 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
N.ReleaseMultiPacketBuffer(packetBuffers) N.ReleaseMultiPacketBuffer(packetBuffers)
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag()) return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
} }
case *R.RuleActionReject: case *rule.RuleActionReject:
N.ReleaseMultiPacketBuffer(packetBuffers) N.ReleaseMultiPacketBuffer(packetBuffers)
return action.Error(ctx) return action.Error(ctx)
case *R.RuleActionHijackDNS: case *rule.RuleActionHijackDNS:
return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose) return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
} }
} }
@@ -266,7 +266,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error {
if selectedRule == nil { if selectedRule == nil {
return nil return nil
} }
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject) rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject)
if !isReject { if !isReject {
return nil return nil
} }
@@ -342,7 +342,7 @@ func (r *Router) matchRule(
//nolint:staticcheck //nolint:staticcheck
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() { if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
if !preMatch && metadata.InboundOptions.SniffEnabled { if !preMatch && metadata.InboundOptions.SniffEnabled {
newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.RuleActionSniff{ newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &rule.RuleActionSniff{
OverrideDestination: metadata.InboundOptions.SniffOverrideDestination, OverrideDestination: metadata.InboundOptions.SniffOverrideDestination,
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout), Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
}, inputConn, inputPacketConn, nil) }, inputConn, inputPacketConn, nil)
@@ -357,7 +357,7 @@ func (r *Router) matchRule(
} }
} }
if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS { if C.DomainStrategy(metadata.InboundOptions.DomainStrategy) != C.DomainStrategyAsIS {
fatalErr = r.actionResolve(ctx, metadata, &R.RuleActionResolve{ fatalErr = r.actionResolve(ctx, metadata, &rule.RuleActionResolve{
Strategy: C.DomainStrategy(metadata.InboundOptions.DomainStrategy), Strategy: C.DomainStrategy(metadata.InboundOptions.DomainStrategy),
}) })
if fatalErr != nil { if fatalErr != nil {
@@ -394,11 +394,11 @@ match:
} }
} }
} }
var routeOptions *R.RuleActionRouteOptions var routeOptions *rule.RuleActionRouteOptions
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *R.RuleActionRoute: case *rule.RuleActionRoute:
routeOptions = &action.RuleActionRouteOptions routeOptions = &action.RuleActionRouteOptions
case *R.RuleActionRouteOptions: case *rule.RuleActionRouteOptions:
routeOptions = action routeOptions = action
} }
if routeOptions != nil { if routeOptions != nil {
@@ -451,7 +451,7 @@ match:
} }
} }
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *R.RuleActionSniff: case *rule.RuleActionSniff:
if !preMatch { if !preMatch {
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers) newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
if newErr != nil { if newErr != nil {
@@ -468,7 +468,7 @@ match:
selectedRuleIndex = currentRuleIndex selectedRuleIndex = currentRuleIndex
break match break match
} }
case *R.RuleActionResolve: case *rule.RuleActionResolve:
fatalErr = r.actionResolve(ctx, metadata, action) fatalErr = r.actionResolve(ctx, metadata, action)
if fatalErr != nil { if fatalErr != nil {
return return
@@ -488,7 +488,7 @@ match:
} }
func (r *Router) actionSniff( func (r *Router) actionSniff(
ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionSniff, ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionSniff,
inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer, inputConn net.Conn, inputPacketConn N.PacketConn, inputBuffers []*buf.Buffer,
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) { ) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
if sniff.Skip(metadata) { if sniff.Skip(metadata) {
@@ -501,9 +501,6 @@ func (r *Router) actionSniff(
if inputConn != nil { if inputConn != nil {
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 { if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
return return
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return
} }
var streamSniffers []sniff.StreamSniffer var streamSniffers []sniff.StreamSniffer
if len(action.StreamSniffers) > 0 { if len(action.StreamSniffers) > 0 {
@@ -528,7 +525,6 @@ func (r *Router) actionSniff(
action.Timeout, action.Timeout,
streamSniffers..., streamSniffers...,
) )
metadata.SniffError = err
if err == nil { if err == nil {
//goland:noinspection GoDeprecation //goland:noinspection GoDeprecation
if action.OverrideDestination && M.IsDomainName(metadata.Domain) { if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
@@ -553,8 +549,8 @@ func (r *Router) actionSniff(
} else if inputPacketConn != nil { } else if inputPacketConn != nil {
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 { if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
return return
} else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) { } else if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError) r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
return return
} }
var packetSniffers []sniff.PacketSniffer var packetSniffers []sniff.PacketSniffer
@@ -602,7 +598,7 @@ func (r *Router) actionSniff(
return return
} }
} else { } else {
if len(packetBuffers) > 0 || metadata.SniffError != nil { if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
err = sniff.PeekPacket( err = sniff.PeekPacket(
ctx, ctx,
metadata, metadata,
@@ -622,7 +618,7 @@ func (r *Router) actionSniff(
Destination: destination, Destination: destination,
} }
packetBuffers = append(packetBuffers, packetBuffer) packetBuffers = append(packetBuffers, packetBuffer)
metadata.SniffError = err metadata.PacketSniffError = err
if errors.Is(err, sniff.ErrNeedMoreData) { if errors.Is(err, sniff.ErrNeedMoreData) {
// TODO: replace with generic message when there are more multi-packet protocols // TODO: replace with generic message when there are more multi-packet protocols
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello") r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
@@ -653,7 +649,7 @@ func (r *Router) actionSniff(
return return
} }
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error { func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error {
if metadata.Destination.IsFqdn() { if metadata.Destination.IsFqdn() {
var transport adapter.DNSTransport var transport adapter.DNSTransport
if action.Server != "" { if action.Server != "" {
@@ -666,7 +662,6 @@ func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundCon
addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{ addresses, err := r.dns.Lookup(adapter.WithContext(ctx, metadata), metadata.Destination.Fqdn, adapter.DNSQueryOptions{
Transport: transport, Transport: transport,
Strategy: action.Strategy, Strategy: action.Strategy,
Timeout: action.Timeout,
DisableCache: action.DisableCache, DisableCache: action.DisableCache,
RewriteTTL: action.RewriteTTL, RewriteTTL: action.RewriteTTL,
ClientSubnet: action.ClientSubnet, ClientSubnet: action.ClientSubnet,

View File

@@ -2,7 +2,6 @@ package rule
import ( import (
"context" "context"
"errors"
"net/netip" "net/netip"
"strings" "strings"
"sync" "sync"
@@ -113,7 +112,6 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
Server: action.RouteOptions.Server, Server: action.RouteOptions.Server,
RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{ RuleActionDNSRouteOptions: RuleActionDNSRouteOptions{
Strategy: C.DomainStrategy(action.RouteOptions.Strategy), Strategy: C.DomainStrategy(action.RouteOptions.Strategy),
Timeout: time.Duration(action.RouteOptions.Timeout),
DisableCache: action.RouteOptions.DisableCache, DisableCache: action.RouteOptions.DisableCache,
RewriteTTL: action.RouteOptions.RewriteTTL, RewriteTTL: action.RouteOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)), ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptions.ClientSubnet)),
@@ -122,7 +120,6 @@ func NewDNSRuleAction(logger logger.ContextLogger, action option.DNSRuleAction)
case C.RuleActionTypeRouteOptions: case C.RuleActionTypeRouteOptions:
return &RuleActionDNSRouteOptions{ return &RuleActionDNSRouteOptions{
Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy), Strategy: C.DomainStrategy(action.RouteOptionsOptions.Strategy),
Timeout: time.Duration(action.RouteOptionsOptions.Timeout),
DisableCache: action.RouteOptionsOptions.DisableCache, DisableCache: action.RouteOptionsOptions.DisableCache,
RewriteTTL: action.RouteOptionsOptions.RewriteTTL, RewriteTTL: action.RouteOptionsOptions.RewriteTTL,
ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)), ClientSubnet: netip.Prefix(common.PtrValueOrDefault(action.RouteOptionsOptions.ClientSubnet)),
@@ -237,13 +234,20 @@ func (r *RuleActionDNSRoute) Type() string {
func (r *RuleActionDNSRoute) String() string { func (r *RuleActionDNSRoute) String() string {
var descriptions []string var descriptions []string
descriptions = append(descriptions, r.Server) descriptions = append(descriptions, r.Server)
descriptions = append(descriptions, r.Descriptions()...) if r.DisableCache {
descriptions = append(descriptions, "disable-cache")
}
if r.RewriteTTL != nil {
descriptions = append(descriptions, F.ToString("rewrite-ttl=", *r.RewriteTTL))
}
if r.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
}
return F.ToString("route(", strings.Join(descriptions, ","), ")") return F.ToString("route(", strings.Join(descriptions, ","), ")")
} }
type RuleActionDNSRouteOptions struct { type RuleActionDNSRouteOptions struct {
Strategy C.DomainStrategy Strategy C.DomainStrategy
Timeout time.Duration
DisableCache bool DisableCache bool
RewriteTTL *uint32 RewriteTTL *uint32
ClientSubnet netip.Prefix ClientSubnet netip.Prefix
@@ -254,17 +258,7 @@ func (r *RuleActionDNSRouteOptions) Type() string {
} }
func (r *RuleActionDNSRouteOptions) String() string { func (r *RuleActionDNSRouteOptions) String() string {
return F.ToString("route-options(", strings.Join(r.Descriptions(), ","), ")")
}
func (r *RuleActionDNSRouteOptions) Descriptions() []string {
var descriptions []string var descriptions []string
if r.Strategy != C.DomainStrategyAsIS {
descriptions = append(descriptions, F.ToString("strategy=", option.DomainStrategy(r.Strategy)))
}
if r.Timeout > 0 {
descriptions = append(descriptions, F.ToString("timeout=", r.Timeout.String()))
}
if r.DisableCache { if r.DisableCache {
descriptions = append(descriptions, "disable-cache") descriptions = append(descriptions, "disable-cache")
} }
@@ -274,7 +268,7 @@ func (r *RuleActionDNSRouteOptions) Descriptions() []string {
if r.ClientSubnet.IsValid() { if r.ClientSubnet.IsValid() {
descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet)) descriptions = append(descriptions, F.ToString("client-subnet=", r.ClientSubnet))
} }
return descriptions return F.ToString("route-options(", strings.Join(descriptions, ","), ")")
} }
type RuleActionDirect struct { type RuleActionDirect struct {
@@ -290,23 +284,6 @@ func (r *RuleActionDirect) String() string {
return "direct" + r.description return "direct" + r.description
} }
type RejectedError struct {
Cause error
}
func (r *RejectedError) Error() string {
return "rejected"
}
func (r *RejectedError) Unwrap() error {
return r.Cause
}
func IsRejected(err error) bool {
var rejected *RejectedError
return errors.As(err, &rejected)
}
type RuleActionReject struct { type RuleActionReject struct {
Method string Method string
NoDrop bool NoDrop bool
@@ -330,9 +307,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
var returnErr error var returnErr error
switch r.Method { switch r.Method {
case C.RuleActionRejectMethodDefault: case C.RuleActionRejectMethodDefault:
returnErr = &RejectedError{syscall.ECONNREFUSED} returnErr = syscall.ECONNREFUSED
case C.RuleActionRejectMethodDrop: case C.RuleActionRejectMethodDrop:
return &RejectedError{tun.ErrDrop} return tun.ErrDrop
default: default:
panic(F.ToString("unknown reject method: ", r.Method)) panic(F.ToString("unknown reject method: ", r.Method))
} }
@@ -350,7 +327,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
if ctx != nil { if ctx != nil {
r.logger.DebugContext(ctx, "dropped due to flooding") r.logger.DebugContext(ctx, "dropped due to flooding")
} }
return &RejectedError{tun.ErrDrop} return tun.ErrDrop
} }
return returnErr return returnErr
} }
@@ -426,7 +403,6 @@ func (r *RuleActionSniff) String() string {
type RuleActionResolve struct { type RuleActionResolve struct {
Server string Server string
Strategy C.DomainStrategy Strategy C.DomainStrategy
Timeout time.Duration
DisableCache bool DisableCache bool
RewriteTTL *uint32 RewriteTTL *uint32
ClientSubnet netip.Prefix ClientSubnet netip.Prefix
@@ -444,9 +420,6 @@ func (r *RuleActionResolve) String() string {
if r.Strategy != C.DomainStrategyAsIS { if r.Strategy != C.DomainStrategyAsIS {
options = append(options, F.ToString(option.DomainStrategy(r.Strategy))) options = append(options, F.ToString(option.DomainStrategy(r.Strategy)))
} }
if r.Timeout > 0 {
options = append(options, F.ToString("timeout=", r.Timeout.String()))
}
if r.DisableCache { if r.DisableCache {
options = append(options, "disable_cache") options = append(options, "disable_cache")
} }