Compare commits

..

126 Commits

Author SHA1 Message Date
世界
989034b8f7
documentation: Bump version 2025-07-03 22:20:37 +08:00
世界
fef0a17bf8
release: Fix publish testflight 2025-07-03 21:49:38 +08:00
世界
c54f87e722
Improve darwin tun performance 2025-07-03 21:49:38 +08:00
世界
9bd9e5ab83
Improve nftables rules for openwrt 2025-07-03 21:49:37 +08:00
世界
7eb72226ab
Fixed DoH server recover from conn freezes 2025-07-03 21:49:37 +08:00
世界
1b08cdeb0a
Update libresolv usage 2025-07-03 21:49:37 +08:00
yu
88f989b934
documentation: Update client configuration manual 2025-07-03 21:49:36 +08:00
yanwo
65af6c7cdf
documentation: Fix typo
Signed-off-by: yanwo <ogilvy@gmail.com>
2025-07-03 21:49:36 +08:00
anytinz
7935126887
documentation: Fix wrong SideStore loopback ip 2025-07-03 21:49:36 +08:00
世界
1a78d3d28d
Revert "release: Add IPA build"
After testing, it seems that since extensions are not handled correctly, it cannot be installed by SideStore.
2025-07-03 21:49:36 +08:00
世界
0314c83afd
release: Add IPA build 2025-07-03 21:49:36 +08:00
世界
beb8741925
Add API to dump AdGuard rules 2025-07-03 21:49:35 +08:00
Sukka
74478e5f84
Improve AdGuard rule-set parser 2025-07-03 21:49:35 +08:00
Restia-Ashbell
82b9af7418
Add ECH support for uTLS 2025-07-03 21:49:35 +08:00
世界
3a5e6ee8fd
Improve TLS fragments 2025-07-03 21:49:34 +08:00
世界
cb20c6ec7f
Add cache support for ssm-api 2025-07-03 21:49:34 +08:00
世界
a5aef88073
Fix service will not be closed 2025-07-03 21:49:34 +08:00
世界
ab0af2960d
Add loopback address support for tun 2025-07-03 21:49:34 +08:00
世界
32a081d229
Fix tproxy listener 2025-07-03 21:49:33 +08:00
世界
6b7b1b2eac
Fix systemd package 2025-07-03 21:49:33 +08:00
世界
81364e3294
Fix missing home for derp service 2025-07-03 21:49:33 +08:00
Zero Clover
ee9474dd07
documentation: Fix services 2025-07-03 21:49:32 +08:00
世界
ec6b78d1b6
Fix dns.client_subnet ignored 2025-07-03 21:49:32 +08:00
世界
078fb5555d
documentation: Minor fixes 2025-07-03 21:49:32 +08:00
世界
99ea71119a
Fix tailscale forward 2025-07-03 21:49:31 +08:00
世界
d0ed53dae3
Minor fixes 2025-07-03 21:49:31 +08:00
世界
79deb889b1
Add SSM API service 2025-07-03 21:49:31 +08:00
世界
480d92901e
Add resolved service and DNS server 2025-07-03 21:49:30 +08:00
世界
3510a228e2
Add DERP service 2025-07-03 21:49:30 +08:00
世界
135e8e673f
Add service component type 2025-07-03 21:49:30 +08:00
世界
97fb38d8bf
Fix tproxy tcp control 2025-07-03 21:49:29 +08:00
愚者
f376bf834f
release: Fix build tags for android
Signed-off-by: 愚者 <11926619+FansChou@users.noreply.github.com>
2025-07-03 21:49:29 +08:00
世界
b9f53ec6cc
prevent creation of bind and mark controls on unsupported platforms 2025-07-03 21:49:29 +08:00
PuerNya
9208dd58c2
documentation: Fix description of reject DNS action behavior 2025-07-03 21:49:29 +08:00
Restia-Ashbell
c548a90f4a
Fix TLS record fragment 2025-07-03 21:49:28 +08:00
世界
e57e5f4415
Add missing accept_routes option for Tailscale 2025-07-03 21:49:28 +08:00
世界
63520226a0
Add TLS record fragment support 2025-07-03 21:49:28 +08:00
世界
02aeaca87a
Fix set edns0 client subnet 2025-07-03 21:49:27 +08:00
世界
c00373d930
Update minor dependencies 2025-07-03 21:49:27 +08:00
世界
db8ece9667
Update certmagic and providers 2025-07-03 21:49:27 +08:00
世界
763ca0c3f8
Update protobuf and grpc 2025-07-03 21:49:27 +08:00
世界
d641c152a7
Add control options for listeners 2025-07-03 21:49:26 +08:00
世界
5ccb67f1a5
Update quic-go to v0.52.0 2025-07-03 21:49:26 +08:00
世界
b763a2f178
Update utls to v1.7.2 2025-07-03 21:49:26 +08:00
世界
81a14bb5f2
Handle EDNS version downgrade 2025-07-03 21:49:25 +08:00
世界
5dbc9d2fc5
documentation: Fix anytls padding scheme description 2025-07-03 21:49:25 +08:00
安容
a35c4a6a96
Report invalid DNS address early 2025-07-03 21:49:25 +08:00
世界
ea2d355907
Fix wireguard listen_port 2025-07-03 21:49:24 +08:00
世界
ee58016702
clash-api: Add more meta api 2025-07-03 21:49:24 +08:00
世界
b1dc45898b
Fix DNS lookup 2025-07-03 21:49:24 +08:00
世界
f51c2a4e66
Fix fetch ECH configs 2025-07-03 21:49:24 +08:00
reletor
3cd42b6d37
documentation: Minor fixes 2025-07-03 21:49:23 +08:00
caelansar
9494f2ae21
Fix callback deletion in UDP transport 2025-07-03 21:49:23 +08:00
世界
23c24cace8
documentation: Try to make the play review happy 2025-07-03 21:49:23 +08:00
世界
e225a6476e
Fix missing handling of legacy domain_strategy options 2025-07-03 21:49:23 +08:00
世界
53ea9cb4c7
Improve local DNS server 2025-07-03 21:49:22 +08:00
anytls
7b5eef8ae2
Update anytls
Co-authored-by: anytls <anytls>
2025-07-03 21:49:22 +08:00
世界
ffa70c0288
Fix DNS dialer 2025-07-03 21:49:21 +08:00
世界
1cb605924a
release: Skip override version for iOS 2025-07-03 21:49:21 +08:00
iikira
90320bc80d
Fix UDP DNS server crash
Signed-off-by: iikira <i2@mail.iikira.com>
2025-07-03 21:49:21 +08:00
ReleTor
b4402e27d9
Fix fetch ECH configs 2025-07-03 21:49:20 +08:00
世界
14bcf34f86
Allow direct outbounds without domain_resolver 2025-07-03 21:49:20 +08:00
世界
e41cb6d559
Fix Tailscale dialer 2025-07-03 21:49:20 +08:00
dyhkwong
d90dec5381
Fix DNS over QUIC stream close 2025-07-03 21:49:20 +08:00
anytls
e09aa7ca84
Update anytls
Co-authored-by: anytls <anytls>
2025-07-03 21:49:20 +08:00
Rambling2076
832d37b808
Fix missing with_tailscale in Dockerfile
Signed-off-by: Rambling2076 <Rambling2076@proton.me>
2025-07-03 21:49:19 +08:00
世界
3a70ae7afe
Fail when default DNS server not found 2025-07-03 21:49:19 +08:00
世界
cf1058de4c
Update gVisor to 20250319.0 2025-07-03 21:49:19 +08:00
世界
73e6bbb49a
Explicitly reject detour to empty direct outbounds 2025-07-03 21:49:18 +08:00
世界
329d4bb4c9
Add netns support 2025-07-03 21:49:18 +08:00
世界
b43615ef35
Add wildcard name support for predefined records 2025-07-03 21:49:18 +08:00
世界
e7479dea90
Remove map usage in options 2025-07-03 21:49:17 +08:00
世界
f1f1406eb1
Fix unhandled DNS loop 2025-07-03 21:49:17 +08:00
世界
fe9cc7461b
Add wildcard-sni support for shadow-tls inbound 2025-07-03 21:49:17 +08:00
k9982874
710ca243aa
Add ntp protocol sniffing 2025-07-03 21:49:16 +08:00
世界
a249770e24
option: Fix marshal legacy DNS options 2025-07-03 21:49:16 +08:00
世界
6d2bd2116b
Make domain_resolver optional when only one DNS server is configured 2025-07-03 21:49:16 +08:00
世界
2e1698fa78
Fix DNS lookup context pollution 2025-07-03 21:49:16 +08:00
世界
6946ec37cf
Fix http3 DNS server connecting to wrong address 2025-07-03 21:49:15 +08:00
Restia-Ashbell
25f844ec6d
documentation: Fix typo 2025-07-03 21:49:15 +08:00
anytls
5a5c906fb2
Update sing-anytls
Co-authored-by: anytls <anytls>
2025-07-03 21:49:15 +08:00
k9982874
31c191debb
Fix hosts DNS server 2025-07-03 21:49:15 +08:00
世界
1b02fca33f
Fix UDP DNS server crash 2025-07-03 21:49:14 +08:00
世界
5308c46e07
documentation: Fix missing ip_accept_any DNS rule option 2025-07-03 21:49:14 +08:00
世界
0cfcaf2c62
Fix anytls dialer usage 2025-07-03 21:49:14 +08:00
世界
a2bd384c32
Move predefined DNS server to rule action 2025-07-03 21:49:13 +08:00
世界
3723dd2583
Fix domain resolver on direct outbound 2025-07-03 21:49:13 +08:00
Zephyruso
9bbc3dabc8
Fix missing AnyTLS display name 2025-07-03 21:49:13 +08:00
anytls
6827d67a09
Update sing-anytls
Co-authored-by: anytls <anytls>
2025-07-03 21:49:13 +08:00
Estel
5ae3e97388
documentation: Fix typo
Signed-off-by: Estel <callmebedrockdigger@gmail.com>
2025-07-03 21:49:13 +08:00
TargetLocked
f6d1099b69
Fix parsing legacy DNS options 2025-07-03 21:49:12 +08:00
世界
ca1b5bbcdf
Fix DNS fallback 2025-07-03 21:49:12 +08:00
世界
1c6c48ea8d
documentation: Fix missing hosts DNS server 2025-07-03 21:49:11 +08:00
anytls
0d86d225a3
Add MinIdleSession option to AnyTLS outbound
Co-authored-by: anytls <anytls>
2025-07-03 21:49:11 +08:00
ReleTor
b75e6b88b7
documentation: Minor fixes 2025-07-03 21:49:11 +08:00
libtry486
000f7b1045
documentation: Fix typo
fix typo

