mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-07-24 06:54:08 +08:00
Compare commits
127 Commits
c985206169
...
1c62f8742e
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1c62f8742e | ||
![]() |
989034b8f7 | ||
![]() |
fef0a17bf8 | ||
![]() |
c54f87e722 | ||
![]() |
9bd9e5ab83 | ||
![]() |
7eb72226ab | ||
![]() |
1b08cdeb0a | ||
![]() |
88f989b934 | ||
![]() |
65af6c7cdf | ||
![]() |
7935126887 | ||
![]() |
1a78d3d28d | ||
![]() |
0314c83afd | ||
![]() |
beb8741925 | ||
![]() |
74478e5f84 | ||
![]() |
82b9af7418 | ||
![]() |
3a5e6ee8fd | ||
![]() |
cb20c6ec7f | ||
![]() |
a5aef88073 | ||
![]() |
ab0af2960d | ||
![]() |
32a081d229 | ||
![]() |
6b7b1b2eac | ||
![]() |
81364e3294 | ||
![]() |
ee9474dd07 | ||
![]() |
ec6b78d1b6 | ||
![]() |
078fb5555d | ||
![]() |
99ea71119a | ||
![]() |
d0ed53dae3 | ||
![]() |
79deb889b1 | ||
![]() |
480d92901e | ||
![]() |
3510a228e2 | ||
![]() |
135e8e673f | ||
![]() |
97fb38d8bf | ||
![]() |
f376bf834f | ||
![]() |
b9f53ec6cc | ||
![]() |
9208dd58c2 | ||
![]() |
c548a90f4a | ||
![]() |
e57e5f4415 | ||
![]() |
63520226a0 | ||
![]() |
02aeaca87a | ||
![]() |
c00373d930 | ||
![]() |
db8ece9667 | ||
![]() |
763ca0c3f8 | ||
![]() |
d641c152a7 | ||
![]() |
5ccb67f1a5 | ||
![]() |
b763a2f178 | ||
![]() |
81a14bb5f2 | ||
![]() |
5dbc9d2fc5 | ||
![]() |
a35c4a6a96 | ||
![]() |
ea2d355907 | ||
![]() |
ee58016702 | ||
![]() |
b1dc45898b | ||
![]() |
f51c2a4e66 | ||
![]() |
3cd42b6d37 | ||
![]() |
9494f2ae21 | ||
![]() |
23c24cace8 | ||
![]() |
e225a6476e | ||
![]() |
53ea9cb4c7 | ||
![]() |
7b5eef8ae2 | ||
![]() |
ffa70c0288 | ||
![]() |
1cb605924a | ||
![]() |
90320bc80d | ||
![]() |
b4402e27d9 | ||
![]() |
14bcf34f86 | ||
![]() |
e41cb6d559 | ||
![]() |
d90dec5381 | ||
![]() |
e09aa7ca84 | ||
![]() |
832d37b808 | ||
![]() |
3a70ae7afe | ||
![]() |
cf1058de4c | ||
![]() |
73e6bbb49a | ||
![]() |
329d4bb4c9 | ||
![]() |
b43615ef35 | ||
![]() |
e7479dea90 | ||
![]() |
f1f1406eb1 | ||
![]() |
fe9cc7461b | ||
![]() |
710ca243aa | ||
![]() |
a249770e24 | ||
![]() |
6d2bd2116b | ||
![]() |
2e1698fa78 | ||
![]() |
6946ec37cf | ||
![]() |
25f844ec6d | ||
![]() |
5a5c906fb2 | ||
![]() |
31c191debb | ||
![]() |
1b02fca33f | ||
![]() |
5308c46e07 | ||
![]() |
0cfcaf2c62 | ||
![]() |
a2bd384c32 | ||
![]() |
3723dd2583 | ||
![]() |
9bbc3dabc8 | ||
![]() |
6827d67a09 | ||
![]() |
5ae3e97388 | ||
![]() |
f6d1099b69 | ||
![]() |
ca1b5bbcdf | ||
![]() |
1c6c48ea8d | ||
![]() |
0d86d225a3 | ||
![]() |
b75e6b88b7 | ||
![]() |
000f7b1045 | ||
![]() |
b39204f4aa | ||
![]() |
2b2655ffc7 | ||
![]() |
3f9f41caed | ||
![]() |
0231e3c575 | ||
![]() |
918b70b1cc | ||
![]() |
af4c4d1841 | ||
![]() |
88e830b5d8 | ||
![]() |
7eb14cfe9f | ||
![]() |
e6761fa538 | ||
![]() |
485d28ceec | ||
![]() |
159a2858e2 | ||
![]() |
b6691707e9 | ||
![]() |
38f1736f78 | ||
![]() |
ff77bf63f5 | ||
![]() |
9dad482e70 | ||
![]() |
d43791307c | ||
![]() |
0a82b8a9ad | ||
![]() |
7bf91fb2af | ||
![]() |
81d2eb5f3f | ||
![]() |
ee731a32c8 | ||
![]() |
6f804adf39 | ||
![]() |
36db31c55a | ||
![]() |
4dbbf59c82 | ||
![]() |
832eb4458d | ||
![]() |
2cf989d306 | ||
![]() |
7d3ee29bd0 | ||
![]() |
cba0e46aba | ||
![]() |
9b8ab3e61e | ||
![]() |
47f18e823a | ||
![]() |
2d1b824b62 |
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@ -109,7 +109,7 @@ jobs:
|
||||
if: ${{ ! matrix.legacy_go }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Cache Legacy Go
|
||||
if: matrix.require_legacy_go
|
||||
id: cache-legacy-go
|
||||
@ -294,7 +294,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -374,7 +374,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
id: setup-ndk
|
||||
uses: nttld/setup-ndk@v1
|
||||
@ -472,15 +472,15 @@ jobs:
|
||||
if: matrix.if
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Setup Xcode stable
|
||||
if: matrix.if && github.ref == 'refs/heads/main-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
- name: Setup Xcode beta
|
||||
if: matrix.if && github.ref == 'refs/heads/dev-next'
|
||||
run: |-
|
||||
sudo xcode-select -s /Applications/Xcode_16.2.app
|
||||
sudo xcode-select -s /Applications/Xcode_16.4.app
|
||||
- name: Set tag
|
||||
if: matrix.if
|
||||
run: |-
|
||||
@ -615,7 +615,7 @@ jobs:
|
||||
path: 'dist'
|
||||
upload:
|
||||
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
|
||||
needs:
|
||||
- calculate_version
|
||||
|
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
|
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Check input version
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |-
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ^1.24.4
|
||||
go-version: ^1.24
|
||||
- name: Setup Android NDK
|
||||
if: matrix.os == 'android'
|
||||
uses: nttld/setup-ndk@v1
|
||||
|
24
Makefile
24
Makefile
@ -108,6 +108,16 @@ upload_ios_app_store:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
export_ios_ipa:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath build/SFI.xcarchive -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFI && \
|
||||
cp build/SFI/sing-box.ipa dist/SFI.ipa
|
||||
|
||||
upload_ios_ipa:
|
||||
cd dist && \
|
||||
cp SFI.ipa "SFI-${VERSION}.ipa" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFI-${VERSION}.ipa"
|
||||
|
||||
release_ios: build_ios upload_ios_app_store
|
||||
|
||||
build_macos:
|
||||
@ -175,6 +185,16 @@ upload_tvos_app_store:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Upload.plist -allowProvisioningUpdates
|
||||
|
||||
export_tvos_ipa:
|
||||
cd ../sing-box-for-apple && \
|
||||
xcodebuild -exportArchive -archivePath "build/SFT.xcarchive" -exportOptionsPlist SFI/Export.plist -allowProvisioningUpdates -exportPath build/SFT && \
|
||||
cp build/SFT/sing-box.ipa dist/SFT.ipa
|
||||
|
||||
upload_tvos_ipa:
|
||||
cd dist && \
|
||||
cp SFT.ipa "SFT-${VERSION}.ipa" && \
|
||||
ghr --replace --draft --prerelease "v${VERSION}" "SFT-${VERSION}.ipa"
|
||||
|
||||
release_tvos: build_tvos upload_tvos_app_store
|
||||
|
||||
update_apple_version:
|
||||
@ -225,8 +245,8 @@ lib:
|
||||
go run ./cmd/internal/build_libbox -target ios
|
||||
|
||||
lib_install:
|
||||
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.6
|
||||
go install -v github.com/sagernet/gomobile/cmd/gobind@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.7
|
||||
|
||||
docs:
|
||||
venv/bin/mkdocs serve
|
||||
|
@ -53,11 +53,11 @@ type InboundContext struct {
|
||||
|
||||
// sniffer
|
||||
|
||||
Protocol string
|
||||
Domain string
|
||||
Client string
|
||||
SniffContext any
|
||||
PacketSniffError error
|
||||
Protocol string
|
||||
Domain string
|
||||
Client string
|
||||
SniffContext any
|
||||
SniffError error
|
||||
|
||||
// cache
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 320170a1077ea5c93872b3e055b96b8836615ef0
|
||||
Subproject commit eb2e13a6f9a8c03a35ae672395ccab0a6bdcd954
|
@ -177,7 +177,7 @@ func publishTestflight(ctx context.Context) error {
|
||||
}
|
||||
log.Info(string(platform), " ", tag, " publish")
|
||||
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")
|
||||
time.Sleep(15 * time.Second)
|
||||
continue
|
||||
|
@ -16,15 +16,17 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
debugEnabled bool
|
||||
target string
|
||||
platform string
|
||||
debugEnabled bool
|
||||
target string
|
||||
platform string
|
||||
withTailscale bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debugEnabled, "debug", false, "enable debug")
|
||||
flag.StringVar(&target, "target", "android", "target platform")
|
||||
flag.StringVar(&platform, "platform", "", "specify platform")
|
||||
flag.BoolVar(&withTailscale, "with-tailscale", false, "build tailscale for iOS and tvOS")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@ -44,8 +46,9 @@ var (
|
||||
sharedFlags []string
|
||||
debugFlags []string
|
||||
sharedTags []string
|
||||
iosTags []string
|
||||
darwinTags []string
|
||||
memcTags []string
|
||||
notMemcTags []string
|
||||
debugTags []string
|
||||
)
|
||||
|
||||
@ -60,8 +63,9 @@ func init() {
|
||||
debugFlags = append(debugFlags, "-ldflags", "-X github.com/sagernet/sing-box/constant.Version="+currentTag)
|
||||
|
||||
sharedTags = append(sharedTags, "with_gvisor", "with_quic", "with_wireguard", "with_utls", "with_clash_api", "with_conntrack")
|
||||
iosTags = append(iosTags, "with_dhcp", "with_low_memory")
|
||||
darwinTags = append(darwinTags, "with_dhcp")
|
||||
memcTags = append(memcTags, "with_tailscale")
|
||||
notMemcTags = append(notMemcTags, "with_low_memory")
|
||||
debugTags = append(debugTags, "debug")
|
||||
}
|
||||
|
||||
@ -151,7 +155,10 @@ func buildApple() {
|
||||
"-v",
|
||||
"-target", bindTarget,
|
||||
"-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 {
|
||||
@ -160,7 +167,10 @@ func buildApple() {
|
||||
args = append(args, debugFlags...)
|
||||
}
|
||||
|
||||
tags := append(sharedTags, iosTags...)
|
||||
tags := append(sharedTags, darwinTags...)
|
||||
if withTailscale {
|
||||
tags = append(tags, memcTags...)
|
||||
}
|
||||
if debugEnabled {
|
||||
tags = append(tags, debugTags...)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"os"
|
||||
"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"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
@ -54,7 +54,7 @@ func convertRuleSet(sourcePath string) error {
|
||||
var rules []option.HeadlessRule
|
||||
switch flagRuleSetConvertType {
|
||||
case "adguard":
|
||||
rules, err = adguard.Convert(reader)
|
||||
rules, err = adguard.ToOptions(reader, log.StdLogger())
|
||||
case "":
|
||||
return E.New("source type is required")
|
||||
default:
|
||||
|
@ -6,7 +6,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/common/srs"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -50,6 +53,11 @@ func decompileRuleSet(sourcePath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hasRule(ruleSet.Options.Rules, func(rule option.DefaultHeadlessRule) bool {
|
||||
return len(rule.AdGuardDomain) > 0
|
||||
}) {
|
||||
return E.New("unable to decompile binary AdGuard rules to rule-set.")
|
||||
}
|
||||
var outputPath string
|
||||
if flagRuleSetDecompileOutput == flagRuleSetDecompileDefaultOutput {
|
||||
if strings.HasSuffix(sourcePath, ".srs") {
|
||||
@ -75,3 +83,19 @@ func decompileRuleSet(sourcePath string) error {
|
||||
outputFile.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasRule(rules []option.HeadlessRule, cond func(rule option.DefaultHeadlessRule) bool) bool {
|
||||
for _, rule := range rules {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeDefault:
|
||||
if cond(rule.DefaultOptions) {
|
||||
return true
|
||||
}
|
||||
case C.RuleTypeLogical:
|
||||
if hasRule(rule.LogicalOptions.Rules, cond) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package adguard
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
@ -9,10 +10,10 @@ import (
|
||||
"strings"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/log"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
@ -27,7 +28,7 @@ type agdguardRuleLine struct {
|
||||
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)
|
||||
var (
|
||||
ruleLines []agdguardRuleLine
|
||||
@ -36,7 +37,10 @@ func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||
parseLine:
|
||||
for scanner.Scan() {
|
||||
ruleLine := scanner.Text()
|
||||
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||
if ruleLine == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
||||
continue
|
||||
}
|
||||
originRuleLine := ruleLine
|
||||
@ -92,7 +96,7 @@ parseLine:
|
||||
}
|
||||
if !ignored {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", originRuleLine)
|
||||
continue parseLine
|
||||
}
|
||||
}
|
||||
@ -120,27 +124,35 @@ parseLine:
|
||||
ruleLine = ruleLine[1 : len(ruleLine)-1]
|
||||
if ignoreIPCIDRRegexp(ruleLine) {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with IPCIDR regexp: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with IPCIDR regexp: ", originRuleLine)
|
||||
continue
|
||||
}
|
||||
isRegexp = true
|
||||
} else {
|
||||
if strings.Contains(ruleLine, "://") {
|
||||
ruleLine = common.SubstringAfter(ruleLine, "://")
|
||||
isSuffix = true
|
||||
}
|
||||
if strings.Contains(ruleLine, "/") {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with path: ", originRuleLine)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(ruleLine, "##") {
|
||||
if strings.Contains(ruleLine, "?") || strings.Contains(ruleLine, "&") {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with element hiding: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with query: ", originRuleLine)
|
||||
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++
|
||||
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
|
||||
}
|
||||
var domainCheck string
|
||||
@ -151,7 +163,7 @@ parseLine:
|
||||
}
|
||||
if ruleLine == "" {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||
logger.Debug("ignored unsupported rule with empty domain", originRuleLine)
|
||||
continue
|
||||
} else {
|
||||
domainCheck = strings.ReplaceAll(domainCheck, "*", "x")
|
||||
@ -159,13 +171,13 @@ parseLine:
|
||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||
if ipErr == nil {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with IPCIDR: ", originRuleLine)
|
||||
continue
|
||||
}
|
||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with port: ", originRuleLine)
|
||||
} else {
|
||||
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with invalid domain: ", originRuleLine)
|
||||
}
|
||||
ignoredLines++
|
||||
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
|
||||
}
|
||||
|
||||
var ErrInvalid = E.New("invalid binary AdGuard rule-set")
|
||||
|
||||
func FromOptions(rules []option.HeadlessRule) ([]byte, error) {
|
||||
if len(rules) != 1 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
rule := rules[0]
|
||||
var (
|
||||
importantDomain []string
|
||||
importantDomainRegex []string
|
||||
importantExcludeDomain []string
|
||||
importantExcludeDomainRegex []string
|
||||
domain []string
|
||||
domainRegex []string
|
||||
excludeDomain []string
|
||||
excludeDomainRegex []string
|
||||
)
|
||||
parse:
|
||||
for {
|
||||
switch rule.Type {
|
||||
case C.RuleTypeLogical:
|
||||
if !(len(rule.LogicalOptions.Rules) == 2 && rule.LogicalOptions.Rules[0].Type == C.RuleTypeDefault) {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
if rule.LogicalOptions.Mode == C.LogicalTypeAnd && rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
||||
if len(importantExcludeDomain) == 0 && len(importantExcludeDomainRegex) == 0 {
|
||||
importantExcludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
||||
importantExcludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
||||
if len(importantExcludeDomain)+len(importantExcludeDomainRegex) == 0 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
} else {
|
||||
excludeDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
||||
excludeDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
||||
if len(excludeDomain)+len(excludeDomainRegex) == 0 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
}
|
||||
} else if rule.LogicalOptions.Mode == C.LogicalTypeOr && !rule.LogicalOptions.Rules[0].DefaultOptions.Invert {
|
||||
importantDomain = rule.LogicalOptions.Rules[0].DefaultOptions.AdGuardDomain
|
||||
importantDomainRegex = rule.LogicalOptions.Rules[0].DefaultOptions.DomainRegex
|
||||
if len(importantDomain)+len(importantDomainRegex) == 0 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
} else {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
rule = rule.LogicalOptions.Rules[1]
|
||||
case C.RuleTypeDefault:
|
||||
domain = rule.DefaultOptions.AdGuardDomain
|
||||
domainRegex = rule.DefaultOptions.DomainRegex
|
||||
if len(domain)+len(domainRegex) == 0 {
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
break parse
|
||||
}
|
||||
}
|
||||
var output bytes.Buffer
|
||||
for _, ruleLine := range importantDomain {
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("$important\n")
|
||||
}
|
||||
for _, ruleLine := range importantDomainRegex {
|
||||
output.WriteString("/")
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("/$important\n")
|
||||
|
||||
}
|
||||
for _, ruleLine := range importantExcludeDomain {
|
||||
output.WriteString("@@")
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("$important\n")
|
||||
}
|
||||
for _, ruleLine := range importantExcludeDomainRegex {
|
||||
output.WriteString("@@/")
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("/$important\n")
|
||||
}
|
||||
for _, ruleLine := range domain {
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("\n")
|
||||
}
|
||||
for _, ruleLine := range domainRegex {
|
||||
output.WriteString("/")
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("/\n")
|
||||
}
|
||||
for _, ruleLine := range excludeDomain {
|
||||
output.WriteString("@@")
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("\n")
|
||||
}
|
||||
for _, ruleLine := range excludeDomainRegex {
|
||||
output.WriteString("@@/")
|
||||
output.WriteString(ruleLine)
|
||||
output.WriteString("/\n")
|
||||
}
|
||||
return output.Bytes(), nil
|
||||
}
|
||||
|
||||
func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||
if strings.HasPrefix(ruleLine, "(http?:\\/\\/)") {
|
||||
ruleLine = ruleLine[12:]
|
||||
@ -294,11 +408,9 @@ func ignoreIPCIDRRegexp(ruleLine string) bool {
|
||||
ruleLine = ruleLine[13:]
|
||||
} else if strings.HasPrefix(ruleLine, "^") {
|
||||
ruleLine = ruleLine[1:]
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
_, parseErr := strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)
|
||||
return parseErr == nil
|
||||
return common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "\\."), 10, 8)) == nil ||
|
||||
common.Error(strconv.ParseUint(common.SubstringBefore(ruleLine, "."), 10, 8)) == nil
|
||||
}
|
||||
|
||||
func parseAdGuardHostLine(ruleLine string) (string, error) {
|
@ -7,13 +7,15 @@ import (
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/route/rule"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestConverter(t *testing.T) {
|
||||
t.Parallel()
|
||||
rules, err := Convert(strings.NewReader(`
|
||||
ruleString := `||sagernet.org^$important
|
||||
@@|sing-box.sagernet.org^$important
|
||||
||example.org^
|
||||
|example.com^
|
||||
example.net^
|
||||
@ -21,10 +23,9 @@ example.net^
|
||||
||example.edu.tw^
|
||||
|example.gov
|
||||
example.arpa
|
||||
@@|sagernet.example.org|
|
||||
||sagernet.org^$important
|
||||
@@|sing-box.sagernet.org^$important
|
||||
`))
|
||||
@@|sagernet.example.org^
|
||||
`
|
||||
rules, err := ToOptions(strings.NewReader(ruleString), logger.NOP())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
@ -75,15 +76,18 @@ example.arpa
|
||||
Domain: domain,
|
||||
}), domain)
|
||||
}
|
||||
ruleFromOptions, err := FromOptions(rules)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ruleString, string(ruleFromOptions))
|
||||
}
|
||||
|
||||
func TestHosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
rules, err := Convert(strings.NewReader(`
|
||||
rules, err := ToOptions(strings.NewReader(`
|
||||
127.0.0.1 localhost
|
||||
::1 localhost #[IPv6]
|
||||
0.0.0.0 google.com
|
||||
`))
|
||||
`), logger.NOP())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
||||
@ -110,10 +114,10 @@ func TestHosts(t *testing.T) {
|
||||
|
||||
func TestSimpleHosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
rules, err := Convert(strings.NewReader(`
|
||||
rules, err := ToOptions(strings.NewReader(`
|
||||
example.com
|
||||
www.example.org
|
||||
`))
|
||||
`), logger.NOP())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, rules, 1)
|
||||
rule, err := rule.NewHeadlessRule(context.Background(), rules[0])
|
@ -76,6 +76,8 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
// rup8(sizeof(xtcpcb_n))
|
||||
itemSize += 208
|
||||
}
|
||||
|
||||
var fallbackUDPProcess string
|
||||
// skip the first xinpgen(24 bytes) block
|
||||
for i := 24; i+itemSize <= len(buf); i += itemSize {
|
||||
// offset of xinpcb_n and xsocket_n
|
||||
@ -90,10 +92,12 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
flag := buf[inp+44]
|
||||
|
||||
var srcIP netip.Addr
|
||||
srcIsIPv4 := false
|
||||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// ipv4
|
||||
srcIP = netip.AddrFrom4(*(*[4]byte)(buf[inp+76 : inp+80]))
|
||||
srcIsIPv4 = true
|
||||
case flag&0x2 > 0 && !isIPv4:
|
||||
// ipv6
|
||||
srcIP = netip.AddrFrom16(*(*[16]byte)(buf[inp+64 : inp+80]))
|
||||
@ -101,13 +105,21 @@ func findProcessName(network string, ip netip.Addr, port int) (string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if ip != srcIP {
|
||||
continue
|
||||
if ip == srcIP {
|
||||
// xsocket_n.so_last_pid
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
return getExecPathFromPID(pid)
|
||||
}
|
||||
|
||||
// xsocket_n.so_last_pid
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
return getExecPathFromPID(pid)
|
||||
// udp packet connection may be not equal with srcIP
|
||||
if network == N.NetworkUDP && srcIP.IsUnspecified() && isIPv4 == srcIsIPv4 {
|
||||
pid := readNativeUint32(buf[so+68 : so+72])
|
||||
fallbackUDPProcess, _ = getExecPathFromPID(pid)
|
||||
}
|
||||
}
|
||||
|
||||
if network == N.NetworkUDP && len(fallbackUDPProcess) > 0 {
|
||||
return fallbackUDPProcess, nil
|
||||
}
|
||||
|
||||
return "", ErrNotFound
|
||||
|
@ -215,16 +215,15 @@ func readDefaultRule(reader varbin.Reader, recover bool) (rule option.DefaultHea
|
||||
case ruleItemWIFIBSSID:
|
||||
rule.WIFIBSSID, err = readRuleItemString(reader)
|
||||
case ruleItemAdGuardDomain:
|
||||
if recover {
|
||||
err = E.New("unable to decompile binary AdGuard rules to rule-set")
|
||||
return
|
||||
}
|
||||
var matcher *domain.AdGuardMatcher
|
||||
matcher, err = domain.ReadAdGuardMatcher(reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
rule.AdGuardDomainMatcher = matcher
|
||||
if recover {
|
||||
rule.AdGuardDomain = matcher.Dump()
|
||||
}
|
||||
case ruleItemNetworkType:
|
||||
rule.NetworkType, err = readRuleItemUint8[option.InterfaceType](reader)
|
||||
case ruleItemNetworkIsExpensive:
|
||||
|
@ -5,13 +5,13 @@ package tls
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
|
||||
"github.com/caddyserver/certmagic"
|
||||
"github.com/libdns/alidns"
|
||||
@ -37,7 +37,38 @@ func (w *acmeWrapper) Close() error {
|
||||
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
|
||||
switch options.Provider {
|
||||
case "", "letsencrypt":
|
||||
@ -58,14 +89,15 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
||||
} else {
|
||||
storage = certmagic.Default.Storage
|
||||
}
|
||||
zapLogger := zap.New(zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(encoderConfig()),
|
||||
&acmeLogWriter{logger: logger},
|
||||
zap.DebugLevel,
|
||||
))
|
||||
config := &certmagic.Config{
|
||||
DefaultServerName: options.DefaultServerName,
|
||||
Storage: storage,
|
||||
Logger: zap.New(zapcore.NewCore(
|
||||
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()),
|
||||
os.Stderr,
|
||||
zap.InfoLevel,
|
||||
)),
|
||||
Logger: zapLogger,
|
||||
}
|
||||
acmeConfig := certmagic.ACMEIssuer{
|
||||
CA: acmeServer,
|
||||
@ -75,7 +107,7 @@ func startACME(ctx context.Context, options option.InboundACMEOptions) (*tls.Con
|
||||
DisableTLSALPNChallenge: options.DisableTLSALPNChallenge,
|
||||
AltHTTPPort: int(options.AlternativeHTTPPort),
|
||||
AltTLSALPNPort: int(options.AlternativeTLSPort),
|
||||
Logger: config.Logger,
|
||||
Logger: zapLogger,
|
||||
}
|
||||
if dnsOptions := options.DNS01Challenge; dnsOptions != nil && dnsOptions.Provider != "" {
|
||||
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) {
|
||||
return config, nil
|
||||
},
|
||||
Logger: zapLogger,
|
||||
})
|
||||
config = certmagic.New(cache, *config)
|
||||
var tlsConfig *tls.Config
|
||||
|
@ -9,8 +9,9 @@ import (
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/option"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/logger"
|
||||
)
|
||||
|
||||
func startACME(ctx context.Context, 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`)
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
|
||||
var err error
|
||||
if options.ACME != nil && len(options.ACME.Domain) > 0 {
|
||||
//nolint:staticcheck
|
||||
tlsConfig, acmeService, err = startACME(ctx, common.PtrValueOrDefault(options.ACME))
|
||||
tlsConfig, acmeService, err = startACME(ctx, logger, common.PtrValueOrDefault(options.ACME))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -3,11 +3,15 @@ package transport
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
"github.com/sagernet/sing-box/common/dialer"
|
||||
@ -39,11 +43,13 @@ func RegisterHTTPS(registry *dns.TransportRegistry) {
|
||||
|
||||
type HTTPSTransport struct {
|
||||
dns.TransportAdapter
|
||||
logger logger.ContextLogger
|
||||
dialer N.Dialer
|
||||
destination *url.URL
|
||||
headers http.Header
|
||||
transport *http.Transport
|
||||
logger logger.ContextLogger
|
||||
dialer N.Dialer
|
||||
destination *url.URL
|
||||
headers http.Header
|
||||
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) {
|
||||
@ -161,12 +167,33 @@ func (t *HTTPSTransport) Start(stage adapter.StartStage) error {
|
||||
}
|
||||
|
||||
func (t *HTTPSTransport) Close() error {
|
||||
t.transportAccess.Lock()
|
||||
defer t.transportAccess.Unlock()
|
||||
t.transport.CloseIdleConnections()
|
||||
t.transport = t.transport.Clone()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *HTTPSTransport) Exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
startAt := time.Now()
|
||||
response, err := t.exchange(ctx, message)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrDeadlineExceeded) {
|
||||
t.transportAccess.Lock()
|
||||
defer t.transportAccess.Unlock()
|
||||
if t.transportResetAt.After(startAt) {
|
||||
return nil, err
|
||||
}
|
||||
t.transport.CloseIdleConnections()
|
||||
t.transport = t.transport.Clone()
|
||||
t.transportResetAt = time.Now()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (t *HTTPSTransport) exchange(ctx context.Context, message *mDNS.Msg) (*mDNS.Msg, error) {
|
||||
exMessage := *message
|
||||
exMessage.Id = 0
|
||||
exMessage.Compress = true
|
||||
|
@ -20,7 +20,8 @@ import (
|
||||
)
|
||||
|
||||
func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||
if C.res_init() != 0 {
|
||||
var state C.res_state
|
||||
if C.res_ninit(state) != 0 {
|
||||
return &dnsConfig{
|
||||
servers: defaultNS,
|
||||
search: dnsDefaultSearch(),
|
||||
@ -33,10 +34,10 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||
conf := &dnsConfig{
|
||||
ndots: 1,
|
||||
timeout: 5 * time.Second,
|
||||
attempts: int(C._res.retry),
|
||||
attempts: int(state.retry),
|
||||
}
|
||||
for i := 0; i < int(C._res.nscount); i++ {
|
||||
ns := C._res.nsaddr_list[i]
|
||||
for i := 0; i < int(state.nscount); i++ {
|
||||
ns := state.nsaddr_list[i]
|
||||
addr := C.inet_ntoa(ns.sin_addr)
|
||||
if addr == nil {
|
||||
continue
|
||||
@ -44,7 +45,7 @@ func dnsReadConfig(_ context.Context, _ string) *dnsConfig {
|
||||
conf.servers = append(conf.servers, C.GoString(addr))
|
||||
}
|
||||
for i := 0; ; i++ {
|
||||
search := C._res.dnsrch[i]
|
||||
search := state.dnsrch[i]
|
||||
if search == nil {
|
||||
break
|
||||
}
|
||||
|
@ -2,6 +2,22 @@
|
||||
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
|
||||
|
||||
* Allow `tls_fragment` and `tls_record_fragment` to be enabled together **1**
|
||||
|
@ -25,7 +25,7 @@ icon: material/new-box
|
||||
|
||||
| 类型 | 格式 |
|
||||
|-------------|---------------------------|
|
||||
| `wireguard` | [WireGuard](./wiregaurd/) |
|
||||
| `wireguard` | [WireGuard](./wireguard/) |
|
||||
| `tailscale` | [Tailscale](./tailscale/) |
|
||||
|
||||
#### tag
|
||||
|
@ -59,7 +59,7 @@
|
||||
{
|
||||
"external_controller": "0.0.0.0:9090",
|
||||
"external_ui": "dashboard"
|
||||
// external_ui_download_detour: "direct"
|
||||
// "external_ui_download_detour": "direct"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -59,7 +59,7 @@
|
||||
{
|
||||
"external_controller": "0.0.0.0:9090",
|
||||
"external_ui": "dashboard"
|
||||
// external_ui_download_detour: "direct"
|
||||
// "external_ui_download_detour": "direct"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -64,7 +64,7 @@ icon: material/new-box
|
||||
"auto_redirect_input_mark": "0x2023",
|
||||
"auto_redirect_output_mark": "0x2024",
|
||||
"loopback_address": [
|
||||
"10.0.7.1"
|
||||
"10.7.0.1"
|
||||
],
|
||||
"strict_route": true,
|
||||
"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.
|
||||
|
||||
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.
|
||||
|
||||
|
@ -64,7 +64,7 @@ icon: material/new-box
|
||||
"auto_redirect_input_mark": "0x2023",
|
||||
"auto_redirect_output_mark": "0x2024",
|
||||
"loopback_address": [
|
||||
"10.0.7.1"
|
||||
"10.7.0.1"
|
||||
],
|
||||
"strict_route": true,
|
||||
"route_address": [
|
||||
@ -283,7 +283,7 @@ tun 接口的 IPv6 前缀。
|
||||
|
||||
环回地址是用于使指向指定地址的 TCP 连接连接到来源地址的。
|
||||
|
||||
将选项值设置为 `10.0.7.1` 可实现与 SideStore/StosVPN 相同的行为。
|
||||
将选项值设置为 `10.7.0.1` 可实现与 SideStore/StosVPN 相同的行为。
|
||||
|
||||
当启用 `auto_redirect` 时,可以作为网关为局域网设备(而不仅仅是本地)实现相同的行为。
|
||||
|
||||
|
@ -94,18 +94,13 @@ flowchart TB
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
"type": "tls",
|
||||
"server": "8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
"type": "udp",
|
||||
"server": "223.5.5.5"
|
||||
}
|
||||
],
|
||||
"strategy": "ipv4_only"
|
||||
@ -115,7 +110,8 @@ flowchart TB
|
||||
"type": "tun",
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"auto_route": true,
|
||||
"strict_route": false
|
||||
// "auto_redirect": true, // On linux
|
||||
"strict_route": true
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@ -123,25 +119,23 @@ flowchart TB
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
"action": "sniff"
|
||||
},
|
||||
{
|
||||
"geoip": [
|
||||
"private"
|
||||
],
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"default_domain_resolver": "local",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@ -155,18 +149,13 @@ flowchart TB
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
"type": "tls",
|
||||
"server": "8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
"type": "udp",
|
||||
"server": "223.5.5.5"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -176,7 +165,8 @@ flowchart TB
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"auto_route": true,
|
||||
"strict_route": false
|
||||
// "auto_redirect": true, // On linux
|
||||
"strict_route": true
|
||||
}
|
||||
],
|
||||
"outbounds": [
|
||||
@ -184,25 +174,23 @@ flowchart TB
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
"action": "sniff"
|
||||
},
|
||||
{
|
||||
"geoip": [
|
||||
"private"
|
||||
],
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"default_domain_resolver": "local",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@ -216,23 +204,22 @@ flowchart TB
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
"type": "tls",
|
||||
"server": "8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
"type": "udp",
|
||||
"server": "223.5.5.5"
|
||||
},
|
||||
{
|
||||
"tag": "remote",
|
||||
"address": "fakeip"
|
||||
"type": "fakeip",
|
||||
"inet4_range": "198.18.0.0/15",
|
||||
"inet6_range": "fc00::/18"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"query_type": [
|
||||
"A",
|
||||
@ -241,11 +228,6 @@ flowchart TB
|
||||
"server": "remote"
|
||||
}
|
||||
],
|
||||
"fakeip": {
|
||||
"enabled": true,
|
||||
"inet4_range": "198.18.0.0/15",
|
||||
"inet6_range": "fc00::/18"
|
||||
},
|
||||
"independent_cache": true
|
||||
},
|
||||
"inbounds": [
|
||||
@ -254,6 +236,7 @@ flowchart TB
|
||||
"inet4_address": "172.19.0.1/30",
|
||||
"inet6_address": "fdfe:dcba:9876::1/126",
|
||||
"auto_route": true,
|
||||
// "auto_redirect": true, // On linux
|
||||
"strict_route": true
|
||||
}
|
||||
],
|
||||
@ -262,25 +245,23 @@ flowchart TB
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "dns",
|
||||
"tag": "dns-out"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"protocol": "dns",
|
||||
"outbound": "dns-out"
|
||||
"action": "sniff"
|
||||
},
|
||||
{
|
||||
"geoip": [
|
||||
"private"
|
||||
],
|
||||
"protocol": "dns",
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
}
|
||||
],
|
||||
"default_domain_resolver": "local",
|
||||
"auto_detect_interface": true
|
||||
}
|
||||
}
|
||||
@ -290,54 +271,6 @@ flowchart TB
|
||||
|
||||
=== ":material-dns: DNS rules"
|
||||
|
||||
```json
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "223.5.5.5",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
}
|
||||
]
|
||||
},
|
||||
"route": {
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
"tag": "geosite-geolocation-cn",
|
||||
"format": "binary",
|
||||
"url": "https://raw.githubusercontent.com/SagerNet/sing-geosite/rule-set/geosite-geolocation-cn.srs"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
=== ":material-dns: DNS rules (Enhanced, but slower) (1.9.0+)"
|
||||
|
||||
=== ":material-shield-off: With DNS leaks"
|
||||
|
||||
```json
|
||||
@ -346,35 +279,20 @@ flowchart TB
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
"type": "tls",
|
||||
"server": "8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
"type": "https",
|
||||
"server": "223.5.5.5"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Default",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
@ -392,6 +310,7 @@ flowchart TB
|
||||
]
|
||||
},
|
||||
"route": {
|
||||
"default_domain_resolver": "local",
|
||||
"rule_set": [
|
||||
{
|
||||
"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
|
||||
{
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "google",
|
||||
"address": "tls://8.8.8.8"
|
||||
"type": "tls",
|
||||
"server": "8.8.8.8"
|
||||
},
|
||||
{
|
||||
"tag": "local",
|
||||
"address": "https://223.5.5.5/dns-query",
|
||||
"detour": "direct"
|
||||
"type": "https",
|
||||
"server": "223.5.5.5"
|
||||
}
|
||||
],
|
||||
"rules": [
|
||||
{
|
||||
"outbound": "any",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"server": "local"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"server": "google"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"server": "local"
|
||||
@ -476,6 +384,7 @@ flowchart TB
|
||||
]
|
||||
},
|
||||
"route": {
|
||||
"default_domain_resolver": "local",
|
||||
"rule_set": [
|
||||
{
|
||||
"type": "remote",
|
||||
@ -517,14 +426,13 @@ flowchart TB
|
||||
{
|
||||
"type": "direct",
|
||||
"tag": "direct"
|
||||
},
|
||||
{
|
||||
"type": "block",
|
||||
"tag": "block"
|
||||
}
|
||||
],
|
||||
"route": {
|
||||
"rules": [
|
||||
{
|
||||
"action": "sniff"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
@ -536,20 +444,12 @@ flowchart TB
|
||||
"port": 53
|
||||
}
|
||||
],
|
||||
"outbound": "dns"
|
||||
"action": "hijack-dns"
|
||||
},
|
||||
{
|
||||
"ip_is_private": true,
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Direct",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"clash_mode": "Global",
|
||||
"outbound": "default"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "or",
|
||||
@ -565,12 +465,23 @@ flowchart TB
|
||||
"protocol": "stun"
|
||||
}
|
||||
],
|
||||
"outbound": "block"
|
||||
"action": "reject"
|
||||
},
|
||||
{
|
||||
"rule_set": [
|
||||
"geoip-cn",
|
||||
"geosite-geolocation-cn"
|
||||
"rule_set": "geosite-geolocation-cn",
|
||||
"outbound": "direct"
|
||||
},
|
||||
{
|
||||
"type": "logical",
|
||||
"mode": "and",
|
||||
"rules": [
|
||||
{
|
||||
"rule_set": "geoip-cn"
|
||||
},
|
||||
{
|
||||
"rule_set": "geosite-geolocation-!cn",
|
||||
"invert": true
|
||||
}
|
||||
],
|
||||
"outbound": "direct"
|
||||
}
|
||||
@ -591,4 +502,4 @@ flowchart TB
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
6
go.mod
6
go.mod
@ -25,16 +25,16 @@ require (
|
||||
github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a
|
||||
github.com/sagernet/cors v1.2.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/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-quic v0.5.0-beta.2
|
||||
github.com/sagernet/sing-shadowsocks v0.2.8
|
||||
github.com/sagernet/sing-shadowsocks2 v0.2.1
|
||||
github.com/sagernet/sing-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/smux v1.5.34-mod.2
|
||||
github.com/sagernet/tailscale v1.80.3-mod.5
|
||||
|
12
go.sum
12
go.sum
@ -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/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs=
|
||||
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.6/go.mod h1:Pqq2+ZVvs10U7xK+UwJgwYWUykewi8H6vlslAO73n9E=
|
||||
github.com/sagernet/gomobile v0.1.7 h1:I9jCJZTH0weP5MsuydvYHX5QfN/r6Fe8ptAIj1+SJVg=
|
||||
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/go.mod h1:QkkPEJLw59/tfxgapHta14UL5qMUah5NXhO0Kw2Kan4=
|
||||
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/go.mod h1:OV+V5kEBb8kJS7k29MzDu6oj9GyMc7HA07sE1tedxz4=
|
||||
github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b h1:ZjTCYPb5f7aHdf1UpUvE22dVmf7BL8eQ/zLZhjgh7Wo=
|
||||
github.com/sagernet/sing v0.6.11-0.20250521033217-30d675ea099b/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/sagernet/sing v0.6.12-0.20250703120903-7081a0c40539 h1:SK4M4FCNdwV4EiYKIUZ9qM4lr/1NQogJe1YoyYw5DV8=
|
||||
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/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA=
|
||||
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-shadowtls v0.2.1-0.20250503051639-fcd445d33c11 h1:tK+75l64tm9WvEFrYRE1t0YxoFdWQqw/h7Uhzj0vJ+w=
|
||||
github.com/sagernet/sing-shadowtls v0.2.1-0.20250503051639-fcd445d33c11/go.mod h1:sWqKnGlMipCHaGsw1sTTlimyUpgzP4WP3pjhCsYt9oA=
|
||||
github.com/sagernet/sing-tun v0.6.6-0.20250610083027-da0a50057fb5 h1:zlcioVa11g8VLz5L0yPG7PbvQrw7mrxkDDdlMPEgqDk=
|
||||
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 h1:eH9naJXvyF/DZDk0V1SYkL6ypYD+A1tUFWLcT7PRezg=
|
||||
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/go.mod h1:IL8Rr+EGwuqijszZkNrEFTQDKhilEpkqFqOlvdpS6/w=
|
||||
github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4=
|
||||
|
@ -130,9 +130,14 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
deprecated.Report(ctx, deprecated.OptionTUNGSO)
|
||||
}
|
||||
|
||||
platformInterface := service.FromContext[platform.Interface](ctx)
|
||||
tunMTU := options.MTU
|
||||
if tunMTU == 0 {
|
||||
tunMTU = 9000
|
||||
if platformInterface != nil && platformInterface.UnderNetworkExtension() {
|
||||
tunMTU = 4000
|
||||
} else {
|
||||
tunMTU = 9000
|
||||
}
|
||||
}
|
||||
var udpTimeout time.Duration
|
||||
if options.UDPTimeout != 0 {
|
||||
@ -208,7 +213,7 @@ func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLo
|
||||
},
|
||||
udpTimeout: udpTimeout,
|
||||
stack: options.Stack,
|
||||
platformInterface: service.FromContext[platform.Interface](ctx),
|
||||
platformInterface: platformInterface,
|
||||
platformOptions: common.PtrValueOrDefault(options.Platform),
|
||||
}
|
||||
for _, routeAddressSet := range options.RouteAddressSet {
|
||||
|
@ -205,6 +205,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Source = source
|
||||
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)
|
||||
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
@ -219,6 +219,10 @@ func (h *inboundTransportHandler) NewConnectionEx(ctx context.Context, conn net.
|
||||
var metadata adapter.InboundContext
|
||||
metadata.Source = source
|
||||
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)
|
||||
(*Inbound)(h).NewConnectionEx(ctx, conn, metadata, onClose)
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package route
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
@ -10,7 +9,7 @@ import (
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"github.com/sagernet/sing-box/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"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"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) {
|
||||
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"))
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import (
|
||||
"github.com/sagernet/sing-box/common/sniff"
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
"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-vmess"
|
||||
"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)
|
||||
if err != nil {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||
} else {
|
||||
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
|
||||
if selectedRule != nil {
|
||||
switch action := selectedRule.Action().(type) {
|
||||
case *rule.RuleActionRoute:
|
||||
case *R.RuleActionRoute:
|
||||
var loaded bool
|
||||
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||
if !loaded {
|
||||
@ -110,10 +110,10 @@ func (r *Router) routeConnection(ctx context.Context, conn net.Conn, metadata ad
|
||||
buf.ReleaseMulti(buffers)
|
||||
return E.New("TCP is not supported by outbound: ", selectedOutbound.Tag())
|
||||
}
|
||||
case *rule.RuleActionReject:
|
||||
case *R.RuleActionReject:
|
||||
buf.ReleaseMulti(buffers)
|
||||
return action.Error(ctx)
|
||||
case *rule.RuleActionHijackDNS:
|
||||
case *R.RuleActionHijackDNS:
|
||||
for _, buffer := range buffers {
|
||||
conn = bufio.NewCachedConn(conn, buffer)
|
||||
}
|
||||
@ -151,7 +151,7 @@ func (r *Router) RoutePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
}))
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||
} else {
|
||||
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)
|
||||
if err != nil {
|
||||
N.CloseOnHandshakeFailure(conn, onClose, err)
|
||||
if E.IsClosedOrCanceled(err) {
|
||||
if E.IsClosedOrCanceled(err) || R.IsRejected(err) {
|
||||
r.logger.DebugContext(ctx, "connection closed: ", err)
|
||||
} else {
|
||||
r.logger.ErrorContext(ctx, err)
|
||||
@ -214,7 +214,7 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
var selectReturn bool
|
||||
if selectedRule != nil {
|
||||
switch action := selectedRule.Action().(type) {
|
||||
case *rule.RuleActionRoute:
|
||||
case *R.RuleActionRoute:
|
||||
var loaded bool
|
||||
selectedOutbound, loaded = r.outbound.Outbound(action.Outbound)
|
||||
if !loaded {
|
||||
@ -225,10 +225,10 @@ func (r *Router) routePacketConnection(ctx context.Context, conn N.PacketConn, m
|
||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||
return E.New("UDP is not supported by outbound: ", selectedOutbound.Tag())
|
||||
}
|
||||
case *rule.RuleActionReject:
|
||||
case *R.RuleActionReject:
|
||||
N.ReleaseMultiPacketBuffer(packetBuffers)
|
||||
return action.Error(ctx)
|
||||
case *rule.RuleActionHijackDNS:
|
||||
case *R.RuleActionHijackDNS:
|
||||
return r.hijackDNSPacket(ctx, conn, packetBuffers, metadata, onClose)
|
||||
}
|
||||
}
|
||||
@ -266,7 +266,7 @@ func (r *Router) PreMatch(metadata adapter.InboundContext) error {
|
||||
if selectedRule == nil {
|
||||
return nil
|
||||
}
|
||||
rejectAction, isReject := selectedRule.Action().(*rule.RuleActionReject)
|
||||
rejectAction, isReject := selectedRule.Action().(*R.RuleActionReject)
|
||||
if !isReject {
|
||||
return nil
|
||||
}
|
||||
@ -342,7 +342,7 @@ func (r *Router) matchRule(
|
||||
//nolint:staticcheck
|
||||
if metadata.InboundOptions != common.DefaultValue[option.InboundOptions]() {
|
||||
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,
|
||||
Timeout: time.Duration(metadata.InboundOptions.SniffTimeout),
|
||||
}, inputConn, inputPacketConn, nil)
|
||||
@ -357,7 +357,7 @@ func (r *Router) matchRule(
|
||||
}
|
||||
}
|
||||
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),
|
||||
})
|
||||
if fatalErr != nil {
|
||||
@ -394,11 +394,11 @@ match:
|
||||
}
|
||||
}
|
||||
}
|
||||
var routeOptions *rule.RuleActionRouteOptions
|
||||
var routeOptions *R.RuleActionRouteOptions
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *rule.RuleActionRoute:
|
||||
case *R.RuleActionRoute:
|
||||
routeOptions = &action.RuleActionRouteOptions
|
||||
case *rule.RuleActionRouteOptions:
|
||||
case *R.RuleActionRouteOptions:
|
||||
routeOptions = action
|
||||
}
|
||||
if routeOptions != nil {
|
||||
@ -451,7 +451,7 @@ match:
|
||||
}
|
||||
}
|
||||
switch action := currentRule.Action().(type) {
|
||||
case *rule.RuleActionSniff:
|
||||
case *R.RuleActionSniff:
|
||||
if !preMatch {
|
||||
newBuffer, newPacketBuffers, newErr := r.actionSniff(ctx, metadata, action, inputConn, inputPacketConn, buffers)
|
||||
if newErr != nil {
|
||||
@ -468,7 +468,7 @@ match:
|
||||
selectedRuleIndex = currentRuleIndex
|
||||
break match
|
||||
}
|
||||
case *rule.RuleActionResolve:
|
||||
case *R.RuleActionResolve:
|
||||
fatalErr = r.actionResolve(ctx, metadata, action)
|
||||
if fatalErr != nil {
|
||||
return
|
||||
@ -488,7 +488,7 @@ match:
|
||||
}
|
||||
|
||||
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,
|
||||
) (buffer *buf.Buffer, packetBuffers []*N.PacketBuffer, fatalErr error) {
|
||||
if sniff.Skip(metadata) {
|
||||
@ -501,6 +501,9 @@ func (r *Router) actionSniff(
|
||||
if inputConn != nil {
|
||||
if len(action.StreamSniffers) == 0 && len(action.PacketSniffers) > 0 {
|
||||
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
|
||||
if len(action.StreamSniffers) > 0 {
|
||||
@ -525,6 +528,7 @@ func (r *Router) actionSniff(
|
||||
action.Timeout,
|
||||
streamSniffers...,
|
||||
)
|
||||
metadata.SniffError = err
|
||||
if err == nil {
|
||||
//goland:noinspection GoDeprecation
|
||||
if action.OverrideDestination && M.IsDomainName(metadata.Domain) {
|
||||
@ -549,8 +553,8 @@ func (r *Router) actionSniff(
|
||||
} else if inputPacketConn != nil {
|
||||
if len(action.PacketSniffers) == 0 && len(action.StreamSniffers) > 0 {
|
||||
return
|
||||
} else if metadata.PacketSniffError != nil && !errors.Is(metadata.PacketSniffError, sniff.ErrNeedMoreData) {
|
||||
r.logger.DebugContext(ctx, "packet sniff skipped due to previous error: ", metadata.PacketSniffError)
|
||||
} 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 packetSniffers []sniff.PacketSniffer
|
||||
@ -598,7 +602,7 @@ func (r *Router) actionSniff(
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if len(packetBuffers) > 0 || metadata.PacketSniffError != nil {
|
||||
if len(packetBuffers) > 0 || metadata.SniffError != nil {
|
||||
err = sniff.PeekPacket(
|
||||
ctx,
|
||||
metadata,
|
||||
@ -618,7 +622,7 @@ func (r *Router) actionSniff(
|
||||
Destination: destination,
|
||||
}
|
||||
packetBuffers = append(packetBuffers, packetBuffer)
|
||||
metadata.PacketSniffError = err
|
||||
metadata.SniffError = err
|
||||
if errors.Is(err, sniff.ErrNeedMoreData) {
|
||||
// TODO: replace with generic message when there are more multi-packet protocols
|
||||
r.logger.DebugContext(ctx, "attempt to sniff fragmented QUIC client hello")
|
||||
@ -649,7 +653,7 @@ func (r *Router) actionSniff(
|
||||
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() {
|
||||
var transport adapter.DNSTransport
|
||||
if action.Server != "" {
|
||||
|
@ -2,6 +2,7 @@ package rule
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -284,6 +285,23 @@ func (r *RuleActionDirect) String() string {
|
||||
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 {
|
||||
Method string
|
||||
NoDrop bool
|
||||
@ -307,9 +325,9 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
||||
var returnErr error
|
||||
switch r.Method {
|
||||
case C.RuleActionRejectMethodDefault:
|
||||
returnErr = syscall.ECONNREFUSED
|
||||
returnErr = &RejectedError{syscall.ECONNREFUSED}
|
||||
case C.RuleActionRejectMethodDrop:
|
||||
return tun.ErrDrop
|
||||
return &RejectedError{tun.ErrDrop}
|
||||
default:
|
||||
panic(F.ToString("unknown reject method: ", r.Method))
|
||||
}
|
||||
@ -327,7 +345,7 @@ func (r *RuleActionReject) Error(ctx context.Context) error {
|
||||
if ctx != nil {
|
||||
r.logger.DebugContext(ctx, "dropped due to flooding")
|
||||
}
|
||||
return tun.ErrDrop
|
||||
return &RejectedError{tun.ErrDrop}
|
||||
}
|
||||
return returnErr
|
||||
}
|
||||
|
@ -31,6 +31,9 @@ type HTTPConn struct {
|
||||
}
|
||||
|
||||
func NewHTTP1Conn(conn net.Conn, request *http.Request) *HTTPConn {
|
||||
if request.Header.Get("Host") == "" {
|
||||
request.Header.Set("Host", request.Host)
|
||||
}
|
||||
return &HTTPConn{
|
||||
Conn: conn,
|
||||
request: request,
|
||||
@ -89,9 +92,6 @@ func (c *HTTPConn) writeRequest(payload []byte) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.request.Header.Get("Host") == "" {
|
||||
c.request.Header.Set("Host", c.request.Host)
|
||||
}
|
||||
for key, value := range c.request.Header {
|
||||
_, err = writer.Write([]byte(F.ToString(key, ": ", strings.Join(value, ", "), CRLF)))
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user