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
35 changed files with 296 additions and 500 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:
@ -245,8 +225,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.7 go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.7 go install -v github.com/sagernet/gomobile/cmd/gobind@v0.1.6
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
SniffError error PacketSniffError error
// cache // cache

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

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 || response.StatusCode == http.StatusNotFound) { if response != nil && response.StatusCode == http.StatusUnprocessableEntity {
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,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() {
@ -46,9 +44,8 @@ var (
sharedFlags []string sharedFlags []string
debugFlags []string debugFlags []string
sharedTags []string sharedTags []string
darwinTags []string iosTags []string
memcTags []string memcTags []string
notMemcTags []string
debugTags []string debugTags []string
) )
@ -63,9 +60,8 @@ 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")
darwinTags = append(darwinTags, "with_dhcp") iosTags = append(iosTags, "with_dhcp", "with_low_memory")
memcTags = append(memcTags, "with_tailscale") memcTags = append(memcTags, "with_tailscale")
notMemcTags = append(notMemcTags, "with_low_memory")
debugTags = append(debugTags, "debug") debugTags = append(debugTags, "debug")
} }
@ -155,10 +151,7 @@ func buildApple() {
"-v", "-v",
"-target", bindTarget, "-target", bindTarget,
"-libname=box", "-libname=box",
"-tags-not-macos=with_low_memory", "-tags-macos=" + strings.Join(memcTags, ","),
}
if !withTailscale {
args = append(args, "-tags-macos="+strings.Join(memcTags, ","))
} }
if !debugEnabled { if !debugEnabled {
@ -167,10 +160,7 @@ func buildApple() {
args = append(args, debugFlags...) args = append(args, debugFlags...)
} }
tags := append(sharedTags, darwinTags...) 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

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

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

@ -2,22 +2,6 @@
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](./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
] ]
} }
} }
``` ```

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.7 github.com/sagernet/gomobile v0.1.6
github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb github.com/sagernet/gvisor v0.0.0-20250325023245-7a9c0f5725fb
github.com/sagernet/quic-go v0.52.0-beta.1 github.com/sagernet/quic-go v0.52.0-beta.1
github.com/sagernet/sing v0.6.12-0.20250703120903-7081a0c40539 github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b
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.10-0.20250703121732-a0881ada3251 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

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.7 h1:I9jCJZTH0weP5MsuydvYHX5QfN/r6Fe8ptAIj1+SJVg= github.com/sagernet/gomobile v0.1.6 h1:JkR1ToKOrdoiwULte4pYS5HYdPBzl2N+JNuuwVuLs0k=
github.com/sagernet/gomobile v0.1.7/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E= github.com/sagernet/gomobile v0.1.6/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.12-0.20250703120903-7081a0c40539 h1:SK4M4FCNdwV4EiYKIUZ9qM4lr/1NQogJe1YoyYw5DV8= github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo=
github.com/sagernet/sing v0.6.12-0.20250703120903-7081a0c40539/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/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.10-0.20250703121732-a0881ada3251 h1:eH9naJXvyF/DZDk0V1SYkL6ypYD+A1tUFWLcT7PRezg= github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5 h1:zlcioVa11g8VLz5L0yPG7PbvQrw7mrxkDDdlMPEgqDk=
github.com/sagernet/sing-tun v0.6.10-0.20250703121732-a0881ada3251/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

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

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

@ -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 != "" {

View File

@ -2,7 +2,6 @@ package rule
import ( import (
"context" "context"
"errors"
"net/netip" "net/netip"
"strings" "strings"
"sync" "sync"
@ -285,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
@ -325,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))
} }
@ -345,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
} }

View File

@ -31,9 +31,6 @@ 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,
@ -92,6 +89,9 @@ 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 {