Signed-off-by: libtry486 <89328481+libtry486@users.noreply.github.com>
2025-07-03 21:49:11 +08:00
Alireza Ahmadi
b39204f4aa
Fix Outbound deadlock 2025-07-03 21:49:10 +08:00
世界
2b2655ffc7
documentation: Fix AnyTLS doc 2025-07-03 21:49:10 +08:00
anytls
3f9f41caed
Add AnyTLS protocol 2025-07-03 21:49:10 +08:00
世界
0231e3c575
Migrate to stdlib ECH support 2025-07-03 21:49:09 +08:00
世界
918b70b1cc
Add fallback local DNS server for iOS 2025-07-03 21:49:09 +08:00
世界
af4c4d1841
Get darwin local DNS server from libresolv 2025-07-03 21:49:09 +08:00
世界
88e830b5d8
Improve resolve action 2025-07-03 21:49:08 +08:00
世界
7eb14cfe9f
Add back port hopping to hysteria 1 2025-07-03 21:49:08 +08:00
xchacha20-poly1305
e6761fa538
Remove single quotes of raw Moziila certs 2025-07-03 21:49:08 +08:00
世界
485d28ceec
Add Tailscale endpoint 2025-07-03 21:49:07 +08:00
世界
159a2858e2
Build legacy binaries with latest Go 2025-07-03 21:49:07 +08:00
世界
b6691707e9
documentation: Remove outdated icons 2025-07-03 21:49:07 +08:00
世界
38f1736f78
documentation: Certificate store 2025-07-03 21:49:06 +08:00
世界
ff77bf63f5
documentation: TLS fragment 2025-07-03 21:49:06 +08:00
世界
9dad482e70
documentation: Outbound domain resolver 2025-07-03 21:49:06 +08:00
世界
d43791307c
documentation: Refactor DNS 2025-07-03 21:49:05 +08:00
世界
0a82b8a9ad
Add certificate store 2025-07-03 21:49:05 +08:00
世界
7bf91fb2af
Add TLS fragment support 2025-07-03 21:49:05 +08:00
世界
81d2eb5f3f
refactor: Outbound domain resolver 2025-07-03 21:49:05 +08:00
世界
ee731a32c8
refactor: DNS 2025-07-03 21:49:04 +08:00
世界
6f804adf39
Fix v2rayhttp crash 2025-07-03 21:48:10 +08:00
Kyson
36db31c55a
documentation: Fix typo
Co-authored-by: chenqixin <chenqixin@bytedance.com>
2025-06-29 18:54:05 +08:00
世界
4dbbf59c82
Fix logger for acme 2025-06-29 18:44:40 +08:00
世界
832eb4458d
release: Fix xcode version 2025-06-29 18:44:40 +08:00
dyhkwong
2cf989d306
Fix inbound with v2ray transport missing InboundOptions 2025-06-25 13:20:00 +08:00
世界
7d3ee29bd0
Also skip duplicate sniff for TCP 2025-06-21 12:57:27 +08:00
世界
cba0e46aba
Fix log for rejected connections 2025-06-21 12:57:26 +08:00
世界
9b8ab3e61e
Bump version 2025-06-19 11:57:44 +08:00
dyhkwong
47f18e823a
Fix: macOS udp find process should use unspecified fallback
be8d63ba8f
2025-06-18 08:34:59 +08:00
世界
2d1b824b62
Fix gLazyConn race 2025-06-17 14:24:11 +08:00
35 changed files with 503 additions and 299 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.4 go-version: ^1.24
- 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.4 go-version: ^1.24
- 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.4 go-version: ^1.24
- 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.4 go-version: ^1.24
- 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.4 go-version: ^1.24
- 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.2.app sudo xcode-select -s /Applications/Xcode_16.4.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.2.app sudo xcode-select -s /Applications/Xcode_16.4.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: always() && github.event_name == 'workflow_dispatch' && (inputs.build == 'All' || inputs.build == 'Binary' || inputs.build == 'Android' || inputs.build == 'Apple' || inputs.build == 'macOS-standalone') if: "!failure() && 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.4 go-version: ^1.24
- 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.4 go-version: ^1.24
- 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.4 go-version: ^1.24
- 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,6 +108,16 @@ 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:
@ -175,6 +185,16 @@ 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:
@ -225,8 +245,8 @@ lib:
go run ./cmd/internal/build_libbox -target ios go run ./cmd/internal/build_libbox -target ios
lib_install: lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6 go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.7
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6 go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7
docs: docs:
venv/bin/mkdocs serve venv/bin/mkdocs serve

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
PacketSniffError error SniffError error
// cache // cache

