From f100a33d9849d6b39dbf0dd729d7656e00282374 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Tue, 3 Jan 2023 21:27:07 +0800 Subject: [PATCH 01/23] proxy-provider support exclude node by node type --- adapter/provider/parser.go | 5 ++++- adapter/provider/provider.go | 32 +++++++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index e55ab914..3fdedce7 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -27,6 +27,7 @@ type proxyProviderSchema struct { Interval int `provider:"interval,omitempty"` Filter string `provider:"filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"` + ExcludeType string `provider:"exclude-type,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"` } @@ -63,5 +64,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter excludeFilter := schema.ExcludeFilter - return NewProxySetProvider(name, interval, filter, excludeFilter, vehicle, hc) + excludeType:=schema.ExcludeType + + return NewProxySetProvider(name, interval, filter, excludeFilter,excludeType,vehicle, hc) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 51f536fe..e32c2bec 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -141,11 +141,16 @@ func stopProxyProvider(pd *ProxySetProvider) { _ = pd.Fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string,excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) if err != nil { return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) } + var excludeTypeArray []string + if excludeType !=""{ + excludeTypeArray=strings.Split(excludeType,"|") + } + var filterRegs []*regexp2.Regexp for _, filter := range strings.Split(filter, "`") { filterReg, err := regexp2.Compile(filter, 0) @@ -164,7 +169,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc healthCheck: hc, } - fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) + fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray,filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) pd.Fetcher = fetcher pd.getSubscriptionInfo() @@ -262,7 +267,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { } } -func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { +func proxiesParseAndFilter(filter string, excludeFilter string,excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { return func(buf []byte) ([]C.Proxy, error) { schema := &ProxySchema{} @@ -282,6 +287,27 @@ func proxiesParseAndFilter(filter string, excludeFilter string, filterRegs []*re proxiesSet := map[string]struct{}{} for _, filterReg := range filterRegs { for idx, mapping := range schema.Proxies { + if nil !=excludeTypeArray && len(excludeTypeArray)>0{ + mType,ok:=mapping["type"] + if !ok { + continue + } + pType,ok:=mType.(string) + if !ok { + continue + } + flag:=false + for i := range excludeTypeArray { + if(strings.EqualFold(pType,excludeTypeArray[i])){ + flag=true + } + + } + if(flag){ + continue + } + + } mName, ok := mapping["name"] if !ok { continue From 29b72df14cecb6869ee3bf406687ce555f71acea Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Tue, 3 Jan 2023 21:47:57 +0800 Subject: [PATCH 02/23] proxy-groups support exclude node by node type --- adapter/outboundgroup/fallback.go | 1 + adapter/outboundgroup/groupbase.go | 25 +++++++++++++++++++++++++ adapter/outboundgroup/loadbalance.go | 1 + adapter/outboundgroup/parser.go | 1 + adapter/outboundgroup/relay.go | 1 + adapter/outboundgroup/selector.go | 1 + adapter/outboundgroup/urltest.go | 1 + 7 files changed, 31 insertions(+) diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 0a4dab41..22ca6db9 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -132,6 +132,7 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) }, option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), disableUDP: option.DisableUDP, diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 426a1282..53c08ad6 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -20,6 +20,7 @@ type GroupBase struct { *outbound.Base filterRegs []*regexp2.Regexp excludeFilterReg *regexp2.Regexp + excludeTypeArray []string providers []provider.ProxyProvider failedTestMux sync.Mutex failedTimes int @@ -33,6 +34,7 @@ type GroupBaseOption struct { outbound.BaseOption filter string excludeFilter string + excludeType string providers []provider.ProxyProvider } @@ -41,6 +43,10 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { if opt.excludeFilter != "" { excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0) } + var excludeTypeArray []string + if opt.excludeType!="" { + excludeTypeArray=strings.Split(opt.excludeType,"|") + } var filterRegs []*regexp2.Regexp if opt.filter != "" { @@ -54,6 +60,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { Base: outbound.NewBase(opt.BaseOption), filterRegs: filterRegs, excludeFilterReg: excludeFilterReg, + excludeTypeArray: excludeTypeArray, providers: opt.providers, failedTesting: atomic.NewBool(false), } @@ -148,6 +155,24 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { } proxies = newProxies } + if gb.excludeTypeArray !=nil{ + var newProxies []C.Proxy + for _, p := range proxies { + mType := p.Type().String() + flag:=false + for i := range gb.excludeTypeArray { + if(strings.EqualFold(mType,gb.excludeTypeArray[i])){ + flag=true + } + + } + if(flag){ + continue + } + newProxies = append(newProxies, p) + } + proxies = newProxies + } if gb.excludeFilterReg != nil { var newProxies []C.Proxy diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 7f875451..0098c505 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -229,6 +229,7 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide }, option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), strategyFn: strategyFn, diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 53a82a60..d8c6db51 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -31,6 +31,7 @@ type GroupCommonOption struct { DisableUDP bool `group:"disable-udp,omitempty"` Filter string `group:"filter,omitempty"` ExcludeFilter string `group:"exclude-filter,omitempty"` + ExcludeType string `group:"exclude-type,omitempty"` } func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 43ef81c9..908204a5 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -191,6 +191,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re }, "", "", + "", providers, }), } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index 71ebacce..de0e3c50 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -100,6 +100,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) }, option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), selected: "COMPATIBLE", diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 55c1cc7c..1ded1656 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -144,6 +144,7 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o option.Filter, option.ExcludeFilter, + option.ExcludeType, providers, }), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), From 8e6989758eb6bacc323371cb03f55eef08ea2d03 Mon Sep 17 00:00:00 2001 From: ag2s20150909 Date: Tue, 3 Jan 2023 22:33:29 +0800 Subject: [PATCH 03/23] fix converter error --- common/convert/converter.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/convert/converter.go b/common/convert/converter.go index 24043a41..1b0e8cf3 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -162,8 +162,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if jsonDc.Decode(&values) != nil { continue } - - name := uniqueName(names, values["ps"].(string)) + tempName,ok:=values["ps"].(string) + if !ok{ + continue + } + name := uniqueName(names, tempName) vmess := make(map[string]any, 20) vmess["name"] = name From 5fa67772392a6acba60dfcf9c8093cac39ff9ebe Mon Sep 17 00:00:00 2001 From: Skyxim Date: Wed, 4 Jan 2023 21:18:07 +0800 Subject: [PATCH 04/23] fix: Process rule is not work in classical rule-set --- rules/provider/classical_strategy.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 12623271..73426e85 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -8,10 +8,11 @@ import ( ) type classicalStrategy struct { - rules []C.Rule - count int - shouldResolveIP bool - parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) + rules []C.Rule + count int + shouldResolveIP bool + shouldFindProcess bool + parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) } func (c *classicalStrategy) Match(metadata *C.Metadata) bool { @@ -32,6 +33,10 @@ func (c *classicalStrategy) ShouldResolveIP() bool { return c.shouldResolveIP } +func (c *classicalStrategy) ShouldFindProcess() bool { + return c.shouldFindProcess +} + func (c *classicalStrategy) OnUpdate(rules []string) { var classicalRules []C.Rule shouldResolveIP := false @@ -45,6 +50,10 @@ func (c *classicalStrategy) OnUpdate(rules []string) { shouldResolveIP = r.ShouldResolveIP() } + if !c.shouldFindProcess { + c.shouldFindProcess = r.ShouldFindProcess() + } + classicalRules = append(classicalRules, r) } } From fd48c6df8a160ab56dd42fefd1074e62e09b6a21 Mon Sep 17 00:00:00 2001 From: H1JK Date: Sat, 7 Jan 2023 12:24:28 +0800 Subject: [PATCH 05/23] chore: Fix fmt in #321 Replace all double spaces to tabs due to Go fmt proposal. --- adapter/outboundgroup/fallback.go | 5 ++- adapter/outboundgroup/groupbase.go | 56 ++++++++++++++------------- adapter/outboundgroup/loadbalance.go | 4 +- adapter/outboundgroup/parser.go | 2 +- adapter/outboundgroup/relay.go | 2 +- adapter/outboundgroup/selector.go | 2 +- adapter/outboundgroup/urltest.go | 2 +- adapter/provider/parser.go | 8 ++-- adapter/provider/provider.go | 57 ++++++++++++++-------------- common/convert/converter.go | 8 ++-- 10 files changed, 75 insertions(+), 71 deletions(-) diff --git a/adapter/outboundgroup/fallback.go b/adapter/outboundgroup/fallback.go index 22ca6db9..34365d0e 100644 --- a/adapter/outboundgroup/fallback.go +++ b/adapter/outboundgroup/fallback.go @@ -4,11 +4,12 @@ import ( "context" "encoding/json" "errors" + "time" + "github.com/Dreamacro/clash/adapter/outbound" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" - "time" ) type Fallback struct { @@ -132,7 +133,7 @@ func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) }, option.Filter, option.ExcludeFilter, - option.ExcludeType, + option.ExcludeType, providers, }), disableUDP: option.DisableUDP, diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 53c08ad6..fee24bd6 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -3,24 +3,26 @@ package outboundgroup import ( "context" "fmt" + "strings" + "sync" + "time" + "github.com/Dreamacro/clash/adapter/outbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" types "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/log" "github.com/Dreamacro/clash/tunnel" + "github.com/dlclark/regexp2" "go.uber.org/atomic" - "strings" - "sync" - "time" ) type GroupBase struct { *outbound.Base filterRegs []*regexp2.Regexp excludeFilterReg *regexp2.Regexp - excludeTypeArray []string + excludeTypeArray []string providers []provider.ProxyProvider failedTestMux sync.Mutex failedTimes int @@ -34,7 +36,7 @@ type GroupBaseOption struct { outbound.BaseOption filter string excludeFilter string - excludeType string + excludeType string providers []provider.ProxyProvider } @@ -43,10 +45,10 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { if opt.excludeFilter != "" { excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0) } - var excludeTypeArray []string - if opt.excludeType!="" { - excludeTypeArray=strings.Split(opt.excludeType,"|") - } + var excludeTypeArray []string + if opt.excludeType != "" { + excludeTypeArray = strings.Split(opt.excludeType, "|") + } var filterRegs []*regexp2.Regexp if opt.filter != "" { @@ -60,7 +62,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase { Base: outbound.NewBase(opt.BaseOption), filterRegs: filterRegs, excludeFilterReg: excludeFilterReg, - excludeTypeArray: excludeTypeArray, + excludeTypeArray: excludeTypeArray, providers: opt.providers, failedTesting: atomic.NewBool(false), } @@ -155,24 +157,24 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { } proxies = newProxies } - if gb.excludeTypeArray !=nil{ - var newProxies []C.Proxy - for _, p := range proxies { - mType := p.Type().String() - flag:=false - for i := range gb.excludeTypeArray { - if(strings.EqualFold(mType,gb.excludeTypeArray[i])){ - flag=true - } + if gb.excludeTypeArray != nil { + var newProxies []C.Proxy + for _, p := range proxies { + mType := p.Type().String() + flag := false + for i := range gb.excludeTypeArray { + if strings.EqualFold(mType, gb.excludeTypeArray[i]) { + flag = true + } - } - if(flag){ - continue - } - newProxies = append(newProxies, p) - } - proxies = newProxies - } + } + if flag { + continue + } + newProxies = append(newProxies, p) + } + proxies = newProxies + } if gb.excludeFilterReg != nil { var newProxies []C.Proxy diff --git a/adapter/outboundgroup/loadbalance.go b/adapter/outboundgroup/loadbalance.go index 0098c505..7dc4d3d3 100644 --- a/adapter/outboundgroup/loadbalance.go +++ b/adapter/outboundgroup/loadbalance.go @@ -5,11 +5,11 @@ import ( "encoding/json" "errors" "fmt" - "github.com/Dreamacro/clash/common/cache" "net" "time" "github.com/Dreamacro/clash/adapter/outbound" + "github.com/Dreamacro/clash/common/cache" "github.com/Dreamacro/clash/common/murmur3" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" @@ -229,7 +229,7 @@ func NewLoadBalance(option *GroupCommonOption, providers []provider.ProxyProvide }, option.Filter, option.ExcludeFilter, - option.ExcludeType, + option.ExcludeType, providers, }), strategyFn: strategyFn, diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index d8c6db51..ebae562a 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -31,7 +31,7 @@ type GroupCommonOption struct { DisableUDP bool `group:"disable-udp,omitempty"` Filter string `group:"filter,omitempty"` ExcludeFilter string `group:"exclude-filter,omitempty"` - ExcludeType string `group:"exclude-type,omitempty"` + ExcludeType string `group:"exclude-type,omitempty"` } func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider) (C.ProxyAdapter, error) { diff --git a/adapter/outboundgroup/relay.go b/adapter/outboundgroup/relay.go index 908204a5..b466b1ff 100644 --- a/adapter/outboundgroup/relay.go +++ b/adapter/outboundgroup/relay.go @@ -191,7 +191,7 @@ func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Re }, "", "", - "", + "", providers, }), } diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index de0e3c50..6356d10e 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -100,7 +100,7 @@ func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) }, option.Filter, option.ExcludeFilter, - option.ExcludeType, + option.ExcludeType, providers, }), selected: "COMPATIBLE", diff --git a/adapter/outboundgroup/urltest.go b/adapter/outboundgroup/urltest.go index 1ded1656..27cef9c6 100644 --- a/adapter/outboundgroup/urltest.go +++ b/adapter/outboundgroup/urltest.go @@ -144,7 +144,7 @@ func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, o option.Filter, option.ExcludeFilter, - option.ExcludeType, + option.ExcludeType, providers, }), fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10), diff --git a/adapter/provider/parser.go b/adapter/provider/parser.go index 3fdedce7..fc5ed936 100644 --- a/adapter/provider/parser.go +++ b/adapter/provider/parser.go @@ -3,10 +3,10 @@ package provider import ( "errors" "fmt" - "github.com/Dreamacro/clash/component/resource" "time" "github.com/Dreamacro/clash/common/structure" + "github.com/Dreamacro/clash/component/resource" C "github.com/Dreamacro/clash/constant" types "github.com/Dreamacro/clash/constant/provider" ) @@ -27,7 +27,7 @@ type proxyProviderSchema struct { Interval int `provider:"interval,omitempty"` Filter string `provider:"filter,omitempty"` ExcludeFilter string `provider:"exclude-filter,omitempty"` - ExcludeType string `provider:"exclude-type,omitempty"` + ExcludeType string `provider:"exclude-type,omitempty"` HealthCheck healthCheckSchema `provider:"health-check,omitempty"` } @@ -64,7 +64,7 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide interval := time.Duration(uint(schema.Interval)) * time.Second filter := schema.Filter excludeFilter := schema.ExcludeFilter - excludeType:=schema.ExcludeType + excludeType := schema.ExcludeType - return NewProxySetProvider(name, interval, filter, excludeFilter,excludeType,vehicle, hc) + return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, vehicle, hc) } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index e32c2bec..7d7ba977 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -5,8 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/dlclark/regexp2" - "gopkg.in/yaml.v3" "net/http" "runtime" "strings" @@ -19,6 +17,9 @@ import ( C "github.com/Dreamacro/clash/constant" types "github.com/Dreamacro/clash/constant/provider" "github.com/Dreamacro/clash/log" + + "github.com/dlclark/regexp2" + "gopkg.in/yaml.v3" ) const ( @@ -141,15 +142,15 @@ func stopProxyProvider(pd *ProxySetProvider) { _ = pd.Fetcher.Destroy() } -func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string,excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { +func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) { excludeFilterReg, err := regexp2.Compile(excludeFilter, 0) if err != nil { return nil, fmt.Errorf("invalid excludeFilter regex: %w", err) } - var excludeTypeArray []string - if excludeType !=""{ - excludeTypeArray=strings.Split(excludeType,"|") - } + var excludeTypeArray []string + if excludeType != "" { + excludeTypeArray = strings.Split(excludeType, "|") + } var filterRegs []*regexp2.Regexp for _, filter := range strings.Split(filter, "`") { @@ -169,7 +170,7 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc healthCheck: hc, } - fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray,filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) + fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg), proxiesOnUpdate(pd)) pd.Fetcher = fetcher pd.getSubscriptionInfo() @@ -267,7 +268,7 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) { } } -func proxiesParseAndFilter(filter string, excludeFilter string,excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { +func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp) resource.Parser[[]C.Proxy] { return func(buf []byte) ([]C.Proxy, error) { schema := &ProxySchema{} @@ -287,27 +288,27 @@ func proxiesParseAndFilter(filter string, excludeFilter string,excludeTypeArray proxiesSet := map[string]struct{}{} for _, filterReg := range filterRegs { for idx, mapping := range schema.Proxies { - if nil !=excludeTypeArray && len(excludeTypeArray)>0{ - mType,ok:=mapping["type"] - if !ok { - continue - } - pType,ok:=mType.(string) - if !ok { - continue - } - flag:=false - for i := range excludeTypeArray { - if(strings.EqualFold(pType,excludeTypeArray[i])){ - flag=true - } + if nil != excludeTypeArray && len(excludeTypeArray) > 0 { + mType, ok := mapping["type"] + if !ok { + continue + } + pType, ok := mType.(string) + if !ok { + continue + } + flag := false + for i := range excludeTypeArray { + if strings.EqualFold(pType, excludeTypeArray[i]) { + flag = true + } - } - if(flag){ - continue - } + } + if flag { + continue + } - } + } mName, ok := mapping["name"] if !ok { continue diff --git a/common/convert/converter.go b/common/convert/converter.go index 1b0e8cf3..da69f9d0 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -162,10 +162,10 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if jsonDc.Decode(&values) != nil { continue } - tempName,ok:=values["ps"].(string) - if !ok{ - continue - } + tempName, ok := values["ps"].(string) + if !ok { + continue + } name := uniqueName(names, tempName) vmess := make(map[string]any, 20) From b4503908df06b6b24a8bf084a8327e96f9fe2e2e Mon Sep 17 00:00:00 2001 From: chain710 Date: Thu, 5 Jan 2023 16:58:36 +0800 Subject: [PATCH 06/23] fix #322: add option general.find-process-mode, user can turn off findProcess feature in router findProcess slow down connection due to repeat call to FindProcessName in router environment this option has 3 values: always, strict, off - always, equal to enable-process: true. Just try to merge all process related option into one - strict, as default value, behavior remains unchanged - off, turn off findProcess, useful in router environment --- component/process/find_process_mode.go | 57 +++++++++++ config/config.go | 133 +++++++++++++------------ hub/executor/executor.go | 2 +- tunnel/tunnel.go | 15 ++- 4 files changed, 137 insertions(+), 70 deletions(-) create mode 100644 component/process/find_process_mode.go diff --git a/component/process/find_process_mode.go b/component/process/find_process_mode.go new file mode 100644 index 00000000..06618cef --- /dev/null +++ b/component/process/find_process_mode.go @@ -0,0 +1,57 @@ +package process + +import ( + "encoding/json" + "errors" + "strings" +) + +const ( + FindProcessAlways = "always" + FindProcessStrict = "strict" + FindProcessOff = "off" +) + +var ( + validModes = map[string]struct{}{ + FindProcessAlways: {}, + FindProcessOff: {}, + FindProcessStrict: {}, + } +) + +type FindProcessMode string + +func (m FindProcessMode) Always() bool { + return m == FindProcessAlways +} + +func (m FindProcessMode) Off() bool { + return m == FindProcessOff +} + +func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error { + var tp string + if err := unmarshal(&tp); err != nil { + return err + } + return m.Set(tp) +} + +func (m *FindProcessMode) UnmarshalJSON(data []byte) error { + var tp string + if err := json.Unmarshal(data, &tp); err != nil { + return err + } + return m.Set(tp) +} + +func (m *FindProcessMode) Set(value string) error { + mode := strings.ToLower(value) + _, exist := validModes[mode] + if !exist { + return errors.New("invalid find process mode") + } + *m = FindProcessMode(mode) + return nil +} diff --git a/config/config.go b/config/config.go index da05877f..e2250e8a 100644 --- a/config/config.go +++ b/config/config.go @@ -4,6 +4,7 @@ import ( "container/list" "errors" "fmt" + P "github.com/Dreamacro/clash/component/process" "net" "net/netip" "net/url" @@ -42,18 +43,19 @@ import ( type General struct { Inbound Controller - Mode T.TunnelMode `json:"mode"` - UnifiedDelay bool - LogLevel log.LogLevel `json:"log-level"` - IPv6 bool `json:"ipv6"` - Interface string `json:"interface-name"` - RoutingMark int `json:"-"` - GeodataMode bool `json:"geodata-mode"` - GeodataLoader string `json:"geodata-loader"` - TCPConcurrent bool `json:"tcp-concurrent"` - EnableProcess bool `json:"enable-process"` - Sniffing bool `json:"sniffing"` - EBpf EBpf `json:"-"` + Mode T.TunnelMode `json:"mode"` + UnifiedDelay bool + LogLevel log.LogLevel `json:"log-level"` + IPv6 bool `json:"ipv6"` + Interface string `json:"interface-name"` + RoutingMark int `json:"-"` + GeodataMode bool `json:"geodata-mode"` + GeodataLoader string `json:"geodata-loader"` + TCPConcurrent bool `json:"tcp-concurrent"` + EnableProcess bool `json:"enable-process"` + FindProcessMode P.FindProcessMode `json:"find-process-mode"` + Sniffing bool `json:"sniffing"` + EBpf EBpf `json:"-"` } // Inbound config @@ -226,32 +228,33 @@ type RawTuicServer struct { } type RawConfig struct { - Port int `yaml:"port"` - SocksPort int `yaml:"socks-port"` - RedirPort int `yaml:"redir-port"` - TProxyPort int `yaml:"tproxy-port"` - MixedPort int `yaml:"mixed-port"` - ShadowSocksConfig string `yaml:"ss-config"` - VmessConfig string `yaml:"vmess-config"` - InboundTfo bool `yaml:"inbound-tfo"` - Authentication []string `yaml:"authentication"` - AllowLan bool `yaml:"allow-lan"` - BindAddress string `yaml:"bind-address"` - Mode T.TunnelMode `yaml:"mode"` - UnifiedDelay bool `yaml:"unified-delay"` - LogLevel log.LogLevel `yaml:"log-level"` - IPv6 bool `yaml:"ipv6"` - ExternalController string `yaml:"external-controller"` - ExternalControllerTLS string `yaml:"external-controller-tls"` - ExternalUI string `yaml:"external-ui"` - Secret string `yaml:"secret"` - Interface string `yaml:"interface-name"` - RoutingMark int `yaml:"routing-mark"` - Tunnels []LC.Tunnel `yaml:"tunnels"` - GeodataMode bool `yaml:"geodata-mode"` - GeodataLoader string `yaml:"geodata-loader"` - TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` - EnableProcess bool `yaml:"enable-process" json:"enable-process"` + Port int `yaml:"port"` + SocksPort int `yaml:"socks-port"` + RedirPort int `yaml:"redir-port"` + TProxyPort int `yaml:"tproxy-port"` + MixedPort int `yaml:"mixed-port"` + ShadowSocksConfig string `yaml:"ss-config"` + VmessConfig string `yaml:"vmess-config"` + InboundTfo bool `yaml:"inbound-tfo"` + Authentication []string `yaml:"authentication"` + AllowLan bool `yaml:"allow-lan"` + BindAddress string `yaml:"bind-address"` + Mode T.TunnelMode `yaml:"mode"` + UnifiedDelay bool `yaml:"unified-delay"` + LogLevel log.LogLevel `yaml:"log-level"` + IPv6 bool `yaml:"ipv6"` + ExternalController string `yaml:"external-controller"` + ExternalControllerTLS string `yaml:"external-controller-tls"` + ExternalUI string `yaml:"external-ui"` + Secret string `yaml:"secret"` + Interface string `yaml:"interface-name"` + RoutingMark int `yaml:"routing-mark"` + Tunnels []LC.Tunnel `yaml:"tunnels"` + GeodataMode bool `yaml:"geodata-mode"` + GeodataLoader string `yaml:"geodata-loader"` + TCPConcurrent bool `yaml:"tcp-concurrent" json:"tcp-concurrent"` + EnableProcess bool `yaml:"enable-process" json:"enable-process"` + FindProcessMode P.FindProcessMode `yaml:"find-process-mode" json:"find-process-mode"` Sniffer RawSniffer `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -314,21 +317,22 @@ func Parse(buf []byte) (*Config, error) { func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { // config with default value rawCfg := &RawConfig{ - AllowLan: false, - BindAddress: "*", - IPv6: true, - Mode: T.Rule, - GeodataMode: C.GeodataMode, - GeodataLoader: "memconservative", - UnifiedDelay: false, - Authentication: []string{}, - LogLevel: log.INFO, - Hosts: map[string]string{}, - Rule: []string{}, - Proxy: []map[string]any{}, - ProxyGroup: []map[string]any{}, - TCPConcurrent: false, - EnableProcess: false, + AllowLan: false, + BindAddress: "*", + IPv6: true, + Mode: T.Rule, + GeodataMode: C.GeodataMode, + GeodataLoader: "memconservative", + UnifiedDelay: false, + Authentication: []string{}, + LogLevel: log.INFO, + Hosts: map[string]string{}, + Rule: []string{}, + Proxy: []map[string]any{}, + ProxyGroup: []map[string]any{}, + TCPConcurrent: false, + EnableProcess: false, + FindProcessMode: P.FindProcessStrict, Tun: RawTun{ Enable: false, Device: "", @@ -536,17 +540,18 @@ func parseGeneral(cfg *RawConfig) (*General, error) { Secret: cfg.Secret, ExternalControllerTLS: cfg.ExternalControllerTLS, }, - UnifiedDelay: cfg.UnifiedDelay, - Mode: cfg.Mode, - LogLevel: cfg.LogLevel, - IPv6: cfg.IPv6, - Interface: cfg.Interface, - RoutingMark: cfg.RoutingMark, - GeodataMode: cfg.GeodataMode, - GeodataLoader: cfg.GeodataLoader, - TCPConcurrent: cfg.TCPConcurrent, - EnableProcess: cfg.EnableProcess, - EBpf: cfg.EBpf, + UnifiedDelay: cfg.UnifiedDelay, + Mode: cfg.Mode, + LogLevel: cfg.LogLevel, + IPv6: cfg.IPv6, + Interface: cfg.Interface, + RoutingMark: cfg.RoutingMark, + GeodataMode: cfg.GeodataMode, + GeodataLoader: cfg.GeodataLoader, + TCPConcurrent: cfg.TCPConcurrent, + EnableProcess: cfg.EnableProcess, + FindProcessMode: cfg.FindProcessMode, + EBpf: cfg.EBpf, }, nil } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index eb4436fb..55786864 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -309,7 +309,7 @@ func updateTunnels(tunnels []LC.Tunnel) { func updateGeneral(general *config.General, force bool) { tunnel.SetMode(general.Mode) - tunnel.SetAlwaysFindProcess(general.EnableProcess) + tunnel.SetFindProcessMode(general.EnableProcess, general.FindProcessMode) dialer.DisableIPv6 = !general.IPv6 if !dialer.DisableIPv6 { log.Infoln("Use IPv6") diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 8f0ef8f6..c5534062 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -43,7 +43,7 @@ var ( // default timeout for UDP session udpTimeout = 60 * time.Second - alwaysFindProcess = false + findProcessMode P.FindProcessMode fakeIPRange netip.Prefix ) @@ -146,9 +146,14 @@ func SetMode(m TunnelMode) { mode = m } -// SetAlwaysFindProcess set always find process info, may be increase many memory -func SetAlwaysFindProcess(findProcess bool) { - alwaysFindProcess = findProcess +// SetFindProcessMode replace SetAlwaysFindProcess +// always find process info if legacyAlways = true or mode.Always() = true, may be increase many memory +func SetFindProcessMode(legacyAlways bool, mode P.FindProcessMode) { + if legacyAlways { + findProcessMode = P.FindProcessAlways + } else { + findProcessMode = mode + } } // processUDP starts a loop to handle udp packet @@ -463,7 +468,7 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { }() } - if !processFound && (alwaysFindProcess || rule.ShouldFindProcess()) { + if !findProcessMode.Off() && !processFound && (findProcessMode.Always() || rule.ShouldFindProcess()) { srcPort, err := strconv.ParseUint(metadata.SrcPort, 10, 16) uid, path, err := P.FindProcessName(metadata.NetWork.String(), metadata.SrcIP, int(srcPort)) if err != nil { From e9a7e104c01614b50751257a77a62cee328c9968 Mon Sep 17 00:00:00 2001 From: metacubex Date: Mon, 9 Jan 2023 21:07:31 +0800 Subject: [PATCH 07/23] fix: geoip mmdb/geodata init --- component/geodata/init.go | 74 +++++++++++++++++++++++++++++++++-- component/mmdb/mmdb.go | 20 ++++++++++ config/initial.go | 82 +-------------------------------------- rules/common/base.go | 1 - rules/common/geoip.go | 5 +++ rules/common/geosite.go | 11 ++---- 6 files changed, 101 insertions(+), 92 deletions(-) diff --git a/component/geodata/init.go b/component/geodata/init.go index 2b36b626..f7dd7a9e 100644 --- a/component/geodata/init.go +++ b/component/geodata/init.go @@ -2,6 +2,7 @@ package geodata import ( "fmt" + "github.com/Dreamacro/clash/component/mmdb" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" "io" @@ -9,7 +10,8 @@ import ( "os" ) -var initFlag bool +var initGeoSite bool +var initGeoIP int func InitGeoSite() error { if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { @@ -18,8 +20,9 @@ func InitGeoSite() error { return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) } log.Infoln("Download GeoSite.dat finish") + initGeoSite = false } - if !initFlag { + if !initGeoSite { if err := Verify(C.GeositeName); err != nil { log.Warnln("GeoSite.dat invalid, remove and download: %s", err) if err := os.Remove(C.Path.GeoSite()); err != nil { @@ -29,7 +32,7 @@ func InitGeoSite() error { return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) } } - initFlag = true + initGeoSite = true } return nil } @@ -50,3 +53,68 @@ func downloadGeoSite(path string) (err error) { return err } + +func downloadGeoIP(path string) (err error) { + resp, err := http.Get(C.GeoIpUrl) + if err != nil { + return + } + defer resp.Body.Close() + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + + return err +} + +func InitGeoIP() error { + if C.GeodataMode { + if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { + log.Infoln("Can't find GeoIP.dat, start download") + if err := downloadGeoIP(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) + } + log.Infoln("Download GeoIP.dat finish") + initGeoIP = 0 + } + + if initGeoIP != 1 { + if err := Verify(C.GeoipName); err != nil { + log.Warnln("GeoIP.dat invalid, remove and download: %s", err) + if err := os.Remove(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error()) + } + if err := downloadGeoIP(C.Path.GeoIP()); err != nil { + return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) + } + } + initGeoIP = 1 + } + return nil + } + + if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { + log.Infoln("Can't find MMDB, start download") + if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil { + return fmt.Errorf("can't download MMDB: %s", err.Error()) + } + } + + if initGeoIP != 2 { + if !mmdb.Verify() { + log.Warnln("MMDB invalid, remove and download") + if err := os.Remove(C.Path.MMDB()); err != nil { + return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) + } + if err := mmdb.DownloadMMDB(C.Path.MMDB()); err != nil { + return fmt.Errorf("can't download MMDB: %s", err.Error()) + } + } + initGeoIP = 2 + } + return nil +} diff --git a/component/mmdb/mmdb.go b/component/mmdb/mmdb.go index 58f3fe00..8f28d486 100644 --- a/component/mmdb/mmdb.go +++ b/component/mmdb/mmdb.go @@ -2,6 +2,9 @@ package mmdb import ( "github.com/oschwald/geoip2-golang" + "io" + "net/http" + "os" "sync" C "github.com/Dreamacro/clash/constant" @@ -42,3 +45,20 @@ func Instance() *geoip2.Reader { return mmdb } + +func DownloadMMDB(path string) (err error) { + resp, err := http.Get(C.MmdbUrl) + if err != nil { + return + } + defer resp.Body.Close() + + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return err + } + defer f.Close() + _, err = io.Copy(f, resp.Body) + + return err +} diff --git a/config/initial.go b/config/initial.go index d819168f..0921040d 100644 --- a/config/initial.go +++ b/config/initial.go @@ -3,92 +3,12 @@ package config import ( "fmt" "github.com/Dreamacro/clash/component/geodata" - "github.com/Dreamacro/clash/component/mmdb" - "io" - "net/http" "os" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) -func downloadMMDB(path string) (err error) { - resp, err := http.Get(C.MmdbUrl) - if err != nil { - return - } - defer resp.Body.Close() - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, resp.Body) - - return err -} - -func downloadGeoIP(path string) (err error) { - resp, err := http.Get(C.GeoIpUrl) - if err != nil { - return - } - defer resp.Body.Close() - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return err - } - defer f.Close() - _, err = io.Copy(f, resp.Body) - - return err -} - -func initGeoIP() error { - if C.GeodataMode { - if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { - log.Infoln("Can't find GeoIP.dat, start download") - if err := downloadGeoIP(C.Path.GeoIP()); err != nil { - return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) - } - log.Infoln("Download GeoIP.dat finish") - } - - if err := geodata.Verify(C.GeoipName); err != nil { - log.Warnln("GeoIP.dat invalid, remove and download: %s", err) - if err := os.Remove(C.Path.GeoIP()); err != nil { - return fmt.Errorf("can't remove invalid GeoIP.dat: %s", err.Error()) - } - if err := downloadGeoIP(C.Path.GeoIP()); err != nil { - return fmt.Errorf("can't download GeoIP.dat: %s", err.Error()) - } - } - return nil - } - - if _, err := os.Stat(C.Path.MMDB()); os.IsNotExist(err) { - log.Infoln("Can't find MMDB, start download") - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't download MMDB: %s", err.Error()) - } - } - - if !mmdb.Verify() { - log.Warnln("MMDB invalid, remove and download") - if err := os.Remove(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't remove invalid MMDB: %s", err.Error()) - } - - if err := downloadMMDB(C.Path.MMDB()); err != nil { - return fmt.Errorf("can't download MMDB: %s", err.Error()) - } - } - - return nil -} - // Init prepare necessary files func Init(dir string) error { // initial homedir @@ -122,7 +42,7 @@ func Init(dir string) error { C.GeoSiteUrl = rawCfg.GeoXUrl.GeoSite C.MmdbUrl = rawCfg.GeoXUrl.Mmdb // initial GeoIP - if err := initGeoIP(); err != nil { + if err := geodata.InitGeoIP(); err != nil { return fmt.Errorf("can't initial GeoIP: %w", err) } diff --git a/rules/common/base.go b/rules/common/base.go index abc2b434..d912107c 100644 --- a/rules/common/base.go +++ b/rules/common/base.go @@ -6,7 +6,6 @@ import ( var ( errPayload = errors.New("payloadRule error") - initFlag bool noResolve = "no-resolve" ) diff --git a/rules/common/geoip.go b/rules/common/geoip.go index 72e77045..0c134c63 100644 --- a/rules/common/geoip.go +++ b/rules/common/geoip.go @@ -71,6 +71,11 @@ func (g *GEOIP) GetRecodeSize() int { } func NewGEOIP(country string, adapter string, noResolveIP bool) (*GEOIP, error) { + if err := geodata.InitGeoIP(); err != nil { + log.Errorln("can't initial GeoIP: %s", err) + return nil, err + } + if !C.GeodataMode || strings.EqualFold(country, "LAN") { geoip := &GEOIP{ Base: &Base{}, diff --git a/rules/common/geosite.go b/rules/common/geosite.go index e5a0b9f7..865b0b1b 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -50,12 +50,9 @@ func (gs *GEOSITE) GetRecodeSize() int { } func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { - if !initFlag { - if err := geodata.InitGeoSite(); err != nil { - log.Errorln("can't initial GeoSite: %s", err) - return nil, err - } - initFlag = true + if err := geodata.InitGeoSite(); err != nil { + log.Errorln("can't initial GeoSite: %s", err) + return nil, err } matcher, size, err := geodata.LoadGeoSiteMatcher(country) @@ -76,4 +73,4 @@ func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { return geoSite, nil } -//var _ C.Rule = (*GEOSITE)(nil) +var _ C.Rule = (*GEOSITE)(nil) From 01d8b224dbf77a4ef5b4b92d885c54753920afc9 Mon Sep 17 00:00:00 2001 From: metacubex Date: Mon, 9 Jan 2023 23:15:17 +0800 Subject: [PATCH 08/23] fix: vless RoutingMark bind --- adapter/outbound/vless.go | 1 + 1 file changed, 1 insertion(+) diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 06627912..6004eb6b 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -459,6 +459,7 @@ func NewVless(option VlessOption) (*Vless, error) { tp: C.Vless, udp: option.UDP, iface: option.Interface, + rmark: option.RoutingMark, prefer: C.NewDNSPrefer(option.IPVersion), }, client: client, From 261b8a1d0677f3e3f66da7d36540763cdb5815a4 Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Tue, 10 Jan 2023 13:21:32 +0800 Subject: [PATCH 09/23] fix: vmess udp --- adapter/outbound/vmess.go | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 8d018caa..08fd9f37 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -290,16 +290,6 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o } return v.ListenPacketOnStreamConn(c, metadata) } - c, err = dialer.DialContext(ctx, "tcp", v.addr, v.Base.DialOptions(opts...)...) - if err != nil { - return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) - } - tcpKeepAlive(c) - defer func(c net.Conn) { - safeConnClose(c, err) - }(c) - - c, err = v.StreamConn(c, metadata) return v.ListenPacketWithDialer(ctx, dialer.NewDialer(v.Base.DialOptions(opts...)...), metadata) } @@ -315,10 +305,18 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met } c, err := dialer.DialContext(ctx, "tcp", v.addr) + if err != nil { + return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) + } + tcpKeepAlive(c) + defer func(c net.Conn) { + safeConnClose(c, err) + }(c) + + c, err = v.StreamConn(c, metadata) if err != nil { return nil, fmt.Errorf("new vmess client error: %v", err) } - return v.ListenPacketOnStreamConn(c, metadata) } From 3a8e7c889941003a13c15095a339d6c7b28436ec Mon Sep 17 00:00:00 2001 From: metacubex Date: Tue, 10 Jan 2023 18:10:21 +0800 Subject: [PATCH 10/23] chore: vemss converter xudp is true by default --- common/convert/v.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/convert/v.go b/common/convert/v.go index 0e3960f0..53e28ff2 100644 --- a/common/convert/v.go +++ b/common/convert/v.go @@ -15,6 +15,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m proxy["port"] = url.Port() proxy["uuid"] = url.User.Username() proxy["udp"] = true + proxy["xudp"] = true proxy["skip-cert-verify"] = false proxy["tls"] = false tls := strings.ToLower(query.Get("security")) From 0c354c748a6a1d75f86adaa9ae383bf86ec16ed3 Mon Sep 17 00:00:00 2001 From: metacubex Date: Tue, 10 Jan 2023 18:13:18 +0800 Subject: [PATCH 11/23] fix: ss2022 converter password decode error --- common/convert/converter.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/common/convert/converter.go b/common/convert/converter.go index da69f9d0..33d20cc8 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -280,10 +280,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { ) if password, found = urlSS.User.Password(); !found { - dcBuf, _ := enc.DecodeString(cipher) - if !strings.Contains(string(dcBuf), "2022-blake3") { - dcBuf, _ = encRaw.DecodeString(cipher) - } + dcBuf, _ := encRaw.DecodeString(cipher) cipher, password, found = strings.Cut(string(dcBuf), ":") if !found { continue From d38ceb78c91465f1385a32ca52db6f1f493cec58 Mon Sep 17 00:00:00 2001 From: H1JK Date: Tue, 10 Jan 2023 18:25:05 +0800 Subject: [PATCH 12/23] chore: Refine converter packet encoding parse --- common/convert/converter.go | 8 -------- common/convert/v.go | 9 ++++++++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/common/convert/converter.go b/common/convert/converter.go index 33d20cc8..851920a6 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -144,14 +144,6 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { if encryption := query.Get("encryption"); encryption != "" { vmess["cipher"] = encryption } - if packetEncoding := query.Get("packetEncoding"); packetEncoding != "" { - switch packetEncoding { - case "packet": - vmess["packet-addr"] = true - case "xudp": - vmess["xudp"] = true - } - } proxies = append(proxies, vmess) continue } diff --git a/common/convert/v.go b/common/convert/v.go index 53e28ff2..29be4a9f 100644 --- a/common/convert/v.go +++ b/common/convert/v.go @@ -15,7 +15,6 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m proxy["port"] = url.Port() proxy["uuid"] = url.User.Username() proxy["udp"] = true - proxy["xudp"] = true proxy["skip-cert-verify"] = false proxy["tls"] = false tls := strings.ToLower(query.Get("security")) @@ -26,6 +25,14 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m proxy["servername"] = sni } + switch query.Get("packetEncoding") { + case "none": + case "packet": + proxy["packet-addr"] = true + default: + proxy["xudp"] = true + } + network := strings.ToLower(query.Get("type")) if network == "" { network = "tcp" From 0f29c267be7ab320e14c7b7230ed0430363bb063 Mon Sep 17 00:00:00 2001 From: H1JK Date: Tue, 10 Jan 2023 20:47:58 +0800 Subject: [PATCH 13/23] fix: Converter VMess XUDP not enabled by default when using v2rayN style share link --- common/convert/converter.go | 1 + 1 file changed, 1 insertion(+) diff --git a/common/convert/converter.go b/common/convert/converter.go index 851920a6..07b10367 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -172,6 +172,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { vmess["alterId"] = 0 } vmess["udp"] = true + vmess["xudp"] = true vmess["tls"] = false vmess["skip-cert-verify"] = false From dd4e4d75590fbfba98c9ff58d77b21ea2d0c9539 Mon Sep 17 00:00:00 2001 From: metacubex Date: Tue, 10 Jan 2023 21:55:36 +0800 Subject: [PATCH 14/23] chore: ss2022 converter method verify --- common/convert/converter.go | 12 +++++++++--- common/convert/util.go | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/common/convert/converter.go b/common/convert/converter.go index 07b10367..89b4d95f 100644 --- a/common/convert/converter.go +++ b/common/convert/converter.go @@ -268,16 +268,22 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) { } var ( - cipher = urlSS.User.Username() - password string + cipherRaw = urlSS.User.Username() + cipher string + password string ) if password, found = urlSS.User.Password(); !found { - dcBuf, _ := encRaw.DecodeString(cipher) + dcBuf, _ := enc.DecodeString(cipherRaw) cipher, password, found = strings.Cut(string(dcBuf), ":") if !found { continue } + err := VerifyMethod(cipher, password) + if err != nil { + dcBuf, _ := encRaw.DecodeString(cipherRaw) + cipher, password, found = strings.Cut(string(dcBuf), ":") + } } ss := make(map[string]any, 10) diff --git a/common/convert/util.go b/common/convert/util.go index b3bd6e5b..03a48ecd 100644 --- a/common/convert/util.go +++ b/common/convert/util.go @@ -2,6 +2,7 @@ package convert import ( "encoding/base64" + "github.com/metacubex/sing-shadowsocks/shadowimpl" "math/rand" "net/http" "strings" @@ -314,3 +315,8 @@ func SetUserAgent(header http.Header) { userAgent := RandUserAgent() header.Set("User-Agent", userAgent) } + +func VerifyMethod(cipher, password string) (err error) { + _, err = shadowimpl.FetchMethod(cipher, password) + return +} From 337be9124f05e74ea86c59e1878f9a3488c9ee6b Mon Sep 17 00:00:00 2001 From: metacubex Date: Wed, 11 Jan 2023 00:01:28 +0800 Subject: [PATCH 15/23] chore: clean code --- adapter/outbound/vmess.go | 1 - 1 file changed, 1 deletion(-) diff --git a/adapter/outbound/vmess.go b/adapter/outbound/vmess.go index 08fd9f37..4d3bb3d7 100644 --- a/adapter/outbound/vmess.go +++ b/adapter/outbound/vmess.go @@ -116,7 +116,6 @@ func (v *Vmess) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { if len(v.option.Fingerprint) == 0 { wsOpts.TLSConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) } else { - var err error if wsOpts.TLSConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, v.option.Fingerprint); err != nil { return nil, err } From 51f9b34a7c74368c8ce325623822dc505e9ad5d5 Mon Sep 17 00:00:00 2001 From: 3andero <31029660+3andero@users.noreply.github.com> Date: Tue, 10 Jan 2023 08:10:39 -0800 Subject: [PATCH 16/23] feat: Support ShadowTLS v2 as Shadowsocks plugin (#330) --- .github/workflows/prerelease.yml | 11 +-- README.md | 20 +++++ adapter/outbound/shadowsocks.go | 38 +++++++-- flake.nix | 2 +- transport/shadowtls/shadowtls.go | 142 +++++++++++++++++++++++++++++++ transport/simple-obfs/tls.go | 7 +- transport/trojan/trojan.go | 6 +- 7 files changed, 206 insertions(+), 20 deletions(-) create mode 100644 transport/shadowtls/shadowtls.go diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 4a792eaa..c20efb0b 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -36,14 +36,16 @@ jobs: run: make -j$(($(nproc) + 1)) releases - name: Delete current release assets - uses: andreaswilli/delete-release-assets-action@v2.0.0 + uses: mknejp/delete-release-assets@v1 with: github_token: ${{ secrets.GITHUB_TOKEN }} - tag: Prerelease-${{ github.ref_name }} - deleteOnlyFromDrafts: false + tag_name: Prerelease-${{ github.ref_name }} + assets: | + *.zip + *.gz - name: Tag Repo - uses: richardsimko/update-tag@v1 + uses: richardsimko/update-tag@v1.0.6 with: tag_name: Prerelease-${{ github.ref_name }} env: @@ -53,7 +55,6 @@ jobs: uses: softprops/action-gh-release@v1 if: ${{ success() }} with: - tag: ${{ github.ref_name }} tag_name: Prerelease-${{ github.ref_name }} files: bin/* prerelease: true diff --git a/README.md b/README.md index b62e212d..321ce97e 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,26 @@ Documentations are now moved to [GitHub Wiki](https://github.com/Dreamacro/clash ## Advanced usage for this branch +## Build + +You should install [golang](https://go.dev) first. + +Then get the source code of Clash.Meta: +```shell +git clone https://github.com/MetaCubeX/Clash.Meta.git +cd Clash.Meta && go mod download +``` + +If you can't visit github,you should set proxy first: +```shell +go env -w GOPROXY=https://goproxy.io,direct +``` + +So now you can build it: +```shell +go build +``` + ### DNS configuration Support `geosite` with `fallback-filter`. diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index c318d263..736bb5bd 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -10,11 +10,13 @@ import ( "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/transport/shadowtls" obfs "github.com/Dreamacro/clash/transport/simple-obfs" "github.com/Dreamacro/clash/transport/socks5" + "github.com/Dreamacro/clash/transport/trojan" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" - "github.com/metacubex/sing-shadowsocks" + shadowsocks "github.com/metacubex/sing-shadowsocks" "github.com/metacubex/sing-shadowsocks/shadowimpl" "github.com/sagernet/sing/common/bufio" M "github.com/sagernet/sing/common/metadata" @@ -27,9 +29,11 @@ type ShadowSocks struct { option *ShadowSocksOption // obfs - obfsMode string - obfsOption *simpleObfsOption - v2rayOption *v2rayObfs.Option + obfsMode string + obfsOption *simpleObfsOption + v2rayOption *v2rayObfs.Option + shadowTLSOption *shadowTLSOption + tlsConnector *trojan.Trojan } type ShadowSocksOption struct { @@ -61,6 +65,11 @@ type v2rayObfsOption struct { Mux bool `obfs:"mux,omitempty"` } +type shadowTLSOption struct { + Password string `obfs:"password"` + Host string `obfs:"host"` +} + // StreamConn implements C.ProxyAdapter func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { switch ss.obfsMode { @@ -75,6 +84,11 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e if err != nil { return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } + case shadowtls.Mode: + if ss.tlsConnector == nil { + ss.tlsConnector = trojan.New(&trojan.Option{ServerName: ss.shadowTLSOption.Host, SkipCertVerify: false}) + } + c = shadowtls.NewShadowTls(c, ss.shadowTLSOption.Password, ss.tlsConnector) } if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")) @@ -157,6 +171,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { var v2rayOption *v2rayObfs.Option var obfsOption *simpleObfsOption + var shadowTLSOpt *shadowTLSOption obfsMode := "" decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) @@ -192,6 +207,12 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { v2rayOption.TLS = true v2rayOption.SkipCertVerify = opts.SkipCertVerify } + } else if option.Plugin == shadowtls.Mode { + obfsMode = shadowtls.Mode + shadowTLSOpt = &shadowTLSOption{} + if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil { + return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err) + } } return &ShadowSocks{ @@ -206,10 +227,11 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { }, method: method, - option: &option, - obfsMode: obfsMode, - v2rayOption: v2rayOption, - obfsOption: obfsOption, + option: &option, + obfsMode: obfsMode, + v2rayOption: v2rayOption, + obfsOption: obfsOption, + shadowTLSOption: shadowTLSOpt, }, nil } diff --git a/flake.nix b/flake.nix index c372ff4b..88a6eacb 100644 --- a/flake.nix +++ b/flake.nix @@ -28,7 +28,7 @@ inherit version; src = ./.; - vendorSha256 = "sha256-XVz2vts4on42lfxnov4jnUrHzSFF05+i1TVY3C7bgdw="; + vendorSha256 = "sha256-8cbcE9gKJjU14DNTLPc6nneEPZg7Akt+FlSDlPRvG5k="; # Do not build testing suit excludedPackages = [ "./test" ]; diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go new file mode 100644 index 00000000..4a0db6a9 --- /dev/null +++ b/transport/shadowtls/shadowtls.go @@ -0,0 +1,142 @@ +package shadowtls + +import ( + "crypto/hmac" + "crypto/sha1" + "encoding/binary" + "fmt" + "hash" + "io" + "net" + + "github.com/Dreamacro/clash/common/pool" + "github.com/Dreamacro/clash/transport/trojan" +) + +const ( + chunkSize = 1 << 13 + Mode string = "shadow-tls" + hashLen int = 8 + tlsHeaderLen int = 5 +) + +// TLSObfs is shadowsocks tls simple-obfs implementation +type ShadowTls struct { + net.Conn + password []byte + remain int + firstRequest bool + tlsConnector *trojan.Trojan +} + +type HashedConn struct { + net.Conn + hasher hash.Hash +} + +func newHashedStream(conn net.Conn, password []byte) HashedConn { + return HashedConn{ + Conn: conn, + hasher: hmac.New(sha1.New, password), + } +} + +func (h HashedConn) Read(b []byte) (n int, err error) { + n, err = h.Conn.Read(b) + h.hasher.Write(b[:n]) + return +} + +func (to *ShadowTls) read(b []byte) (int, error) { + buf := pool.Get(tlsHeaderLen) + _, err := io.ReadFull(to.Conn, buf) + if err != nil { + return 0, fmt.Errorf("shadowtls read failed %w", err) + } + if buf[0] != 0x17 || buf[1] != 0x3 || buf[2] != 0x3 { + return 0, fmt.Errorf("invalid shadowtls header %v", buf) + } + length := int(binary.BigEndian.Uint16(buf[3:])) + pool.Put(buf) + + if length > len(b) { + n, err := to.Conn.Read(b) + if err != nil { + return n, err + } + to.remain = length - n + return n, nil + } + + return io.ReadFull(to.Conn, b[:length]) +} + +func (to *ShadowTls) Read(b []byte) (int, error) { + if to.remain > 0 { + length := to.remain + if length > len(b) { + length = len(b) + } + + n, err := io.ReadFull(to.Conn, b[:length]) + if err != nil { + return n, fmt.Errorf("shadowtls Read failed with %w", err) + } + to.remain -= n + return n, nil + } + + return to.read(b) +} + +func (to *ShadowTls) Write(b []byte) (int, error) { + length := len(b) + for i := 0; i < length; i += chunkSize { + end := i + chunkSize + if end > length { + end = length + } + + n, err := to.write(b[i:end]) + if err != nil { + return n, fmt.Errorf("shadowtls Write failed with %w, i=%d, end=%d, n=%d", err, i, end, n) + } + } + return length, nil +} + +func (s *ShadowTls) write(b []byte) (int, error) { + var hashVal []byte + if s.firstRequest { + hashedConn := newHashedStream(s.Conn, s.password) + if _, err := s.tlsConnector.StreamConn(hashedConn); err != nil { + return 0, fmt.Errorf("tls connect failed with %w", err) + } + hashVal = hashedConn.hasher.Sum(nil)[:hashLen] + s.firstRequest = false + } + + buf := pool.GetBuffer() + defer pool.PutBuffer(buf) + buf.Write([]byte{0x17, 0x03, 0x03}) + binary.Write(buf, binary.BigEndian, uint16(len(b)+len(hashVal))) + buf.Write(hashVal) + buf.Write(b) + _, err := s.Conn.Write(buf.Bytes()) + if err != nil { + // return 0 because errors occur here make the + // whole situation irrecoverable + return 0, err + } + return len(b), nil +} + +// NewShadowTls return a ShadowTls +func NewShadowTls(conn net.Conn, password string, tlsConnector *trojan.Trojan) net.Conn { + return &ShadowTls{ + Conn: conn, + password: []byte(password), + firstRequest: true, + tlsConnector: tlsConnector, + } +} diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 1c609c15..fed8a483 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -109,7 +109,12 @@ func (to *TLSObfs) write(b []byte) (int, error) { binary.Write(buf, binary.BigEndian, uint16(len(b))) buf.Write(b) _, err := to.Conn.Write(buf.Bytes()) - return len(b), err + if err != nil { + // return 0 because errors occur here make the + // whole situation irrecoverable + return 0, err + } + return len(b), nil } // NewTLSObfs return a SimpleObfs diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 937b5f91..86de2f65 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -8,18 +8,17 @@ import ( "encoding/hex" "errors" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "io" "net" "net/http" "sync" "github.com/Dreamacro/clash/common/pool" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" "github.com/Dreamacro/clash/transport/vless" "github.com/Dreamacro/clash/transport/vmess" - xtls "github.com/xtls/go" ) @@ -117,9 +116,6 @@ func (t *Trojan) StreamConn(conn net.Conn) (net.Conn, error) { } tlsConn := tls.Client(conn, tlsConfig) - if err := tlsConn.Handshake(); err != nil { - return nil, err - } // fix tls handshake not timeout ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) defer cancel() From 6f62d4d5c16743f0d8967a26fcf38129c3a4bb5d Mon Sep 17 00:00:00 2001 From: metacubex Date: Wed, 11 Jan 2023 00:28:21 +0800 Subject: [PATCH 17/23] chore: update config.yaml --- docs/config.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/config.yaml b/docs/config.yaml index b6fc74f9..27be2ce7 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -258,6 +258,17 @@ proxies: # headers: # custom: value + - name: "ss4" + type: ss + server: server + port: 443 + cipher: chacha20-ietf-poly1305 + password: "password" + plugin: shadow-tls + plugin-opts: + host: "cloud.tencent.com" + password: "shadow_tls_password" + # vmess # cipher支持 auto/aes-128-gcm/chacha20-poly1305/none - name: "vmess" From 0035fc23133ba679af06695898a0fe9f096bb7a1 Mon Sep 17 00:00:00 2001 From: cubemaze <117334465+cubemaze@users.noreply.github.com> Date: Wed, 11 Jan 2023 00:50:04 +0800 Subject: [PATCH 18/23] Update prerelease.yml --- .github/workflows/prerelease.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index c20efb0b..4a792eaa 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -36,16 +36,14 @@ jobs: run: make -j$(($(nproc) + 1)) releases - name: Delete current release assets - uses: mknejp/delete-release-assets@v1 + uses: andreaswilli/delete-release-assets-action@v2.0.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} - tag_name: Prerelease-${{ github.ref_name }} - assets: | - *.zip - *.gz + tag: Prerelease-${{ github.ref_name }} + deleteOnlyFromDrafts: false - name: Tag Repo - uses: richardsimko/update-tag@v1.0.6 + uses: richardsimko/update-tag@v1 with: tag_name: Prerelease-${{ github.ref_name }} env: @@ -55,6 +53,7 @@ jobs: uses: softprops/action-gh-release@v1 if: ${{ success() }} with: + tag: ${{ github.ref_name }} tag_name: Prerelease-${{ github.ref_name }} files: bin/* prerelease: true From 0c9a23a53cd678af46f03a6539a8c2a27ef92e7f Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Wed, 11 Jan 2023 09:53:56 +0800 Subject: [PATCH 19/23] fix: dns cache index out of range --- dns/util.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/dns/util.go b/dns/util.go index 8259f22e..e53abab0 100644 --- a/dns/util.go +++ b/dns/util.go @@ -29,9 +29,11 @@ const ( func putMsgToCache(c *cache.LruCache[string, *D.Msg], key string, msg *D.Msg) { // skip dns cache for acme challenge - if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") { - log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) - return + if len(msg.Question) != 0 { + if q := msg.Question[0]; q.Qtype == D.TypeTXT && strings.HasPrefix(q.Name, "_acme-challenge") { + log.Debugln("[DNS] dns cache ignored because of acme challenge for: %s", q.Name) + return + } } var ttl uint32 switch { From 00695137802fef8e43bf17381097d1b68b57b85d Mon Sep 17 00:00:00 2001 From: wwqgtxx Date: Wed, 11 Jan 2023 10:19:30 +0800 Subject: [PATCH 20/23] chore: shadowtls don't depend on trojan's code --- adapter/outbound/shadowsocks.go | 33 ++++++++++++++----- transport/shadowtls/shadowtls.go | 56 +++++++++++++++++++------------- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 736bb5bd..8df84c7c 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -2,6 +2,7 @@ package outbound import ( "context" + "crypto/tls" "errors" "fmt" "net" @@ -9,11 +10,11 @@ import ( "github.com/Dreamacro/clash/common/structure" "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/shadowtls" obfs "github.com/Dreamacro/clash/transport/simple-obfs" "github.com/Dreamacro/clash/transport/socks5" - "github.com/Dreamacro/clash/transport/trojan" v2rayObfs "github.com/Dreamacro/clash/transport/v2ray-plugin" shadowsocks "github.com/metacubex/sing-shadowsocks" @@ -33,7 +34,7 @@ type ShadowSocks struct { obfsOption *simpleObfsOption v2rayOption *v2rayObfs.Option shadowTLSOption *shadowTLSOption - tlsConnector *trojan.Trojan + tlsConfig *tls.Config } type ShadowSocksOption struct { @@ -66,8 +67,10 @@ type v2rayObfsOption struct { } type shadowTLSOption struct { - Password string `obfs:"password"` - Host string `obfs:"host"` + Password string `obfs:"password"` + Host string `obfs:"host"` + Fingerprint string `obfs:"fingerprint,omitempty"` + SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"` } // StreamConn implements C.ProxyAdapter @@ -85,10 +88,7 @@ func (ss *ShadowSocks) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, e return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) } case shadowtls.Mode: - if ss.tlsConnector == nil { - ss.tlsConnector = trojan.New(&trojan.Option{ServerName: ss.shadowTLSOption.Host, SkipCertVerify: false}) - } - c = shadowtls.NewShadowTls(c, ss.shadowTLSOption.Password, ss.tlsConnector) + c = shadowtls.NewShadowTLS(c, ss.shadowTLSOption.Password, ss.tlsConfig) } if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")) @@ -172,6 +172,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { var v2rayOption *v2rayObfs.Option var obfsOption *simpleObfsOption var shadowTLSOpt *shadowTLSOption + var tlsConfig *tls.Config obfsMode := "" decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) @@ -213,6 +214,21 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil { return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err) } + + tlsConfig = &tls.Config{ + NextProtos: shadowtls.DefaultALPN, + MinVersion: tls.VersionTLS12, + InsecureSkipVerify: shadowTLSOpt.SkipCertVerify, + ServerName: shadowTLSOpt.Host, + } + + if len(shadowTLSOpt.Fingerprint) == 0 { + tlsConfig = tlsC.GetGlobalFingerprintTLCConfig(tlsConfig) + } else { + if tlsConfig, err = tlsC.GetSpecifiedFingerprintTLSConfig(tlsConfig, shadowTLSOpt.Fingerprint); err != nil { + return nil, err + } + } } return &ShadowSocks{ @@ -232,6 +248,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { v2rayOption: v2rayOption, obfsOption: obfsOption, shadowTLSOption: shadowTLSOpt, + tlsConfig: tlsConfig, }, nil } diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index 4a0db6a9..86a23779 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -1,8 +1,10 @@ package shadowtls import ( + "context" "crypto/hmac" "crypto/sha1" + "crypto/tls" "encoding/binary" "fmt" "hash" @@ -10,7 +12,7 @@ import ( "net" "github.com/Dreamacro/clash/common/pool" - "github.com/Dreamacro/clash/transport/trojan" + C "github.com/Dreamacro/clash/constant" ) const ( @@ -20,13 +22,17 @@ const ( tlsHeaderLen int = 5 ) -// TLSObfs is shadowsocks tls simple-obfs implementation -type ShadowTls struct { +var ( + DefaultALPN = []string{"h2", "http/1.1"} +) + +// ShadowTLS is shadow-tls implementation +type ShadowTLS struct { net.Conn password []byte remain int firstRequest bool - tlsConnector *trojan.Trojan + tlsConfig *tls.Config } type HashedConn struct { @@ -47,9 +53,9 @@ func (h HashedConn) Read(b []byte) (n int, err error) { return } -func (to *ShadowTls) read(b []byte) (int, error) { +func (s *ShadowTLS) read(b []byte) (int, error) { buf := pool.Get(tlsHeaderLen) - _, err := io.ReadFull(to.Conn, buf) + _, err := io.ReadFull(s.Conn, buf) if err != nil { return 0, fmt.Errorf("shadowtls read failed %w", err) } @@ -60,36 +66,36 @@ func (to *ShadowTls) read(b []byte) (int, error) { pool.Put(buf) if length > len(b) { - n, err := to.Conn.Read(b) + n, err := s.Conn.Read(b) if err != nil { return n, err } - to.remain = length - n + s.remain = length - n return n, nil } - return io.ReadFull(to.Conn, b[:length]) + return io.ReadFull(s.Conn, b[:length]) } -func (to *ShadowTls) Read(b []byte) (int, error) { - if to.remain > 0 { - length := to.remain +func (s *ShadowTLS) Read(b []byte) (int, error) { + if s.remain > 0 { + length := s.remain if length > len(b) { length = len(b) } - n, err := io.ReadFull(to.Conn, b[:length]) + n, err := io.ReadFull(s.Conn, b[:length]) if err != nil { return n, fmt.Errorf("shadowtls Read failed with %w", err) } - to.remain -= n + s.remain -= n return n, nil } - return to.read(b) + return s.read(b) } -func (to *ShadowTls) Write(b []byte) (int, error) { +func (s *ShadowTLS) Write(b []byte) (int, error) { length := len(b) for i := 0; i < length; i += chunkSize { end := i + chunkSize @@ -97,7 +103,7 @@ func (to *ShadowTls) Write(b []byte) (int, error) { end = length } - n, err := to.write(b[i:end]) + n, err := s.write(b[i:end]) if err != nil { return n, fmt.Errorf("shadowtls Write failed with %w, i=%d, end=%d, n=%d", err, i, end, n) } @@ -105,11 +111,15 @@ func (to *ShadowTls) Write(b []byte) (int, error) { return length, nil } -func (s *ShadowTls) write(b []byte) (int, error) { +func (s *ShadowTLS) write(b []byte) (int, error) { var hashVal []byte if s.firstRequest { hashedConn := newHashedStream(s.Conn, s.password) - if _, err := s.tlsConnector.StreamConn(hashedConn); err != nil { + tlsConn := tls.Client(hashedConn, s.tlsConfig) + // fix tls handshake not timeout + ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) + defer cancel() + if err := tlsConn.HandshakeContext(ctx); err != nil { return 0, fmt.Errorf("tls connect failed with %w", err) } hashVal = hashedConn.hasher.Sum(nil)[:hashLen] @@ -131,12 +141,12 @@ func (s *ShadowTls) write(b []byte) (int, error) { return len(b), nil } -// NewShadowTls return a ShadowTls -func NewShadowTls(conn net.Conn, password string, tlsConnector *trojan.Trojan) net.Conn { - return &ShadowTls{ +// NewShadowTLS return a ShadowTLS +func NewShadowTLS(conn net.Conn, password string, tlsConfig *tls.Config) net.Conn { + return &ShadowTLS{ Conn: conn, password: []byte(password), firstRequest: true, - tlsConnector: tlsConnector, + tlsConfig: tlsConfig, } } From be6142aa43de075a168bb8058487c67ea9d5f443 Mon Sep 17 00:00:00 2001 From: Hellojack <106379370+H1JK@users.noreply.github.com> Date: Wed, 11 Jan 2023 22:01:15 +0800 Subject: [PATCH 21/23] feat: VLESS support packet encodings (#334) * adjust: Do not use XTLS on H2 connections * feat: VLESS support XUDP fullcone NAT * fix: VLESS with PacketAddr does not work * fix: VLESS XUDP crash --- adapter/outbound/trojan.go | 25 ++++++------- adapter/outbound/vless.go | 77 +++++++++++++++++++++++++++----------- transport/gun/gun_xtls.go | 60 ----------------------------- transport/vless/conn.go | 21 ++++++----- transport/vless/vless.go | 7 +++- transport/vless/xtls.go | 6 +-- 6 files changed, 87 insertions(+), 109 deletions(-) delete mode 100644 transport/gun/gun_xtls.go diff --git a/adapter/outbound/trojan.go b/adapter/outbound/trojan.go index 698db598..e7928b50 100644 --- a/adapter/outbound/trojan.go +++ b/adapter/outbound/trojan.go @@ -4,12 +4,12 @@ import ( "context" "crypto/tls" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "net" "net/http" "strconv" "github.com/Dreamacro/clash/component/dialer" + tlsC "github.com/Dreamacro/clash/component/tls" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/gun" "github.com/Dreamacro/clash/transport/trojan" @@ -219,13 +219,16 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { Fingerprint: option.Fingerprint, } - if option.Network != "ws" && len(option.Flow) >= 16 { - option.Flow = option.Flow[:16] - switch option.Flow { - case vless.XRO, vless.XRD, vless.XRS: - tOption.Flow = option.Flow - default: - return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) + switch option.Network { + case "", "tcp": + if len(option.Flow) >= 16 { + option.Flow = option.Flow[:16] + switch option.Flow { + case vless.XRO, vless.XRD, vless.XRS: + tOption.Flow = option.Flow + default: + return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow) + } } } @@ -273,11 +276,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) { } } - if t.option.Flow != "" { - t.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig) - } else { - t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) - } + t.transport = gun.NewHTTP2Client(dialFn, tlsConfig) t.gunTLSConfig = tlsConfig t.gunConfig = &gun.Config{ diff --git a/adapter/outbound/vless.go b/adapter/outbound/vless.go index 6004eb6b..e9e382c4 100644 --- a/adapter/outbound/vless.go +++ b/adapter/outbound/vless.go @@ -12,6 +12,10 @@ import ( "strconv" "sync" + vmessSing "github.com/sagernet/sing-vmess" + "github.com/sagernet/sing-vmess/packetaddr" + M "github.com/sagernet/sing/common/metadata" + "github.com/Dreamacro/clash/common/convert" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" @@ -49,6 +53,9 @@ type VlessOption struct { FlowShow bool `proxy:"flow-show,omitempty"` TLS bool `proxy:"tls,omitempty"` UDP bool `proxy:"udp,omitempty"` + PacketAddr bool `proxy:"packet-addr,omitempty"` + XUDP bool `proxy:"xudp,omitempty"` + PacketEncoding string `proxy:"packet-encoding,omitempty"` Network string `proxy:"network,omitempty"` HTTPOpts HTTPOptions `proxy:"http-opts,omitempty"` HTTP2Opts HTTP2Options `proxy:"h2-opts,omitempty"` @@ -137,11 +144,7 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { c, err = vmess.StreamH2Conn(c, h2Opts) case "grpc": - if v.isXTLSEnabled() { - c, err = gun.StreamGunWithXTLSConn(c, v.gunTLSConfig, v.gunConfig) - } else { - c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) - } + c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig) default: // default tcp network // handle TLS And XTLS @@ -152,21 +155,17 @@ func (v *Vless) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { return nil, err } - return v.client.StreamConn(c, parseVlessAddr(metadata)) + return v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) } func (v *Vless) streamTLSOrXTLSConn(conn net.Conn, isH2 bool) (net.Conn, error) { host, _, _ := net.SplitHostPort(v.addr) - if v.isXTLSEnabled() { + if v.isXTLSEnabled() && !isH2 { xtlsOpts := vless.XTLSConfig{ Host: host, SkipCertVerify: v.option.SkipCertVerify, - FingerPrint: v.option.Fingerprint, - } - - if isH2 { - xtlsOpts.NextProtos = []string{"h2"} + Fingerprint: v.option.Fingerprint, } if v.option.ServerName != "" { @@ -212,7 +211,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d safeConnClose(c, err) }(c) - c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) + c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) if err != nil { return nil, err } @@ -259,7 +258,15 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o safeConnClose(c, err) }(c) - c, err = v.client.StreamConn(c, parseVlessAddr(metadata)) + if v.option.PacketAddr { + packetAddrMetadata := *metadata // make a copy + packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress + packetAddrMetadata.DstPort = "443" + + c, err = v.client.StreamConn(c, parseVlessAddr(&packetAddrMetadata, false)) + } else { + c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) + } if err != nil { return nil, fmt.Errorf("new vless client error: %v", err) @@ -289,7 +296,15 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met safeConnClose(c, err) }(c) - c, err = v.StreamConn(c, metadata) + if v.option.PacketAddr { + packetAddrMetadata := *metadata // make a copy + packetAddrMetadata.Host = packetaddr.SeqPacketMagicAddress + packetAddrMetadata.DstPort = "443" + + c, err = v.StreamConn(c, &packetAddrMetadata) + } else { + c, err = v.StreamConn(c, metadata) + } if err != nil { return nil, fmt.Errorf("new vless client error: %v", err) @@ -305,6 +320,17 @@ func (v *Vless) SupportWithDialer() bool { // ListenPacketOnStreamConn implements C.ProxyAdapter func (v *Vless) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { + if v.option.XUDP { + return newPacketConn(&threadSafePacketConn{ + PacketConn: vmessSing.NewXUDPConn(c, M.ParseSocksaddr(metadata.RemoteAddress())), + }, v), nil + } else if v.option.PacketAddr { + return newPacketConn(&threadSafePacketConn{ + PacketConn: packetaddr.NewConn(&vlessPacketConn{ + Conn: c, rAddr: metadata.UDPAddr(), + }, M.ParseSocksaddr(metadata.RemoteAddress())), + }, v), nil + } return newPacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}, v), nil } @@ -313,7 +339,7 @@ func (v *Vless) SupportUOT() bool { return true } -func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { +func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr { var addrType byte var addr []byte switch metadata.AddrType() { @@ -337,7 +363,8 @@ func parseVlessAddr(metadata *C.Metadata) *vless.DstAddr { UDP: metadata.NetWork == C.UDP, AddrType: addrType, Addr: addr, - Port: uint(port), + Port: uint16(port), + Mux: metadata.NetWork == C.UDP && xudp, } } @@ -466,6 +493,16 @@ func NewVless(option VlessOption) (*Vless, error) { option: &option, } + switch option.PacketEncoding { + case "packetaddr", "packet": + option.PacketAddr = true + case "xudp": + option.XUDP = true + } + if option.XUDP { + option.PacketAddr = false + } + switch option.Network { case "h2": if len(option.HTTP2Opts.Host) == 0 { @@ -498,11 +535,7 @@ func NewVless(option VlessOption) (*Vless, error) { v.gunTLSConfig = tlsConfig v.gunConfig = gunConfig - if v.isXTLSEnabled() { - v.transport = gun.NewHTTP2XTLSClient(dialFn, tlsConfig) - } else { - v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) - } + v.transport = gun.NewHTTP2Client(dialFn, tlsConfig) } return v, nil diff --git a/transport/gun/gun_xtls.go b/transport/gun/gun_xtls.go deleted file mode 100644 index 0d110727..00000000 --- a/transport/gun/gun_xtls.go +++ /dev/null @@ -1,60 +0,0 @@ -// Modified from: https://github.com/Qv2ray/gun-lite -// License: MIT - -package gun - -import ( - "crypto/tls" - "fmt" - "net" - - xtls "github.com/xtls/go" - "golang.org/x/net/http2" -) - -func NewHTTP2XTLSClient(dialFn DialFn, tlsConfig *tls.Config) *TransportWrap { - wrap := TransportWrap{} - dialFunc := func(network, addr string, cfg *tls.Config) (net.Conn, error) { - pconn, err := dialFn(network, addr) - if err != nil { - return nil, err - } - - wrap.remoteAddr = pconn.RemoteAddr() - xtlsConfig := &xtls.Config{ - InsecureSkipVerify: cfg.InsecureSkipVerify, - ServerName: cfg.ServerName, - } - - cn := xtls.Client(pconn, xtlsConfig) - if err := cn.Handshake(); err != nil { - pconn.Close() - return nil, err - } - state := cn.ConnectionState() - if p := state.NegotiatedProtocol; p != http2.NextProtoTLS { - cn.Close() - return nil, fmt.Errorf("http2: unexpected ALPN protocol %s, want %s", p, http2.NextProtoTLS) - } - return cn, nil - } - - wrap.Transport = &http2.Transport{ - DialTLS: dialFunc, - TLSClientConfig: tlsConfig, - AllowHTTP: false, - DisableCompression: true, - PingTimeout: 0, - } - - return &wrap -} - -func StreamGunWithXTLSConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config) (net.Conn, error) { - dialFn := func(network, addr string) (net.Conn, error) { - return conn, nil - } - - transport := NewHTTP2XTLSClient(dialFn, tlsConfig) - return StreamGunWithTransport(transport, cfg) -} diff --git a/transport/vless/conn.go b/transport/vless/conn.go index e6e6e34c..5ee69611 100644 --- a/transport/vless/conn.go +++ b/transport/vless/conn.go @@ -51,17 +51,20 @@ func (vc *Conn) sendRequest() error { buf.WriteByte(0) // addon data length. 0 means no addon data } - // command - if vc.dst.UDP { - buf.WriteByte(CommandUDP) + if vc.dst.Mux { + buf.WriteByte(CommandMux) } else { - buf.WriteByte(CommandTCP) - } + if vc.dst.UDP { + buf.WriteByte(CommandUDP) + } else { + buf.WriteByte(CommandTCP) + } - // Port AddrType Addr - binary.Write(buf, binary.BigEndian, uint16(vc.dst.Port)) - buf.WriteByte(vc.dst.AddrType) - buf.Write(vc.dst.Addr) + // Port AddrType Addr + binary.Write(buf, binary.BigEndian, vc.dst.Port) + buf.WriteByte(vc.dst.AddrType) + buf.Write(vc.dst.Addr) + } _, err := vc.Conn.Write(buf.Bytes()) return err diff --git a/transport/vless/vless.go b/transport/vless/vless.go index 5afe2c3e..4b101703 100644 --- a/transport/vless/vless.go +++ b/transport/vless/vless.go @@ -1,9 +1,10 @@ package vless import ( - "github.com/Dreamacro/clash/common/utils" "net" + "github.com/Dreamacro/clash/common/utils" + "github.com/gofrs/uuid" ) @@ -19,6 +20,7 @@ const ( const ( CommandTCP byte = 1 CommandUDP byte = 2 + CommandMux byte = 3 ) // Addr types @@ -33,7 +35,8 @@ type DstAddr struct { UDP bool AddrType byte Addr []byte - Port uint + Port uint16 + Mux bool // currently used for XUDP only } // Client is vless connection generator diff --git a/transport/vless/xtls.go b/transport/vless/xtls.go index 8ef4b44e..ab8248af 100644 --- a/transport/vless/xtls.go +++ b/transport/vless/xtls.go @@ -12,7 +12,7 @@ import ( type XTLSConfig struct { Host string SkipCertVerify bool - FingerPrint string + Fingerprint string NextProtos []string } @@ -22,11 +22,11 @@ func StreamXTLSConn(conn net.Conn, cfg *XTLSConfig) (net.Conn, error) { InsecureSkipVerify: cfg.SkipCertVerify, NextProtos: cfg.NextProtos, } - if len(cfg.FingerPrint) == 0 { + if len(cfg.Fingerprint) == 0 { xtlsConfig = tlsC.GetGlobalFingerprintXTLCConfig(xtlsConfig) } else { var err error - if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.FingerPrint); err != nil { + if xtlsConfig, err = tlsC.GetSpecifiedFingerprintXTLSConfig(xtlsConfig, cfg.Fingerprint); err != nil { return nil, err } } From 95247154d6b0f082471626ddd01fc81650553e0c Mon Sep 17 00:00:00 2001 From: Larvan2 <78135608+Larvan2@users.noreply.github.com> Date: Thu, 12 Jan 2023 00:53:42 +0800 Subject: [PATCH 22/23] Fix: Deprecate TCPMSS (#336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 修改 DefaultTCPMSS 为 MaxDatagramSize 修改 MaxDatagramSize 的值提高 TUIC 的上传速度 --- transport/tuic/congestion/bbr_sender.go | 61 +++++++++++++++++-------- transport/tuic/conn.go | 1 + 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/transport/tuic/congestion/bbr_sender.go b/transport/tuic/congestion/bbr_sender.go index d4ba20d1..1706956b 100644 --- a/transport/tuic/congestion/bbr_sender.go +++ b/transport/tuic/congestion/bbr_sender.go @@ -12,12 +12,14 @@ import ( "github.com/metacubex/quic-go/congestion" ) -const DefaultTCPMSS congestion.ByteCount = 1460 -const DefaultBBRMaxCongestionWindow congestion.ByteCount = 2000 * DefaultTCPMSS -const InitialCongestionWindow congestion.ByteCount = 32 * DefaultTCPMSS -const MinInitialPacketSize = 1200 -const InitialPacketSizeIPv4 = 1252 -const InitialPacketSizeIPv6 = 1232 +const ( + MaxDatagramSize = 1252 + DefaultBBRMaxCongestionWindow congestion.ByteCount = 2000 * MaxDatagramSize + InitialCongestionWindow congestion.ByteCount = 10 * MaxDatagramSize + MinInitialPacketSize = 1200 + InitialPacketSizeIPv4 = 1252 + InitialPacketSizeIPv6 = 1232 +) func GetMaxPacketSize(addr net.Addr) congestion.ByteCount { maxSize := congestion.ByteCount(MinInitialPacketSize) @@ -33,17 +35,32 @@ func GetMaxPacketSize(addr net.Addr) congestion.ByteCount { return maxSize } +func GetMaxOutgoingPacketSize(addr net.Addr) congestion.ByteCount { + maxSize := congestion.ByteCount(MinInitialPacketSize) + // If this is not a UDP address, we don't know anything about the MTU. + // Use the minimum size of an Initial packet as the max packet size. + if udpAddr, ok := addr.(*net.UDPAddr); ok { + + if udpAddr.IP.To4() != nil { + //The maximum packet size of any QUIC packet over IPv4. 1500(Ethernet) - 20(IPv4 header) - 8(UDP header) = 1472. + maxSize = congestion.ByteCount(1472) + } else { + // The maximum outgoing packet size allowed. + // The maximum packet size of any QUIC packet over IPv6, based on ethernet's max + // size, minus the IP and UDP headers. IPv6 has a 40 byte header, UDP adds an + // additional 8 bytes. This is a total overhead of 48 bytes. Ethernet's + // max packet size is 1500 bytes, 1500 - 48 = 1452. + maxSize = congestion.ByteCount(1452) + } + } + return maxSize +} + var ( - // The maximum outgoing packet size allowed. - // The maximum packet size of any QUIC packet over IPv6, based on ethernet's max - // size, minus the IP and UDP headers. IPv6 has a 40 byte header, UDP adds an - // additional 8 bytes. This is a total overhead of 48 bytes. Ethernet's - // max packet size is 1500 bytes, 1500 - 48 = 1452. - MaxOutgoingPacketSize = congestion.ByteCount(1452) // Default maximum packet size used in the Linux TCP implementation. // Used in QUIC for congestion window computations in bytes. - MaxSegmentSize = DefaultTCPMSS + MaxSegmentSize = MaxDatagramSize // Default initial rtt used before any samples are received. InitialRtt = 100 * time.Millisecond @@ -51,10 +68,10 @@ var ( // Constants based on TCP defaults. // The minimum CWND to ensure delayed acks don't reduce bandwidth measurements. // Does not inflate the pacing rate. - DefaultMinimumCongestionWindow = 4 * DefaultTCPMSS + DefaultMinimumCongestionWindow = 4 * MaxDatagramSize // The gain used for the STARTUP, equal to 2/ln(2). - DefaultHighGain = 2.885 + DefaultHighGain = 2.89 // The gain used in STARTUP after loss has been detected. // 1.5 is enough to allow for 25% exogenous loss and still observe a 25% growth @@ -249,11 +266,14 @@ type bbrSender struct { // Latched value of --quic_always_get_bw_sample_when_acked. alwaysGetBwSampleWhenAcked bool - pacer *pacer + pacer *pacer + maxDatagramSize congestion.ByteCount + + MaxOutgoingPacketSize congestion.ByteCount } -func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, maxCongestionWindow congestion.ByteCount) *bbrSender { +func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, initialMaxOutgoingPacketSize, maxCongestionWindow congestion.ByteCount) *bbrSender { b := &bbrSender{ mode: STARTUP, clock: clock, @@ -263,7 +283,7 @@ func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, congestionWindow: initialCongestionWindow, initialCongestionWindow: initialCongestionWindow, maxCongestionWindow: maxCongestionWindow, - minCongestionWindow: DefaultMinimumCongestionWindow, + minCongestionWindow: congestion.ByteCount(DefaultMinimumCongestionWindow), highGain: DefaultHighGain, highCwndGain: DefaultHighGain, drainGain: 1.0 / DefaultHighGain, @@ -274,6 +294,7 @@ func NewBBRSender(clock Clock, initialMaxDatagramSize, initialCongestionWindow, recoveryState: NOT_IN_RECOVERY, recoveryWindow: maxCongestionWindow, minRttSinceLastProbeRtt: InfiniteRTT, + MaxOutgoingPacketSize: initialMaxOutgoingPacketSize, maxDatagramSize: initialMaxDatagramSize, } b.pacer = newPacer(b.BandwidthEstimate) @@ -790,7 +811,7 @@ func (b *bbrSender) MaybeEnterOrExitProbeRtt(now time.Time, isRoundStart, minRtt // PROBE_RTT. The CWND during PROBE_RTT is kMinimumCongestionWindow, but // we allow an extra packet since QUIC checks CWND before sending a // packet. - if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+MaxOutgoingPacketSize { + if b.GetBytesInFlight() < b.ProbeRttCongestionWindow()+b.MaxOutgoingPacketSize { b.exitProbeRttAt = now.Add(ProbeRttTime) b.probeRttRoundPassed = false } @@ -924,7 +945,7 @@ func (b *bbrSender) CalculateRecoveryWindow(ackedBytes, lostBytes congestion.Byt if b.recoveryWindow >= lostBytes { b.recoveryWindow -= lostBytes } else { - b.recoveryWindow = MaxSegmentSize + b.recoveryWindow = congestion.ByteCount(MaxSegmentSize) } // In CONSERVATION mode, just subtracting losses is sufficient. In GROWTH, // release additional |bytes_acked| to achieve a slow-start-like behavior. diff --git a/transport/tuic/conn.go b/transport/tuic/conn.go index 81e8c40e..8e6722c0 100644 --- a/transport/tuic/conn.go +++ b/transport/tuic/conn.go @@ -45,6 +45,7 @@ func SetCongestionController(quicConn quic.Connection, cc string) { congestion.NewBBRSender( congestion.DefaultClock{}, congestion.GetMaxPacketSize(quicConn.RemoteAddr()), + congestion.GetMaxOutgoingPacketSize(quicConn.RemoteAddr()), congestion.InitialCongestionWindow, congestion.DefaultBBRMaxCongestionWindow, ), From 7d6991dd65e1fe7bce4fc14f5092b4bb345e154d Mon Sep 17 00:00:00 2001 From: metacubex Date: Thu, 12 Jan 2023 01:31:38 +0800 Subject: [PATCH 23/23] chore: adjust makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3c079aa1..e23fa2d3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -NAME=Clash.Meta +NAME=clash.meta BINDIR=bin BRANCH=$(shell git branch --show-current) ifeq ($(BRANCH),Alpha)