mirror of
https://github.com/SagerNet/sing-box.git
synced 2025-08-31 22:38:48 +08:00
Compare commits
109 Commits
2eded383c2
...
1c597d1f5f
Author | SHA1 | Date | |
---|---|---|---|
![]() |
1c597d1f5f | ||
![]() |
ea149f2401 | ||
![]() |
fe732ec51a | ||
![]() |
e650a8fdb0 | ||
![]() |
95f242086d | ||
![]() |
ed7fc2f366 | ||
![]() |
aabc089d26 | ||
![]() |
6db3c448b2 | ||
![]() |
d4789529e4 | ||
![]() |
6da963bb4f | ||
![]() |
a721c50745 | ||
![]() |
de96ccdd64 | ||
![]() |
dd80c94b07 | ||
![]() |
48f0e37dde | ||
![]() |
cff3c9e538 | ||
![]() |
fc04e6e3c7 | ||
![]() |
9375b542c5 | ||
![]() |
696b59373b | ||
![]() |
0135405735 | ||
![]() |
a7b269514e | ||
![]() |
b11311af1e | ||
![]() |
628cd5f993 | ||
![]() |
252894192d | ||
![]() |
177feafd2b | ||
![]() |
26d86e3831 | ||
![]() |
49ed32ec8e | ||
![]() |
ff264ad692 | ||
![]() |
9489ced466 | ||
![]() |
b43901001c | ||
![]() |
3687120b79 | ||
![]() |
f847aad67e | ||
![]() |
f7c72286dd | ||
![]() |
fa6d18b202 | ||
![]() |
b8f0dc16fb | ||
![]() |
c4eb22e954 | ||
![]() |
8acfc8be59 | ||
![]() |
355410f78d | ||
![]() |
0ca65510e5 | ||
![]() |
6444016923 | ||
![]() |
45fc68705c | ||
![]() |
0891c0b972 | ||
![]() |
e6854baeb0 | ||
![]() |
00c2fe7085 | ||
![]() |
a426679f64 | ||
![]() |
eb05fa9240 | ||
![]() |
ccee51c9ea | ||
![]() |
92dc32c851 | ||
![]() |
788b6a3512 | ||
![]() |
0a235149d6 | ||
![]() |
adc9b6a919 | ||
![]() |
ce264ed690 | ||
![]() |
fe963060f1 | ||
![]() |
07babae9bc | ||
![]() |
876f4999d5 | ||
![]() |
641f65df00 | ||
![]() |
7dbd4c5a7c | ||
![]() |
d72ae3f17f | ||
![]() |
4af5d6c276 | ||
![]() |
d527c441be | ||
![]() |
af6ee6b245 | ||
![]() |
873364c8fb | ||
![]() |
0980b2db93 | ||
![]() |
6f9b3b93be | ||
![]() |
236c0d9f6e | ||
![]() |
30115b6b68 | ||
![]() |
7c69371e49 | ||
![]() |
3decd70c59 | ||
![]() |
cb18b7aac2 | ||
![]() |
52a7bb75f5 | ||
![]() |
da0e88f960 | ||
![]() |
82418e4978 | ||
![]() |
4f60f9b7da | ||
![]() |
4324aa3e91 | ||
![]() |
736a0d3a73 | ||
![]() |
5c3c5b2493 | ||
![]() |
60fe58ff2b | ||
![]() |
3748e63d03 | ||
![]() |
6d5f8958f7 | ||
![]() |
e3c29ac877 | ||
![]() |
cc27586995 | ||
![]() |
e093fe3307 | ||
![]() |
e8a2cb638f | ||
![]() |
e42302a1a9 | ||
![]() |
c02ab0db8b | ||
![]() |
2f6b7e5b25 | ||
![]() |
81c5656dc6 | ||
![]() |
293ada693f | ||
![]() |
6df1994608 | ||
![]() |
d84e1ba87c | ||
![]() |
3c6c44ec42 | ||
![]() |
2b64f43f1f | ||
![]() |
9b13090ac4 | ||
![]() |
89a582b609 | ||
![]() |
4d6a1cc296 | ||
![]() |
d13b00a118 | ||
![]() |
e69b173464 | ||
![]() |
1e02700f6e | ||
![]() |
bc49dd9d3e | ||
![]() |
7278d2a71f | ||
![]() |
4a7a3e42b8 | ||
![]() |
8ab8d32b9b | ||
![]() |
df916b55bb | ||
![]() |
d4ef59f3aa | ||
![]() |
2936207436 | ||
![]() |
9e8f33c4f2 | ||
![]() |
9e6967186b | ||
![]() |
c1cebdda2e | ||
![]() |
9806a34411 | ||
![]() |
5dc74f93f4 |
@ -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,7 @@ func Convert(reader io.Reader) ([]option.HeadlessRule, error) {
|
||||
parseLine:
|
||||
for scanner.Scan() {
|
||||
ruleLine := scanner.Text()
|
||||
if ruleLine == "" || ruleLine[0] == '!' || ruleLine[0] == '#' {
|
||||
if ruleLine == "" || strings.HasPrefix(ruleLine, "!") || strings.HasPrefix(ruleLine, "#") {
|
||||
continue
|
||||
}
|
||||
originRuleLine := ruleLine
|
||||
@ -92,7 +93,7 @@ parseLine:
|
||||
}
|
||||
if !ignored {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with modifier: ", paramParts[0], ": ", ruleLine)
|
||||
continue parseLine
|
||||
}
|
||||
}
|
||||
@ -120,7 +121,7 @@ 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: ", ruleLine)
|
||||
continue
|
||||
}
|
||||
isRegexp = true
|
||||
@ -130,17 +131,27 @@ parseLine:
|
||||
}
|
||||
if strings.Contains(ruleLine, "/") {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with path: ", ruleLine)
|
||||
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: ", ruleLine)
|
||||
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 cosmetic filter: ", ruleLine)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(ruleLine, "(") || strings.Contains(ruleLine, ")") {
|
||||
ignoredLines++
|
||||
logger.Debug("ignored unsupported cosmetic filter: ", ruleLine)
|
||||
continue
|
||||
}
|
||||
if strings.Contains(ruleLine, "~") {
|
||||
ignoredLines++
|
||||
logger.Debug("ignored unsupported rule modifier: ", ruleLine)
|
||||
continue
|
||||
}
|
||||
var domainCheck string
|
||||
@ -151,7 +162,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 +170,13 @@ parseLine:
|
||||
_, ipErr := parseADGuardIPCIDRLine(ruleLine)
|
||||
if ipErr == nil {
|
||||
ignoredLines++
|
||||
log.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with IPCIDR: ", ruleLine)
|
||||
continue
|
||||
}
|
||||
if M.ParseSocksaddr(domainCheck).Port != 0 {
|
||||
log.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with port: ", ruleLine)
|
||||
} else {
|
||||
log.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||
logger.Debug("ignored unsupported rule with invalid domain: ", ruleLine)
|
||||
}
|
||||
ignoredLines++
|
||||
continue
|
||||
@ -283,10 +294,110 @@ parseLine:
|
||||
},
|
||||
}
|
||||
}
|
||||
log.Info("parsed rules: ", len(ruleLines), "/", len(ruleLines)+ignoredLines)
|
||||
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:]
|
@ -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])
|
@ -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:
|
||||
|
2
go.mod
2
go.mod
@ -34,7 +34,7 @@ require (
|
||||
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.7-0.20250613101921-11f18fa1f602
|
||||
github.com/sagernet/sing-tun v0.6.8-0.20250615093440-d1f6001b58c2
|
||||
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
|
||||
|
4
go.sum
4
go.sum
@ -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.7-0.20250613101921-11f18fa1f602 h1:A7ghDv4GcxdZisEHgICCu6BbaxnhYoCdcGU0M0DgY9U=
|
||||
github.com/sagernet/sing-tun v0.6.7-0.20250613101921-11f18fa1f602/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE=
|
||||
github.com/sagernet/sing-tun v0.6.8-0.20250615093440-d1f6001b58c2 h1:leio5dGtYCKHQam+SyLszq4bbXGaxF4ElK5Aif/lUb8=
|
||||
github.com/sagernet/sing-tun v0.6.8-0.20250615093440-d1f6001b58c2/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=
|
||||
|
Loading…
x
Reference in New Issue
Block a user