@ -1 +1 @@
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0 Subproject commit eb2e13a6f9a8c03a35ae672395ccab0a6bdcd954

View File

@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
} }
log.Info(string(platform), " ", tag, " publish") log.Info(string(platform), " ", tag, " publish")
response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID}) response, err := client.TestFlight.AddBuildsToBetaGroup(ctx, groupID, []string{build.ID})
if response != nil && response.StatusCode == http.StatusUnprocessableEntity { if response != nil && (response.StatusCode == http.StatusUnprocessableEntity || response.StatusCode == http.StatusNotFound) {
log.Info("waiting for process") log.Info("waiting for process")
time.Sleep(15 * time.Second) time.Sleep(15 * time.Second)
continue continue

View File

@ -16,15 +16,17 @@ 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() {
@ -44,8 +46,9 @@ var (
sharedFlags []string sharedFlags []string
debugFlags []string debugFlags []string
sharedTags []string sharedTags []string
iosTags []string darwinTags []string
memcTags []string memcTags []string
notMemcTags []string
debugTags []string debugTags []string
) )
@ -60,8 +63,9 @@ func init() {
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag) debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack") sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
iosTags = append(iosTags, "with_dhcp", "with_low_memory") darwinTags = append(darwinTags, "with_dhcp")
memcTags = append(memcTags, "with_tailscale") memcTags = append(memcTags, "with_tailscale")
notMemcTags = append(notMemcTags, "with_low_memory")
debugTags = append(debugTags, "debug") debugTags = append(debugTags, "debug")
} }
@ -151,7 +155,10 @@ func buildApple() {
"-v", "-v",
"-target", bindTarget, "-target", bindTarget,
"-libname=box", "-libname=box",
"-tags-macos=" + strings.Join(memcTags, ","), "-tags-not-macos=with_low_memory",
}
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
} }
if !debugEnabled { if !debugEnabled {
@ -160,7 +167,10 @@ func buildApple() {
args = append(args, debugFlags...) args = append(args, debugFlags...)
} }
tags := append(sharedTags, iosTags...) tags := append(sharedTags, darwinTags...)
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/cmd/sing-box/internal/convertor/adguard" "github.com/sagernet/sing-box/common/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.Convert(reader) rules, err = adguard.ToOptions(reader, log.StdLogger())
case "": case "":
return E.New("source type is required") return E.New("source type is required")
default: default:

View File

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

View File

@ -7,13 +7,15 @@ 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()
rules, err := Convert(strings.NewReader(` ruleString := `||sagernet.org^$important
@@|sing-box.sagernet.org^$important
||example.org^ ||example.org^
|example.com^ |example.com^
example.net^ example.net^
@ -21,10 +23,9 @@ 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 `
@@|sing-box.sagernet.org^$important rules, err := ToOptions(strings.NewReader(ruleString), 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])
@ -75,15 +76,18 @@ 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 := Convert(strings.NewReader(` rules, err := ToOptions(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])
@ -110,10 +114,10 @@ func TestHosts(t *testing.T) {
func TestSimpleHosts(t *testing.T) { func TestSimpleHosts(t *testing.T) {
t.Parallel() t.Parallel()
rules, err := Convert(strings.NewReader(` rules, err := ToOptions(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

@ -76,6 +76,8 @@ 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
@ -90,10 +92,12 @@ 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]))
@ -101,13 +105,21 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
continue continue
} }
if ip != srcIP { if ip == srcIP {
continue // xsocket_n.so_last_pid
pid := readNativeUint32(buf[so+68 : so+72])
return getExecPathFromPID(pid)
} }
// xsocket_n.so_last_pid // udp packet connection may be not equal with srcIP
pid := readNativeUint32(buf[so+68 : so+72]) if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
return getExecPathFromPID(pid) pid := readNativeUint32(buf[so+68 : so+72])
fallbackUDPProcess, _ = getExecPathFromPID(pid)
}
}
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
return fallbackUDPProcess, nil
} }
return "", ErrNotFound return "", ErrNotFound

View File

@ -215,16 +215,15 @@ 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,7 +37,38 @@ func (w *acmeWrapper) Close() error {
return nil return nil
} }
func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { 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) {
var acmeServer string var acmeServer string
switch options.Provider { switch options.Provider {
case "", "letsencrypt": case "", "letsencrypt":
@ -58,14 +89,15 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
} 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: zap.New(zapcore.NewCore( Logger: zapLogger,
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
os.Stderr,
zap.InfoLevel,
)),
} }
acmeConfig := certmagic.ACMEIssuer{ acmeConfig := certmagic.ACMEIssuer{
CA: acmeServer, CA: acmeServer,
@ -75,7 +107,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge, DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
AltHTTPPort: int(options.AlternativeHTTPPort), AltHTTPPort: int(options.AlternativeHTTPPort),
AltTLSALPNPort: int(options.AlternativeTLSPort), AltTLSALPNPort: int(options.AlternativeTLSPort),
Logger: config.Logger, Logger: zapLogger,
} }
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" { if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
var solver certmagic.DNS01Solver var solver certmagic.DNS01Solver
@ -103,6 +135,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
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,8 +9,9 @@ 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, options option.InboundACMEOptions) (*tls.Config, adapter.SimpleLifecycle, error) { func startACME(ctx context.Context, logger logger.Logger, 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, common.PtrValueOrDefault(options.ACME)) tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,11 +3,15 @@ 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"
@ -39,11 +43,13 @@ 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
transport *http.Transport transportAccess sync.Mutex
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) {
@ -161,12 +167,33 @@ 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,7 +20,8 @@ import (
) )
func dnsReadConfig(_ context.Context, _ string) *dnsConfig { func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
if C.res_init() != 0 { var state C.res_state
if C.res_ninit(state) != 0 {
return &dnsConfig{ return &dnsConfig{
servers: defaultNS, servers: defaultNS,
search: dnsDefaultSearch(), search: dnsDefaultSearch(),
@ -33,10 +34,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(C._res.retry), attempts: int(state.retry),
} }
for i := 0; i < int(C._res.nscount); i++ { for i := 0; i < int(state.nscount); i++ {
ns := C._res.nsaddr_list[i] ns := state.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
@ -44,7 +45,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 := C._res.dnsrch[i] search := state.dnsrch[i]
if search == nil { if search == nil {
break break
} }

View File

@ -2,6 +2,22 @@
icon: material/alert-decagram icon: material/alert-decagram
--- ---
#### 1.12.0-beta.31
* Improve tun performance on Apple platforms **1**
* Fixes and improvements
**1**:
We have significantly improved the performance of tun inbound on Apple platforms, especially in the gVisor stack.
### 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](./wiregaurd/) | | `wireguard` | [WireGuard](./wireguard/) |
| `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.0.7.1" "10.7.0.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.0.7.1` achieves the same behavior as SideStore/StosVPN. 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. 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.0.7.1" "10.7.0.1"
], ],
"strict_route": true, "strict_route": true,
"route_address": [ "route_address": [
@ -283,7 +283,7 @@ tun 接口的 IPv6 前缀。
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。 环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
将选项值设置为 `10.0.7.1` 可实现与 SideStore/StosVPN 相同的行为。 将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。 当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。

View File

@ -94,18 +94,13 @@ flowchart TB
"servers": [ "servers": [
{ {
"tag": "google", "tag": "google",
"address": "tls://8.8.8.8" "type": "tls",
"server": "8.8.8.8"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"detour": "direct" "server": "223.5.5.5"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
} }
], ],
"strategy": "ipv4_only" "strategy": "ipv4_only"
@ -115,7 +110,8 @@ 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,
"strict_route": false // "auto_redirect": true, // On linux
"strict_route": true
} }
], ],
"outbounds": [ "outbounds": [
@ -123,25 +119,23 @@ flowchart TB
{ {
"type": "direct", "type": "direct",
"tag": "direct" "tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
} }
], ],
"route": { "route": {
"rules": [ "rules": [
{ {
"protocol": "dns", "action": "sniff"
"outbound": "dns-out"
}, },
{ {
"geoip": [ "protocol": "dns",
"private" "action": "hijack-dns"
], },
{
"ip_is_private": true,
"outbound": "direct" "outbound": "direct"
} }
], ],
"default_domain_resolver": "local",
"auto_detect_interface": true "auto_detect_interface": true
} }
} }
@ -155,18 +149,13 @@ flowchart TB
"servers": [ "servers": [
{ {
"tag": "google", "tag": "google",
"address": "tls://8.8.8.8" "type": "tls",
"server": "8.8.8.8"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"detour": "direct" "server": "223.5.5.5"
}
],
"rules": [
{
"outbound": "any",
"server": "local"
} }
] ]
}, },
@ -176,7 +165,8 @@ 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,
"strict_route": false // "auto_redirect": true, // On linux
"strict_route": true
} }
], ],
"outbounds": [ "outbounds": [
@ -184,25 +174,23 @@ flowchart TB
{ {
"type": "direct", "type": "direct",
"tag": "direct" "tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
} }
], ],
"route": { "route": {
"rules": [ "rules": [
{ {
"protocol": "dns", "action": "sniff"
"outbound": "dns-out"
}, },
{ {
"geoip": [ "protocol": "dns",
"private" "action": "hijack-dns"
], },
{
"ip_is_private": true,
"outbound": "direct" "outbound": "direct"
} }
], ],
"default_domain_resolver": "local",
"auto_detect_interface": true "auto_detect_interface": true
} }
} }
@ -216,23 +204,22 @@ flowchart TB
"servers": [ "servers": [
{ {
"tag": "google", "tag": "google",
"address": "tls://8.8.8.8" "type": "tls",
"server": "8.8.8.8"
}, },
{ {
"tag": "local", "tag": "local",
"address": "223.5.5.5", "type": "udp",
"detour": "direct" "server": "223.5.5.5"
}, },
{ {
"tag": "remote", "tag": "remote",
"address": "fakeip" "type": "fakeip",
"inet4_range": "198.18.0.0/15",
"inet6_range": "fc00::/18"
} }
], ],
"rules": [ "rules": [
{
"outbound": "any",
"server": "local"
},
{ {
"query_type": [ "query_type": [
"A", "A",
@ -241,11 +228,6 @@ 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": [
@ -254,6 +236,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": true "strict_route": true
} }
], ],
@ -262,25 +245,23 @@ flowchart TB
{ {
"type": "direct", "type": "direct",
"tag": "direct" "tag": "direct"
},
{
"type": "dns",
"tag": "dns-out"
} }
], ],
"route": { "route": {
"rules": [ "rules": [
{ {
"protocol": "dns", "action": "sniff"
"outbound": "dns-out"
}, },
{ {
"geoip": [ "protocol": "dns",
"private" "action": "hijack-dns"
], },
{
"ip_is_private": true,
"outbound": "direct" "outbound": "direct"
} }
], ],
"default_domain_resolver": "local",
"auto_detect_interface": true "auto_detect_interface": true
} }
} }
@ -290,54 +271,6 @@ 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
@ -346,35 +279,20 @@ flowchart TB
"servers": [ "servers": [
{ {
"tag": "google", "tag": "google",
"address": "tls://8.8.8.8" "type": "tls",
"server": "8.8.8.8"
}, },
{ {
"tag": "local", "tag": "local",
"address": "https://223.5.5.5/dns-query", "type": "https",
"detour": "direct" "server": "223.5.5.5"
} }
], ],
"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",
@ -392,6 +310,7 @@ flowchart TB
] ]
}, },
"route": { "route": {
"default_domain_resolver": "local",
"rule_set": [ "rule_set": [
{ {
"type": "remote", "type": "remote",
@ -425,35 +344,24 @@ flowchart TB
} }
``` ```
=== ":material-security: Without DNS leaks, but slower (1.9.0-alpha.2+)" === ":material-security: Without DNS leaks, but slower"
```json ```json
{ {
"dns": { "dns": {
"servers": [ "servers": [
{ {
"tag": "google", "tag": "google",
"address": "tls://8.8.8.8" "type": "tls",
"server": "8.8.8.8"
}, },
{ {
"tag": "local", "tag": "local",
"address": "https://223.5.5.5/dns-query", "type": "https",
"detour": "direct" "server": "223.5.5.5"
} }
], ],
"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"
@ -476,6 +384,7 @@ flowchart TB
] ]
}, },
"route": { "route": {
"default_domain_resolver": "local",
"rule_set": [ "rule_set": [
{ {
"type": "remote", "type": "remote",
@ -517,14 +426,13 @@ 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",
@ -536,20 +444,12 @@ flowchart TB
"port": 53 "port": 53
} }
], ],
"outbound": "dns" "action": "hijack-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",
@ -565,12 +465,23 @@ flowchart TB
"protocol": "stun" "protocol": "stun"
} }
], ],
"outbound": "block" "action": "reject"
}, },
{ {
"rule_set": [ "rule_set": "geosite-geolocation-cn",
"geoip-cn", "outbound": "direct"
"geosite-geolocation-cn" },
{
"type": "logical",
"mode": "and",
"rules": [
{
"rule_set": "geoip-cn"
},
{
"rule_set": "geosite-geolocation-!cn",
"invert": true
}
], ],
"outbound": "direct" "outbound": "direct"
} }
@ -591,4 +502,4 @@ flowchart TB
] ]
} }
} }
``` ```

6
go.mod
View File

@ -25,16 +25,16 @@ require (
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
github.com/sagernet/cors v1.2.1 github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1 github.com/sagernet/fswatch v0.1.1
github.com/sagernet/gomobile v0.1.6 github.com/sagernet/gomobile v0.1.7
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b github.com/sagernet/sing v0.6.12-0.20250703120903-7081a0c40539
github.com/sagernet/sing-mux v0.3.2 github.com/sagernet/sing-mux v0.3.2
github.com/sagernet/sing-quic v0.5.0-beta.2 github.com/sagernet/sing-quic v0.5.0-beta.2
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.6-0.20250610083027-da0a50057fb5 github.com/sagernet/sing-tun v0.6.10-0.20250703121732-a0881ada3251
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

12
go.sum
View File

@ -157,8 +157,8 @@ github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o=
github.com/sagernet/gomobile v0.1.6 h1:JkR1ToKOrdoiwULte4pYS5HYdPBzl2N+JNuuwVuLs0k= github.com/sagernet/gomobile v0.1.7 h1:I9jCJZTH0weP5MsuydvYHX5QfN/r6Fe8ptAIj1+SJVg=
github.com/sagernet/gomobile v0.1.6/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= github.com/sagernet/gomobile v0.1.7/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38= github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb h1:pprQtDqNgqXkRsXn+0E8ikKOemzmum8bODjSfDene38=
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4= github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
@ -168,8 +168,8 @@ github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/l
github.com/sagernet/quic-go v0.52.0-beta.1 h1:hWkojLg64zjV+MJOvJU/kOeWndm3tiEfBLx5foisszs= 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.52.0-beta.1/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo= github.com/sagernet/sing v0.6.12-0.20250703120903-7081a0c40539 h1:SK4M4FCNdwV4EiYKIUZ9qM4lr/1NQogJe1YoyYw5DV8=
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.12-0.20250703120903-7081a0c40539/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE=
github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk= github.com/sagernet/sing-quic v0.5.0-beta.2 h1:j7KAbBuGmsKwSxVAQL5soJ+wDqxim4/llK2kxB0hSKk=
@ -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.6-0.20250610083027-da0a50057fb5 h1:zlcioVa11g8VLz5L0yPG7PbvQrw7mrxkDDdlMPEgqDk= github.com/sagernet/sing-tun v0.6.10-0.20250703121732-a0881ada3251 h1:eH9naJXvyF/DZDk0V1SYkL6ypYD+A1tUFWLcT7PRezg=
github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= github.com/sagernet/sing-tun v0.6.10-0.20250703121732-a0881ada3251/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

@ -130,9 +130,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
deprecated.Report(ctx, deprecated.OptionTUNGSO) deprecated.Report(ctx, deprecated.OptionTUNGSO)
} }
platformInterface := service.FromContext[platform.Interface](ctx)
tunMTU := options.MTU tunMTU := options.MTU
if tunMTU == 0 { if tunMTU == 0 {
tunMTU = 9000 if platformInterface != nil && platformInterface.UnderNetworkExtension() {
tunMTU = 4000
} else {
tunMTU = 9000
}
} }
var udpTimeout time.Duration var udpTimeout time.Duration
if options.UDPTimeout != 0 { if options.UDPTimeout != 0 {
@ -208,7 +213,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
}, },
udpTimeout: udpTimeout, udpTimeout: udpTimeout,
stack: options.Stack, stack: options.Stack,
platformInterface: service.FromContext[platform.Interface](ctx), platformInterface: platformInterface,
platformOptions: common.PtrValueOrDefault(options.Platform), platformOptions: common.PtrValueOrDefault(options.Platform),
} }
for _, routeAddressSet := range options.RouteAddressSet { for _, routeAddressSet := range options.RouteAddressSet {

View File

@ -205,6 +205,10 @@ 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,6 +219,10 @@ 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,7 +2,6 @@ package route
import ( import (
"context" "context"
"errors"
"net" "net"
"time" "time"
@ -10,7 +9,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"
"github.com/sagernet/sing-tun" R "github.com/sagernet/sing-box/route/rule"
"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"
@ -65,7 +64,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 && !errors.Is(err, tun.ErrDrop) && !E.IsClosedOrCanceled(err) { if err != nil && !R.IsRejected(err) && !E.IsClosedOrCanceled(err) {
logger.ErrorContext(ctx, E.Cause(err, "process DNS packet")) logger.ErrorContext(ctx, E.Cause(err, "process DNS packet"))
} }
} }

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"
"github.com/sagernet/sing-box/route/rule" R "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) { if E.IsClosedOrCanceled(err) || R.IsRejected(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 *rule.RuleActionRoute: case *R.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 *rule.RuleActionReject: case *R.RuleActionReject:
buf.ReleaseMulti(buffers) buf.ReleaseMulti(buffers)
return action.Error(ctx) return action.Error(ctx)
case *rule.RuleActionHijackDNS: case *R.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) { if E.IsClosedOrCanceled(err) || R.IsRejected(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) { if E.IsClosedOrCanceled(err) || R.IsRejected(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 *rule.RuleActionRoute: case *R.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 *rule.RuleActionReject: case *R.RuleActionReject:
N.ReleaseMultiPacketBuffer(packetBuffers) N.ReleaseMultiPacketBuffer(packetBuffers)
return action.Error(ctx) return action.Error(ctx)
case *rule.RuleActionHijackDNS: case *R.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().(*rule.RuleActionReject) rejectAction, isReject := selectedRule.Action().(*R.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, &rule.RuleActionSniff{ newBuffer, newPackerBuffers, newErr := r.actionSniff(ctx, metadata, &R.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, &rule.RuleActionResolve{ fatalErr = r.actionResolve(ctx, metadata, &R.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 *rule.RuleActionRouteOptions var routeOptions *R.RuleActionRouteOptions
switch action := currentRule.Action().(type) { switch action := currentRule.Action().(type) {
case *rule.RuleActionRoute: case *R.RuleActionRoute:
routeOptions = &action.RuleActionRouteOptions routeOptions = &action.RuleActionRouteOptions
case *rule.RuleActionRouteOptions: case *R.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 *rule.RuleActionSniff: case *R.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 *rule.RuleActionResolve: case *R.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 *rule.RuleActionSniff, ctx context.Context, metadata *adapter.InboundContext, action *R.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,6 +501,9 @@ 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 {
@ -525,6 +528,7 @@ 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) {
@ -549,8 +553,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.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) { } else if metadata.SniffError != nil && !errors.Is(metadata.SniffError, sniff.ErrNeedMoreData) {
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError) r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.SniffError)
return return
} }
var packetSniffers []sniff.PacketSniffer var packetSniffers []sniff.PacketSniffer
@ -598,7 +602,7 @@ func (r *Router) actionSniff(
return return
} }
} else { } else {
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil { if len(packetBuffers) > 0 || metadata.SniffError != nil {
err = sniff.PeekPacket( err = sniff.PeekPacket(
ctx, ctx,
metadata, metadata,
@ -618,7 +622,7 @@ func (r *Router) actionSniff(
Destination: destination, Destination: destination,
} }
packetBuffers = append(packetBuffers, packetBuffer) packetBuffers = append(packetBuffers, packetBuffer)
metadata.PacketSniffError = err metadata.SniffError = 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")
@ -649,7 +653,7 @@ func (r *Router) actionSniff(
return return
} }
func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *rule.RuleActionResolve) error { func (r *Router) actionResolve(ctx context.Context, metadata *adapter.InboundContext, action *R.RuleActionResolve) error {
if metadata.Destination.IsFqdn() { if metadata.Destination.IsFqdn() {
var transport adapter.DNSTransport var transport adapter.DNSTransport
if action.Server != "" { if action.Server != "" {

View File

@ -2,6 +2,7 @@ package rule
import ( import (
"context" "context"
"errors"
"net/netip" "net/netip"
"strings" "strings"
"sync" "sync"
@ -284,6 +285,23 @@ 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
@ -307,9 +325,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 = syscall.ECONNREFUSED returnErr = &RejectedError{syscall.ECONNREFUSED}
case C.RuleActionRejectMethodDrop: case C.RuleActionRejectMethodDrop:
return tun.ErrDrop return &RejectedError{tun.ErrDrop}
default: default:
panic(F.ToString("unknown reject method: ", r.Method)) panic(F.ToString("unknown reject method: ", r.Method))
} }
@ -327,7 +345,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 tun.ErrDrop return &RejectedError{tun.ErrDrop}
} }
return returnErr return returnErr
} }

View File

@ -31,6 +31,9 @@ type HTTPConn struct {
} }
func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn { func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn {
if request.Header.Get("Host") == "" {
request.Header.Set("Host", request.Host)
}
return &HTTPConn{ return &HTTPConn{
Conn: conn, Conn: conn,
request: request, request: request,
@ -89,9 +92,6 @@ func (c *HTTPConn) writeRequest(payload []byte) error {
if err != nil { if err != nil {
return err return err
} }
if c.request.Header.Get("Host") == "" {
c.request.Header.Set("Host", c.request.Host)
}
for key, value := range c.request.Header { for key, value := range c.request.Header {
_, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF))) _, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF)))
if err != nil { if err != nil {