From 0a4570b55c7774c323df1d8823184f8079cc58d3 Mon Sep 17 00:00:00 2001 From: adlyq Date: Tue, 7 Jun 2022 17:19:25 +0800 Subject: [PATCH 01/14] fix: group filter touch provider --- adapter/outboundgroup/groupbase.go | 22 ++++++++-------------- adapter/outboundgroup/parser.go | 6 ++++-- adapter/provider/healthcheck.go | 4 +--- adapter/provider/provider.go | 6 ++---- constant/provider/interface.go | 4 +--- 5 files changed, 16 insertions(+), 26 deletions(-) diff --git a/adapter/outboundgroup/groupbase.go b/adapter/outboundgroup/groupbase.go index 707e1a6a49..42b61dd44b 100644 --- a/adapter/outboundgroup/groupbase.go +++ b/adapter/outboundgroup/groupbase.go @@ -51,10 +51,9 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { var proxies []C.Proxy for _, pd := range gb.providers { if touch { - proxies = append(proxies, pd.ProxiesWithTouch()...) - } else { - proxies = append(proxies, pd.Proxies()...) + pd.Touch() } + proxies = append(proxies, pd.Proxies()...) } if len(proxies) == 0 { return append(proxies, tunnel.Proxies()["COMPATIBLE"]) @@ -63,13 +62,12 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { } for _, pd := range gb.providers { - if pd.VehicleType() == types.Compatible { - if touch { - gb.proxies.Store(pd.Name(), pd.ProxiesWithTouch()) - } else { - gb.proxies.Store(pd.Name(), pd.Proxies()) - } + if touch { + pd.Touch() + } + if pd.VehicleType() == types.Compatible { + gb.proxies.Store(pd.Name(), pd.Proxies()) gb.versions.Store(pd.Name(), pd.Version()) continue } @@ -80,11 +78,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy { newProxies []C.Proxy ) - if touch { - proxies = pd.ProxiesWithTouch() - } else { - proxies = pd.Proxies() - } + proxies = pd.Proxies() for _, p := range proxies { if mat, _ := gb.filter.FindStringMatch(p.Name()); mat != nil { diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index 76a1efea16..b808079b9a 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -75,9 +75,11 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide providers = append(providers, pd) providersMap[groupName] = pd } else { - if groupOption.URL == "" || groupOption.Interval == 0 { - //return nil, errMissHealthCheck + if groupOption.URL == "" { groupOption.URL = "http://www.gstatic.com/generate_204" + } + + if groupOption.Interval == 0 { groupOption.Interval = 300 } diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index 430225c443..cc664c9ece 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -32,9 +32,7 @@ func (hc *HealthCheck) process() { ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) go func() { - t := time.NewTicker(30 * time.Second) - <-t.C - t.Stop() + time.Sleep(30 * time.Second) hc.check() }() diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index 32b957010a..a38718a9c5 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -84,9 +84,8 @@ func (pp *proxySetProvider) Proxies() []C.Proxy { return pp.proxies } -func (pp *proxySetProvider) ProxiesWithTouch() []C.Proxy { +func (pp *proxySetProvider) Touch() { pp.healthCheck.touch() - return pp.Proxies() } func (pp *proxySetProvider) setProxies(proxies []C.Proxy) { @@ -178,9 +177,8 @@ func (cp *compatibleProvider) Proxies() []C.Proxy { return cp.proxies } -func (cp *compatibleProvider) ProxiesWithTouch() []C.Proxy { +func (cp *compatibleProvider) Touch() { cp.healthCheck.touch() - return cp.Proxies() } func stopCompatibleProvider(pd *CompatibleProvider) { diff --git a/constant/provider/interface.go b/constant/provider/interface.go index 646e459b24..4e28192522 100644 --- a/constant/provider/interface.go +++ b/constant/provider/interface.go @@ -66,9 +66,7 @@ type Provider interface { type ProxyProvider interface { Provider Proxies() []C.Proxy - // ProxiesWithTouch is used to inform the provider that the proxy is actually being used while getting the list of proxies. - // Commonly used in DialContext and DialPacketConn - ProxiesWithTouch() []C.Proxy + Touch() HealthCheck() Version() uint } From eea9a12560f735d4dfb00a59d275ce0e1f5e0be5 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Thu, 9 Jun 2022 13:52:02 +0800 Subject: [PATCH 02/14] =?UTF-8?q?fix:=20=E8=A7=84=E5=88=99=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E9=BB=98=E8=AE=A4=E7=AD=96=E7=95=A5=E7=BB=84=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tunnel/tunnel.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tunnel/tunnel.go b/tunnel/tunnel.go index 1b7e7514bf..936b2b48ad 100644 --- a/tunnel/tunnel.go +++ b/tunnel/tunnel.go @@ -433,5 +433,5 @@ func match(metadata *C.Metadata) (C.Proxy, C.Rule, error) { } } - return proxies["REJECT"], nil, nil + return proxies["DIRECT"], nil, nil } From a5acd3aa97134fc5eeab95fade339f2b90657829 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 10 Jun 2022 13:36:09 +0800 Subject: [PATCH 03/14] refactor: clear linkname,reduce cycle dependencies,transport init geosite function --- component/geodata/init.go | 52 ++++++++++++++++++++ config/config.go | 4 +- config/initial.go | 42 ---------------- rules/common/geosite.go | 5 +- rules/logic/and.go | 5 +- rules/logic/common.go | 20 +++++--- rules/logic/logic_test.go | 73 +++++++++++++++++++++++++--- rules/logic/not.go | 4 +- rules/logic/or.go | 4 +- rules/parser.go | 47 +++++++++++++++--- rules/provider/classical_strategy.go | 15 ++++-- rules/provider/parse.go | 20 +------- rules/provider/provider.go | 9 ++-- rules/provider/rule_set.go | 2 +- rules/ruleparser/ruleparser.go | 50 ------------------- 15 files changed, 203 insertions(+), 149 deletions(-) create mode 100644 component/geodata/init.go delete mode 100644 rules/ruleparser/ruleparser.go diff --git a/component/geodata/init.go b/component/geodata/init.go new file mode 100644 index 0000000000..2b36b626ce --- /dev/null +++ b/component/geodata/init.go @@ -0,0 +1,52 @@ +package geodata + +import ( + "fmt" + C "github.com/Dreamacro/clash/constant" + "github.com/Dreamacro/clash/log" + "io" + "net/http" + "os" +) + +var initFlag bool + +func InitGeoSite() error { + if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { + log.Infoln("Can't find GeoSite.dat, start download") + if err := downloadGeoSite(C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) + } + log.Infoln("Download GeoSite.dat finish") + } + if !initFlag { + 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 { + return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error()) + } + if err := downloadGeoSite(C.Path.GeoSite()); err != nil { + return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) + } + } + initFlag = true + } + return nil +} + +func downloadGeoSite(path string) (err error) { + resp, err := http.Get(C.GeoSiteUrl) + 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/config.go b/config/config.go index 4d620f8ef4..aa74428333 100644 --- a/config/config.go +++ b/config/config.go @@ -533,7 +533,7 @@ func parseRules(cfg *RawConfig, proxies map[string]C.Proxy) ([]C.Rule, map[strin log.Infoln("Geodata Loader mode: %s", geodata.LoaderName()) // parse rule provider for name, mapping := range cfg.RuleProvider { - rp, err := RP.ParseRuleProvider(name, mapping) + rp, err := RP.ParseRuleProvider(name, mapping, R.ParseRule) if err != nil { return nil, nil, err } @@ -723,7 +723,7 @@ func parseFallbackIPCIDR(ips []string) ([]*netip.Prefix, error) { func parseFallbackGeoSite(countries []string, rules []C.Rule) ([]*router.DomainMatcher, error) { var sites []*router.DomainMatcher if len(countries) > 0 { - if err := initGeoSite(); err != nil { + if err := geodata.InitGeoSite(); err != nil { return nil, fmt.Errorf("can't initial GeoSite: %s", err) } } diff --git a/config/initial.go b/config/initial.go index b41fdd1a42..d819168f36 100644 --- a/config/initial.go +++ b/config/initial.go @@ -12,8 +12,6 @@ import ( "github.com/Dreamacro/clash/log" ) -var initFlag bool - func downloadMMDB(path string) (err error) { resp, err := http.Get(C.MmdbUrl) if err != nil { @@ -48,46 +46,6 @@ func downloadGeoIP(path string) (err error) { return err } -func downloadGeoSite(path string) (err error) { - resp, err := http.Get(C.GeoSiteUrl) - 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 initGeoSite() error { - if _, err := os.Stat(C.Path.GeoSite()); os.IsNotExist(err) { - log.Infoln("Can't find GeoSite.dat, start download") - if err := downloadGeoSite(C.Path.GeoSite()); err != nil { - return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) - } - log.Infoln("Download GeoSite.dat finish") - } - if !initFlag { - if err := geodata.Verify(C.GeositeName); err != nil { - log.Warnln("GeoSite.dat invalid, remove and download: %s", err) - if err := os.Remove(C.Path.GeoSite()); err != nil { - return fmt.Errorf("can't remove invalid GeoSite.dat: %s", err.Error()) - } - if err := downloadGeoSite(C.Path.GeoSite()); err != nil { - return fmt.Errorf("can't download GeoSite.dat: %s", err.Error()) - } - } - initFlag = true - } - return nil -} - func initGeoIP() error { if C.GeodataMode { if _, err := os.Stat(C.Path.GeoIP()); os.IsNotExist(err) { diff --git a/rules/common/geosite.go b/rules/common/geosite.go index 10fe31521c..c88fd78da9 100644 --- a/rules/common/geosite.go +++ b/rules/common/geosite.go @@ -11,9 +11,6 @@ import ( _ "unsafe" ) -//go:linkname initGeoSite github.com/Dreamacro/clash/config.initGeoSite -func initGeoSite() error - type GEOSITE struct { *Base country string @@ -53,7 +50,7 @@ func (gs *GEOSITE) GetRecodeSize() int { func NewGEOSITE(country string, adapter string) (*GEOSITE, error) { if !initFlag { - if err := initGeoSite(); err != nil { + if err := geodata.InitGeoSite(); err != nil { log.Errorln("can't initial GeoSite: %s", err) return nil, err } diff --git a/rules/logic/and.go b/rules/logic/and.go index edcb0e0490..5a9b4d0f54 100644 --- a/rules/logic/and.go +++ b/rules/logic/and.go @@ -19,9 +19,10 @@ func (A *AND) ShouldFindProcess() bool { return false } -func NewAND(payload string, adapter string) (*AND, error) { +func NewAND(payload string, adapter string, + parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*AND, error) { and := &AND{Base: &common.Base{}, payload: payload, adapter: adapter} - rules, err := parseRuleByPayload(payload) + rules, err := parseRuleByPayload(payload, parse) if err != nil { return nil, err } diff --git a/rules/logic/common.go b/rules/logic/common.go index d3d68100e2..736ead43e7 100644 --- a/rules/logic/common.go +++ b/rules/logic/common.go @@ -9,10 +9,7 @@ import ( _ "unsafe" ) -//go:linkname parseRule github.com/Dreamacro/clash/rules.ParseRule -func parseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) - -func parseRuleByPayload(payload string) ([]C.Rule, error) { +func parseRuleByPayload(payload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ([]C.Rule, error) { regex, err := regexp.Compile("\\(.*\\)") if err != nil { return nil, err @@ -29,7 +26,7 @@ func parseRuleByPayload(payload string) ([]C.Rule, error) { for _, subRange := range subRanges { subPayload := payload[subRange.start+1 : subRange.end] - rule, err := payloadToRule(subPayload) + rule, err := payloadToRule(subPayload, parseLogicSubRule(parseRule)) if err != nil { return nil, err } @@ -47,7 +44,7 @@ func containRange(r Range, preStart, preEnd int) bool { return preStart < r.start && preEnd > r.end } -func payloadToRule(subPayload string) (C.Rule, error) { +func payloadToRule(subPayload string, parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (C.Rule, error) { splitStr := strings.SplitN(subPayload, ",", 2) if len(splitStr) < 2 { return nil, fmt.Errorf("[%s] format is error", subPayload) @@ -62,6 +59,17 @@ func payloadToRule(subPayload string) (C.Rule, error) { return parseRule(tp, param[0], "", param[1:]) } +func parseLogicSubRule(parseRule func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { + return func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { + switch tp { + case "MATCH": + return nil, fmt.Errorf("unsupported rule type on logic rule") + default: + return parseRule(tp, payload, target, params) + } + } +} + type Range struct { start int end int diff --git a/rules/logic/logic_test.go b/rules/logic/logic_test.go index c279995b64..b7ea9ebe33 100644 --- a/rules/logic/logic_test.go +++ b/rules/logic/logic_test.go @@ -1,13 +1,72 @@ package logic import ( + "fmt" "github.com/Dreamacro/clash/constant" + RC "github.com/Dreamacro/clash/rules/common" + RP "github.com/Dreamacro/clash/rules/provider" "github.com/stretchr/testify/assert" "testing" ) +func ParseRule(tp, payload, target string, params []string) (parsed constant.Rule, parseErr error) { + switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, target) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, target) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, target) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, target) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "IP-SUFFIX": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve) + case "SRC-IP-SUFFIX": + parsed, parseErr = RC.NewIPSuffix(payload, target, true, true) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, target, true) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, target, false) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, target, true) + case "PROCESS-PATH": + parsed, parseErr = RC.NewProcess(payload, target, false) + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, target) + case "UID": + parsed, parseErr = RC.NewUid(payload, target) + case "IN-TYPE": + parsed, parseErr = RC.NewInType(payload, target) + case "AND": + parsed, parseErr = NewAND(payload, target, ParseRule) + case "OR": + parsed, parseErr = NewOR(payload, target, ParseRule) + case "NOT": + parsed, parseErr = NewNOT(payload, target, ParseRule) + case "RULE-SET": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule) + case "MATCH": + parsed = RC.NewMatch(target) + parseErr = nil + default: + parseErr = fmt.Errorf("unsupported rule type %s", tp) + } + + return +} + func TestAND(t *testing.T) { - and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT") + and, err := NewAND("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) assert.Equal(t, nil, err) assert.Equal(t, "DIRECT", and.adapter) assert.Equal(t, false, and.ShouldResolveIP()) @@ -18,29 +77,29 @@ func TestAND(t *testing.T) { DstPort: "20000", })) - and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT") + and, err = NewAND("(DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) assert.NotEqual(t, nil, err) - and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT") + and, err = NewAND("((AND,(DOMAIN,baidu.com),(NETWORK,TCP)),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) assert.Equal(t, nil, err) } func TestNOT(t *testing.T) { - not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT") + not, err := NewNOT("((DST-PORT,6000-6500))", "REJECT", ParseRule) assert.Equal(t, nil, err) assert.Equal(t, false, not.Match(&constant.Metadata{ DstPort: "6100", })) - _, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT") + _, err = NewNOT("((DST-PORT,5600-6666),(DOMAIN,baidu.com))", "DIRECT", ParseRule) assert.NotEqual(t, nil, err) - _, err = NewNOT("(())", "DIRECT") + _, err = NewNOT("(())", "DIRECT", ParseRule) assert.NotEqual(t, nil, err) } func TestOR(t *testing.T) { - or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT") + or, err := NewOR("((DOMAIN,baidu.com),(NETWORK,TCP),(DST-PORT,10001-65535))", "DIRECT", ParseRule) assert.Equal(t, nil, err) assert.Equal(t, true, or.Match(&constant.Metadata{ NetWork: constant.TCP, diff --git a/rules/logic/not.go b/rules/logic/not.go index d72076fc92..dc14e1d159 100644 --- a/rules/logic/not.go +++ b/rules/logic/not.go @@ -17,9 +17,9 @@ func (not *NOT) ShouldFindProcess() bool { return false } -func NewNOT(payload string, adapter string) (*NOT, error) { +func NewNOT(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*NOT, error) { not := &NOT{Base: &common.Base{}, adapter: adapter} - rule, err := parseRuleByPayload(payload) + rule, err := parseRuleByPayload(payload, parse) if err != nil { return nil, err } diff --git a/rules/logic/or.go b/rules/logic/or.go index f8295552ba..08698b0285 100644 --- a/rules/logic/or.go +++ b/rules/logic/or.go @@ -45,9 +45,9 @@ func (or *OR) ShouldResolveIP() bool { return or.needIP } -func NewOR(payload string, adapter string) (*OR, error) { +func NewOR(payload string, adapter string, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*OR, error) { or := &OR{Base: &common.Base{}, payload: payload, adapter: adapter} - rules, err := parseRuleByPayload(payload) + rules, err := parseRuleByPayload(payload, parse) if err != nil { return nil, err } diff --git a/rules/parser.go b/rules/parser.go index 13154dc358..6ddd63a839 100644 --- a/rules/parser.go +++ b/rules/parser.go @@ -1,29 +1,64 @@ package rules import ( + "fmt" C "github.com/Dreamacro/clash/constant" RC "github.com/Dreamacro/clash/rules/common" "github.com/Dreamacro/clash/rules/logic" RP "github.com/Dreamacro/clash/rules/provider" - "github.com/Dreamacro/clash/rules/ruleparser" ) func ParseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { switch tp { + case "DOMAIN": + parsed = RC.NewDomain(payload, target) + case "DOMAIN-SUFFIX": + parsed = RC.NewDomainSuffix(payload, target) + case "DOMAIN-KEYWORD": + parsed = RC.NewDomainKeyword(payload, target) + case "GEOSITE": + parsed, parseErr = RC.NewGEOSITE(payload, target) + case "GEOIP": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) + case "IP-CIDR", "IP-CIDR6": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) + case "SRC-IP-CIDR": + parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) + case "IP-SUFFIX": + noResolve := RC.HasNoResolve(params) + parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve) + case "SRC-IP-SUFFIX": + parsed, parseErr = RC.NewIPSuffix(payload, target, true, true) + case "SRC-PORT": + parsed, parseErr = RC.NewPort(payload, target, true) + case "DST-PORT": + parsed, parseErr = RC.NewPort(payload, target, false) + case "PROCESS-NAME": + parsed, parseErr = RC.NewProcess(payload, target, true) + case "PROCESS-PATH": + parsed, parseErr = RC.NewProcess(payload, target, false) + case "NETWORK": + parsed, parseErr = RC.NewNetworkType(payload, target) + case "UID": + parsed, parseErr = RC.NewUid(payload, target) + case "IN-TYPE": + parsed, parseErr = RC.NewInType(payload, target) case "AND": - parsed, parseErr = logic.NewAND(payload, target) + parsed, parseErr = logic.NewAND(payload, target, ParseRule) case "OR": - parsed, parseErr = logic.NewOR(payload, target) + parsed, parseErr = logic.NewOR(payload, target, ParseRule) case "NOT": - parsed, parseErr = logic.NewNOT(payload, target) + parsed, parseErr = logic.NewNOT(payload, target, ParseRule) case "RULE-SET": noResolve := RC.HasNoResolve(params) - parsed, parseErr = RP.NewRuleSet(payload, target, noResolve) + parsed, parseErr = RP.NewRuleSet(payload, target, noResolve, ParseRule) case "MATCH": parsed = RC.NewMatch(target) parseErr = nil default: - parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params) + parseErr = fmt.Errorf("unsupported rule type %s", tp) } if parseErr != nil { diff --git a/rules/provider/classical_strategy.go b/rules/provider/classical_strategy.go index 297ad32fbc..2fc5209068 100644 --- a/rules/provider/classical_strategy.go +++ b/rules/provider/classical_strategy.go @@ -1,6 +1,7 @@ package provider import ( + "fmt" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/log" ) @@ -9,6 +10,7 @@ type classicalStrategy struct { rules []C.Rule count int shouldResolveIP bool + parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) } func (c *classicalStrategy) Match(metadata *C.Metadata) bool { @@ -34,7 +36,7 @@ func (c *classicalStrategy) OnUpdate(rules []string) { shouldResolveIP := false for _, rawRule := range rules { ruleType, rule, params := ruleParse(rawRule) - r, err := parseRule(ruleType, rule, "", params) + r, err := c.parse(ruleType, rule, "", params) if err != nil { log.Warnln("parse rule error:[%s]", err.Error()) } else { @@ -50,6 +52,13 @@ func (c *classicalStrategy) OnUpdate(rules []string) { c.count = len(classicalRules) } -func NewClassicalStrategy() *classicalStrategy { - return &classicalStrategy{rules: []C.Rule{}} +func NewClassicalStrategy(parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) *classicalStrategy { + return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { + switch tp { + case "MATCH": + return nil, fmt.Errorf("unsupported rule type on rule-set") + default: + return parse(tp, payload, target, params) + } + }} } diff --git a/rules/provider/parse.go b/rules/provider/parse.go index 51452d09d0..de3668022d 100644 --- a/rules/provider/parse.go +++ b/rules/provider/parse.go @@ -6,8 +6,6 @@ import ( "github.com/Dreamacro/clash/common/structure" C "github.com/Dreamacro/clash/constant" P "github.com/Dreamacro/clash/constant/provider" - RC "github.com/Dreamacro/clash/rules/common" - "github.com/Dreamacro/clash/rules/ruleparser" "time" ) @@ -19,7 +17,7 @@ type ruleProviderSchema struct { Interval int `provider:"interval,omitempty"` } -func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvider, error) { +func ParseRuleProvider(name string, mapping map[string]interface{}, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (P.RuleProvider, error) { schema := &ruleProviderSchema{} decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) if err := decoder.Decode(mapping, schema); err != nil { @@ -49,19 +47,5 @@ func ParseRuleProvider(name string, mapping map[string]interface{}) (P.RuleProvi return nil, fmt.Errorf("unsupported vehicle type: %s", schema.Type) } - return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle), nil -} - -func parseRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { - parsed, parseErr = ruleparser.ParseSameRule(tp, payload, target, params) - - if parseErr != nil { - return nil, parseErr - } - ruleExtra := &C.RuleExtra{ - Network: RC.FindNetwork(params), - SourceIPs: RC.FindSourceIPs(params), - } - parsed.SetRuleExtra(ruleExtra) - return parsed, parseErr + return NewRuleSetProvider(name, behavior, time.Duration(uint(schema.Interval))*time.Second, vehicle, parse), nil } diff --git a/rules/provider/provider.go b/rules/provider/provider.go index f2a4547d80..95c969a2ec 100644 --- a/rules/provider/provider.go +++ b/rules/provider/provider.go @@ -99,7 +99,8 @@ func (rp *ruleSetProvider) MarshalJSON() ([]byte, error) { }) } -func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle) P.RuleProvider { +func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration, vehicle P.Vehicle, + parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) P.RuleProvider { rp := &ruleSetProvider{ behavior: behavior, } @@ -112,7 +113,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration fetcher := newFetcher(name, interval, vehicle, rulesParse, onUpdate) rp.fetcher = fetcher - rp.strategy = newStrategy(behavior) + rp.strategy = newStrategy(behavior, parse) wrapper := &RuleSetProvider{ rp, @@ -123,7 +124,7 @@ func NewRuleSetProvider(name string, behavior P.RuleType, interval time.Duration return wrapper } -func newStrategy(behavior P.RuleType) ruleStrategy { +func newStrategy(behavior P.RuleType, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) ruleStrategy { switch behavior { case P.Domain: strategy := NewDomainStrategy() @@ -132,7 +133,7 @@ func newStrategy(behavior P.RuleType) ruleStrategy { strategy := NewIPCidrStrategy() return strategy case P.Classical: - strategy := NewClassicalStrategy() + strategy := NewClassicalStrategy(parse) return strategy default: return nil diff --git a/rules/provider/rule_set.go b/rules/provider/rule_set.go index 814198185c..84aaf0fb5e 100644 --- a/rules/provider/rule_set.go +++ b/rules/provider/rule_set.go @@ -47,7 +47,7 @@ func (rs *RuleSet) getProviders() P.RuleProvider { return rs.ruleProvider } -func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool) (*RuleSet, error) { +func NewRuleSet(ruleProviderName string, adapter string, noResolveIP bool, parse func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error)) (*RuleSet, error) { rp, ok := RuleProviders()[ruleProviderName] if !ok { return nil, fmt.Errorf("rule set %s not found", ruleProviderName) diff --git a/rules/ruleparser/ruleparser.go b/rules/ruleparser/ruleparser.go deleted file mode 100644 index 642943747a..0000000000 --- a/rules/ruleparser/ruleparser.go +++ /dev/null @@ -1,50 +0,0 @@ -package ruleparser - -import ( - "fmt" - C "github.com/Dreamacro/clash/constant" - RC "github.com/Dreamacro/clash/rules/common" -) - -func ParseSameRule(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) { - switch tp { - case "DOMAIN": - parsed = RC.NewDomain(payload, target) - case "DOMAIN-SUFFIX": - parsed = RC.NewDomainSuffix(payload, target) - case "DOMAIN-KEYWORD": - parsed = RC.NewDomainKeyword(payload, target) - case "GEOSITE": - parsed, parseErr = RC.NewGEOSITE(payload, target) - case "GEOIP": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewGEOIP(payload, target, noResolve) - case "IP-CIDR", "IP-CIDR6": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRNoResolve(noResolve)) - case "SRC-IP-CIDR": - parsed, parseErr = RC.NewIPCIDR(payload, target, RC.WithIPCIDRSourceIP(true), RC.WithIPCIDRNoResolve(true)) - case "IP-SUFFIX": - noResolve := RC.HasNoResolve(params) - parsed, parseErr = RC.NewIPSuffix(payload, target, false, noResolve) - case "SRC-IP-SUFFIX": - parsed, parseErr = RC.NewIPSuffix(payload, target, true, true) - case "SRC-PORT": - parsed, parseErr = RC.NewPort(payload, target, true) - case "DST-PORT": - parsed, parseErr = RC.NewPort(payload, target, false) - case "PROCESS-NAME": - parsed, parseErr = RC.NewProcess(payload, target, true) - case "PROCESS-PATH": - parsed, parseErr = RC.NewProcess(payload, target, false) - case "NETWORK": - parsed, parseErr = RC.NewNetworkType(payload, target) - case "UID": - parsed, parseErr = RC.NewUid(payload, target) - case "IN-TYPE": - parsed, parseErr = RC.NewInType(payload, target) - default: - parseErr = fmt.Errorf("unsupported rule type %s", tp) - } - return -} From 7d0490410961010edc9fb056c719f579e06b0cfd Mon Sep 17 00:00:00 2001 From: Skyxim Date: Fri, 10 Jun 2022 14:29:19 +0800 Subject: [PATCH 04/14] fix: leak dns when domain in hosts list --- dns/middleware.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dns/middleware.go b/dns/middleware.go index 415bcd1b47..0bfc4977b8 100644 --- a/dns/middleware.go +++ b/dns/middleware.go @@ -46,11 +46,11 @@ func withHosts(hosts *trie.DomainTrie[netip.Addr], mapping *cache.LruCache[netip rr.A = ip.AsSlice() msg.Answer = []D.RR{rr} - } else if ip.Is6() && q.Qtype == D.TypeAAAA { + } else if q.Qtype == D.TypeAAAA { rr := &D.AAAA{} rr.Hdr = D.RR_Header{Name: q.Name, Rrtype: D.TypeAAAA, Class: D.ClassINET, Ttl: 10} - rr.AAAA = ip.AsSlice() - + ip := ip.As16() + rr.AAAA = ip[:] msg.Answer = []D.RR{rr} } else { return next(ctx, r) From d1decb8e58c3bd05b44851cd3faf3cd83e04908a Mon Sep 17 00:00:00 2001 From: bash99 Date: Wed, 15 Jun 2022 14:00:05 +0800 Subject: [PATCH 05/14] Update README.md add permissions for systemctl services clash-dashboard change to updated one --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d3834c0c12..acdc0e697a 100644 --- a/README.md +++ b/README.md @@ -251,8 +251,8 @@ User=clash-meta Group=clash-meta LimitNPROC=500 LimitNOFILE=1000000 -CapabilityBoundingSet=cap_net_admin -AmbientCapabilities=cap_net_admin +CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE Restart=always ExecStartPre=/usr/bin/sleep 1s ExecStart=/usr/local/bin/Clash-Meta -d /etc/Clash-Meta @@ -274,7 +274,7 @@ $ systemctl start Clash-Meta Clash add field `Process` to `Metadata` and prepare to get process name for Restful API `GET /connections`. -To display process name in GUI please use [Dashboard For Meta](https://github.com/Clash-Mini/Dashboard). +To display process name in GUI please use [Dashboard For Meta](https://github.com/MetaCubeX/clash-dashboard). ![img.png](https://github.com/Clash-Mini/Dashboard/raw/master/View/Dashboard-Process.png) From f968d0cb82c220c677685f9114b723395c11d66f Mon Sep 17 00:00:00 2001 From: Skimmle <117449739+Skimmle@users.noreply.github.com> Date: Sat, 26 Nov 2022 20:11:43 +0800 Subject: [PATCH 06/14] chore: update github action --- .github/workflows/prerelease.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index daaba026fc..a55eb556d9 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -1,5 +1,6 @@ name: Prerelease on: + workflow_dispatch: push: branches: - Alpha From 9e20f9c26aa02c25799ba334211efb68494531d9 Mon Sep 17 00:00:00 2001 From: metacubex Date: Mon, 28 Nov 2022 20:33:10 +0800 Subject: [PATCH 07/14] chore: update dependencies --- .github/workflows/prerelease.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index a55eb556d9..927d99aec0 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -47,14 +47,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: @@ -64,7 +66,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 From dfbe09860f661fc2cfa476786fe2dccec5e36c16 Mon Sep 17 00:00:00 2001 From: tdjnodj <108401163+tdjnodj@users.noreply.github.com> Date: Sat, 3 Dec 2022 17:17:08 +0800 Subject: [PATCH 08/14] Update README.md --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index acdc0e697a..f95d4572bc 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`. From a1d0f221322c8e2b64503ea13bee63f7e150c5eb Mon Sep 17 00:00:00 2001 From: Rasphino Date: Sat, 7 Jan 2023 23:38:32 +0800 Subject: [PATCH 09/14] fix: update flake.nix hash --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index c372ff4b80..88a6eacbc0 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" ]; From 7c5d137f03d992804ec723dd8ab586e1854528d5 Mon Sep 17 00:00:00 2001 From: 3andero <3andero@github.com> Date: Sun, 8 Jan 2023 22:09:58 -0800 Subject: [PATCH 10/14] feat: impl shadow-tls as a shadowsocks plugin not yet tested --- adapter/outbound/shadowsocks.go | 33 ++++++-- transport/shadowtls/shadowtls.go | 137 +++++++++++++++++++++++++++++++ transport/trojan/trojan.go | 6 +- 3 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 transport/shadowtls/shadowtls.go diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index c318d26320..003d86110c 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -10,11 +10,12 @@ 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" 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 +28,10 @@ type ShadowSocks struct { option *ShadowSocksOption // obfs - obfsMode string - obfsOption *simpleObfsOption - v2rayOption *v2rayObfs.Option + obfsMode string + obfsOption *simpleObfsOption + v2rayOption *v2rayObfs.Option + shadowTLSOption *shadowTLSOption } type ShadowSocksOption struct { @@ -61,6 +63,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 +82,8 @@ 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: + c = shadowtls.NewShadowTls(c, ss.shadowTLSOption.Host, ss.shadowTLSOption.Password) } if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { return ss.method.DialConn(c, M.ParseSocksaddr(uot.UOTMagicAddress+":443")) @@ -157,6 +166,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 +202,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 +222,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/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go new file mode 100644 index 0000000000..7f2adda7ec --- /dev/null +++ b/transport/shadowtls/shadowtls.go @@ -0,0 +1,137 @@ +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 << 14 // 2 ** 14 == 16 * 1024 + MODE string = "shadow-tls" +) + +// TLSObfs is shadowsocks tls simple-obfs implementation +type ShadowTls struct { + net.Conn + server string + password []byte + remain int + firstRequest bool +} + +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 = io.ReadFull(h.Conn, b) + h.hasher.Write(b) + return +} + +const TLS_HEADER_LEN int = 5 + +func (to *ShadowTls) read(b []byte) (int, error) { + buf := pool.Get(TLS_HEADER_LEN) + _, err := io.ReadFull(to.Conn, buf) + if err != nil { + return 0, err + } + if buf[0] != 0x17 || buf[1] != 0x3 || buf[2] != 0x3 { + return 0, fmt.Errorf("invalid shadowtls header %s", 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]) + to.remain -= n + return n, err + } + + 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, err + } + } + return length, nil +} + +const HASH_LEN int = 8 + +func (s *ShadowTls) write(b []byte) (int, error) { + var hashVal []byte + if s.firstRequest { + tlsConn := trojan.New(&trojan.Option{ServerName: s.server}) + hashedConn := newHashedStream(s.Conn, s.password) + if _, err := tlsConn.StreamConn(hashedConn); err != nil { + return 0, err + } + hashVal = hashedConn.hasher.Sum(nil)[:HASH_LEN] + 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()) + return len(b), err +} + +// NewShadowTls return a ShadowTls +func NewShadowTls(conn net.Conn, server string, password string) net.Conn { + return &ShadowTls{ + Conn: conn, + password: []byte(password), + server: server, + firstRequest: true, + } +} diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 937b5f9155..4ffb9c48b8 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -8,12 +8,13 @@ import ( "encoding/hex" "errors" "fmt" - tlsC "github.com/Dreamacro/clash/component/tls" "io" "net" "net/http" "sync" + tlsC "github.com/Dreamacro/clash/component/tls" + "github.com/Dreamacro/clash/common/pool" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/transport/socks5" @@ -117,9 +118,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 7e05d5c34975e0335b578b2e30ee7f735344583d Mon Sep 17 00:00:00 2001 From: 3andero <3andero@github.com> Date: Mon, 9 Jan 2023 12:27:11 -0800 Subject: [PATCH 11/14] fix: several bugs --- adapter/outbound/shadowsocks.go | 2 +- transport/shadowtls/shadowtls.go | 29 +++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index 003d86110c..b03b4134a6 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -205,7 +205,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { } else if option.Plugin == shadowtls.MODE { obfsMode = shadowtls.MODE shadowTLSOpt = &shadowTLSOption{} - if err := decoder.Decode(option.PluginOpts, &shadowTLSOpt); err != nil { + if err := decoder.Decode(option.PluginOpts, shadowTLSOpt); err != nil { return nil, fmt.Errorf("ss %s initialize shadow-tls-plugin error: %w", addr, err) } } diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index 7f2adda7ec..6d1b093cdc 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -14,8 +14,10 @@ import ( ) const ( - chunkSize = 1 << 14 // 2 ** 14 == 16 * 1024 - MODE string = "shadow-tls" + chunkSize = 1 << 13 + MODE string = "shadow-tls" + HASH_LEN int = 8 + TLS_HEADER_LEN int = 5 ) // TLSObfs is shadowsocks tls simple-obfs implementation @@ -40,21 +42,19 @@ func newHashedStream(conn net.Conn, password []byte) HashedConn { } func (h HashedConn) Read(b []byte) (n int, err error) { - n, err = io.ReadFull(h.Conn, b) - h.hasher.Write(b) + n, err = h.Conn.Read(b) + h.hasher.Write(b[:n]) return } -const TLS_HEADER_LEN int = 5 - func (to *ShadowTls) read(b []byte) (int, error) { buf := pool.Get(TLS_HEADER_LEN) _, err := io.ReadFull(to.Conn, buf) if err != nil { - return 0, err + 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 %s", buf) + return 0, fmt.Errorf("invalid shadowtls header %v", buf) } length := int(binary.BigEndian.Uint16(buf[3:])) pool.Put(buf) @@ -79,8 +79,11 @@ func (to *ShadowTls) Read(b []byte) (int, error) { } 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, err + return n, nil } return to.read(b) @@ -96,21 +99,19 @@ func (to *ShadowTls) Write(b []byte) (int, error) { n, err := to.write(b[i:end]) if err != nil { - return n, err + return n, fmt.Errorf("shadowtls Write failed with %w, i=%d, end=%d, n=%d", err, i, end, n) } } return length, nil } -const HASH_LEN int = 8 - func (s *ShadowTls) write(b []byte) (int, error) { var hashVal []byte if s.firstRequest { - tlsConn := trojan.New(&trojan.Option{ServerName: s.server}) + tlsConn := trojan.New(&trojan.Option{ServerName: s.server, SkipCertVerify: false}) hashedConn := newHashedStream(s.Conn, s.password) if _, err := tlsConn.StreamConn(hashedConn); err != nil { - return 0, err + return 0, fmt.Errorf("tls connect failed with %w", err) } hashVal = hashedConn.hasher.Sum(nil)[:HASH_LEN] s.firstRequest = false From bedc2406c6ff317c1356041ee7da6d42feea1c43 Mon Sep 17 00:00:00 2001 From: 3andero <3andero@github.com> Date: Mon, 9 Jan 2023 21:45:02 -0800 Subject: [PATCH 12/14] fix: several comments 1. constant naming 2. import format 3. remove ephemeral trojan instance and add it as a shadowsocks struct field 4. incorrect numOfBytesWritten in ShadowTls::write and obfs::tls::write 5. incomplete write in ShadowTls::write and obfs::tls::write --- adapter/outbound/shadowsocks.go | 13 ++++++++---- transport/shadowtls/shadowtls.go | 34 ++++++++++++++++++++------------ transport/simple-obfs/tls.go | 13 ++++++++++-- transport/trojan/trojan.go | 4 +--- 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/adapter/outbound/shadowsocks.go b/adapter/outbound/shadowsocks.go index b03b4134a6..736bb5bd0b 100644 --- a/adapter/outbound/shadowsocks.go +++ b/adapter/outbound/shadowsocks.go @@ -13,6 +13,7 @@ import ( "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" @@ -32,6 +33,7 @@ type ShadowSocks struct { obfsOption *simpleObfsOption v2rayOption *v2rayObfs.Option shadowTLSOption *shadowTLSOption + tlsConnector *trojan.Trojan } type ShadowSocksOption struct { @@ -82,8 +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: - c = shadowtls.NewShadowTls(c, ss.shadowTLSOption.Host, ss.shadowTLSOption.Password) + 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")) @@ -202,8 +207,8 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) { v2rayOption.TLS = true v2rayOption.SkipCertVerify = opts.SkipCertVerify } - } else if option.Plugin == shadowtls.MODE { - obfsMode = shadowtls.MODE + } 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) diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index 6d1b093cdc..530cf2b2dc 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -14,19 +14,19 @@ import ( ) const ( - chunkSize = 1 << 13 - MODE string = "shadow-tls" - HASH_LEN int = 8 - TLS_HEADER_LEN int = 5 + 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 - server string password []byte remain int firstRequest bool + tlsConnector *trojan.Trojan } type HashedConn struct { @@ -48,7 +48,7 @@ func (h HashedConn) Read(b []byte) (n int, err error) { } func (to *ShadowTls) read(b []byte) (int, error) { - buf := pool.Get(TLS_HEADER_LEN) + buf := pool.Get(tlsHeaderLen) _, err := io.ReadFull(to.Conn, buf) if err != nil { return 0, fmt.Errorf("shadowtls read failed %w", err) @@ -108,12 +108,11 @@ func (to *ShadowTls) Write(b []byte) (int, error) { func (s *ShadowTls) write(b []byte) (int, error) { var hashVal []byte if s.firstRequest { - tlsConn := trojan.New(&trojan.Option{ServerName: s.server, SkipCertVerify: false}) hashedConn := newHashedStream(s.Conn, s.password) - if _, err := tlsConn.StreamConn(hashedConn); err != nil { + if _, err := s.tlsConnector.StreamConn(hashedConn); err != nil { return 0, fmt.Errorf("tls connect failed with %w", err) } - hashVal = hashedConn.hasher.Sum(nil)[:HASH_LEN] + hashVal = hashedConn.hasher.Sum(nil)[:hashLen] s.firstRequest = false } @@ -123,16 +122,25 @@ func (s *ShadowTls) write(b []byte) (int, error) { binary.Write(buf, binary.BigEndian, uint16(len(b)+len(hashVal))) buf.Write(hashVal) buf.Write(b) - _, err := s.Conn.Write(buf.Bytes()) - return len(b), err + remain := buf.Len() + for remain > 0 { + n, err := s.Conn.Write(buf.Bytes()) + if err != nil { + // return 0 because errors occur here make the + // whole situation irrecoverable + return 0, err + } + remain -= n + } + return len(b), nil } // NewShadowTls return a ShadowTls -func NewShadowTls(conn net.Conn, server string, password string) net.Conn { +func NewShadowTls(conn net.Conn, password string, tlsConnector *trojan.Trojan) net.Conn { return &ShadowTls{ Conn: conn, password: []byte(password), - server: server, firstRequest: true, + tlsConnector: tlsConnector, } } diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 1c609c15a8..fd4bdae1bf 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -108,8 +108,17 @@ func (to *TLSObfs) write(b []byte) (int, error) { buf.Write([]byte{0x17, 0x03, 0x03}) binary.Write(buf, binary.BigEndian, uint16(len(b))) buf.Write(b) - _, err := to.Conn.Write(buf.Bytes()) - return len(b), err + remain := buf.Len() + for remain > 0 { + n, err := to.Conn.Write(buf.Bytes()) + if err != nil { + // return 0 because errors occur here make the + // whole situation irrecoverable + return 0, err + } + remain -= n + } + return len(b), nil } // NewTLSObfs return a SimpleObfs diff --git a/transport/trojan/trojan.go b/transport/trojan/trojan.go index 4ffb9c48b8..86de2f65a5 100644 --- a/transport/trojan/trojan.go +++ b/transport/trojan/trojan.go @@ -13,14 +13,12 @@ import ( "net/http" "sync" - tlsC "github.com/Dreamacro/clash/component/tls" - "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" ) From 81011a29668154ccfd615624111d51249a2d3624 Mon Sep 17 00:00:00 2001 From: 3andero <3andero@github.com> Date: Mon, 9 Jan 2023 22:04:56 -0800 Subject: [PATCH 13/14] fix: hot fix --- transport/shadowtls/shadowtls.go | 9 +++++---- transport/simple-obfs/tls.go | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index 530cf2b2dc..e3e5db91c4 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -122,15 +122,16 @@ func (s *ShadowTls) write(b []byte) (int, error) { binary.Write(buf, binary.BigEndian, uint16(len(b)+len(hashVal))) buf.Write(hashVal) buf.Write(b) - remain := buf.Len() - for remain > 0 { - n, err := s.Conn.Write(buf.Bytes()) + written := 0 + bufBytes := buf.Bytes() + for written < len(bufBytes) { + n, err := s.Conn.Write(bufBytes[written:]) if err != nil { // return 0 because errors occur here make the // whole situation irrecoverable return 0, err } - remain -= n + written += n } return len(b), nil } diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index fd4bdae1bf..2b7661280a 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -108,15 +108,16 @@ func (to *TLSObfs) write(b []byte) (int, error) { buf.Write([]byte{0x17, 0x03, 0x03}) binary.Write(buf, binary.BigEndian, uint16(len(b))) buf.Write(b) - remain := buf.Len() - for remain > 0 { - n, err := to.Conn.Write(buf.Bytes()) + written := 0 + bufBytes := buf.Bytes() + for written < len(bufBytes) { + n, err := to.Conn.Write(bufBytes[written:]) if err != nil { // return 0 because errors occur here make the // whole situation irrecoverable return 0, err } - remain -= n + written += n } return len(b), nil } From 5badbeaae6451532b02e7e6bc17bc3364478b737 Mon Sep 17 00:00:00 2001 From: 3andero <3andero@github.com> Date: Mon, 9 Jan 2023 22:16:36 -0800 Subject: [PATCH 14/14] fix: clean up --- transport/shadowtls/shadowtls.go | 15 +++++---------- transport/simple-obfs/tls.go | 15 +++++---------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/transport/shadowtls/shadowtls.go b/transport/shadowtls/shadowtls.go index e3e5db91c4..4a0db6a9e9 100644 --- a/transport/shadowtls/shadowtls.go +++ b/transport/shadowtls/shadowtls.go @@ -122,16 +122,11 @@ func (s *ShadowTls) write(b []byte) (int, error) { binary.Write(buf, binary.BigEndian, uint16(len(b)+len(hashVal))) buf.Write(hashVal) buf.Write(b) - written := 0 - bufBytes := buf.Bytes() - for written < len(bufBytes) { - n, err := s.Conn.Write(bufBytes[written:]) - if err != nil { - // return 0 because errors occur here make the - // whole situation irrecoverable - return 0, err - } - written += n + _, 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 } diff --git a/transport/simple-obfs/tls.go b/transport/simple-obfs/tls.go index 2b7661280a..fed8a48331 100644 --- a/transport/simple-obfs/tls.go +++ b/transport/simple-obfs/tls.go @@ -108,16 +108,11 @@ func (to *TLSObfs) write(b []byte) (int, error) { buf.Write([]byte{0x17, 0x03, 0x03}) binary.Write(buf, binary.BigEndian, uint16(len(b))) buf.Write(b) - written := 0 - bufBytes := buf.Bytes() - for written < len(bufBytes) { - n, err := to.Conn.Write(bufBytes[written:]) - if err != nil { - // return 0 because errors occur here make the - // whole situation irrecoverable - return 0, err - } - written += n + _, err := to.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 }