diff --git a/adapter/outboundgroup/common.go b/adapter/outboundgroup/common.go index 758c7801..2c645af1 100644 --- a/adapter/outboundgroup/common.go +++ b/adapter/outboundgroup/common.go @@ -3,6 +3,8 @@ package outboundgroup import ( "time" + "github.com/Dreamacro/clash/adapter" + "github.com/Dreamacro/clash/adapter/outbound" C "github.com/Dreamacro/clash/constant" "github.com/Dreamacro/clash/constant/provider" ) @@ -11,14 +13,19 @@ const ( defaultGetProxiesDuration = time.Second * 5 ) +var defaultRejectProxy = adapter.NewProxy(outbound.NewReject()) + func getProvidersProxies(providers []provider.ProxyProvider, touch bool) []C.Proxy { proxies := []C.Proxy{} - for _, provider := range providers { + for _, pd := range providers { if touch { - proxies = append(proxies, provider.ProxiesWithTouch()...) + proxies = append(proxies, pd.ProxiesWithTouch()...) } else { - proxies = append(proxies, provider.Proxies()...) + proxies = append(proxies, pd.Proxies()...) } } + if len(proxies) == 0 { + proxies = append(proxies, defaultRejectProxy) + } return proxies } diff --git a/adapter/outboundgroup/parser.go b/adapter/outboundgroup/parser.go index ad83231b..f8028595 100644 --- a/adapter/outboundgroup/parser.go +++ b/adapter/outboundgroup/parser.go @@ -78,30 +78,18 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide return nil, errDuplicateProvider } - // select don't need health check - if groupOption.Type == "select" || groupOption.Type == "relay" { - hc := provider.NewHealthCheck(ps, "", 0, true) - pd, err := provider.NewCompatibleProvider(groupName, ps, hc) - if err != nil { - return nil, err - } - - providers = append(providers, pd) - providersMap[groupName] = pd - } else { - if groupOption.URL == "" || groupOption.Interval == 0 { - return nil, errMissHealthCheck - } - - hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy) - pd, err := provider.NewCompatibleProvider(groupName, ps, hc) - if err != nil { - return nil, err - } - - providers = append(providers, pd) - providersMap[groupName] = pd + hc, err := newHealthCheck(ps, groupOption) + if err != nil { + return nil, err } + + pd, err := provider.NewCompatibleProvider(groupName, ps, hc) + if err != nil { + return nil, err + } + + providers = append(providers, pd) + providersMap[groupName] = pd } if len(groupOption.Use) != 0 { @@ -109,7 +97,12 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide if err != nil { return nil, err } - providers = append(providers, list...) + + if groupOption.Type == "fallback" { + providers = append(list, providers...) + } else { + providers = append(providers, list...) + } } var group C.ProxyAdapter @@ -163,22 +156,18 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm } if filterRegx != nil { - var hc *provider.HealthCheck - if groupOption.Type == "select" || groupOption.Type == "relay" { - hc = provider.NewHealthCheck([]C.Proxy{}, "", 0, true) - } else { - if groupOption.URL == "" || groupOption.Interval == 0 { - return nil, errMissHealthCheck - } - hc = provider.NewHealthCheck([]C.Proxy{}, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy) + hc, err := newHealthCheck([]C.Proxy{}, groupOption) + if err != nil { + return nil, err } - if _, ok = mapping[groupName]; ok { - groupName += "->" + p.Name() + gName := groupName + if _, ok = mapping[gName]; ok { + gName = groupName + " -> " + p.Name() } pd := p.(*provider.ProxySetProvider) - p = provider.NewProxyFilterProvider(groupName, pd, hc, filterRegx) + p = provider.NewProxyFilterProvider(gName, pd, hc, filterRegx) pd.RegisterProvidersInUse(p) } @@ -186,3 +175,18 @@ func getProviders(mapping map[string]types.ProxyProvider, groupOption *GroupComm } return ps, nil } + +func newHealthCheck(ps []C.Proxy, groupOption *GroupCommonOption) (*provider.HealthCheck, error) { + var hc *provider.HealthCheck + + // select don't need health check + if groupOption.Type == "select" || groupOption.Type == "relay" { + hc = provider.NewHealthCheck(ps, "", 0, true) + } else { + if groupOption.URL == "" || groupOption.Interval == 0 { + return nil, errMissHealthCheck + } + hc = provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.Interval), groupOption.Lazy) + } + return hc, nil +} diff --git a/adapter/outboundgroup/selector.go b/adapter/outboundgroup/selector.go index da71022f..e2d91839 100644 --- a/adapter/outboundgroup/selector.go +++ b/adapter/outboundgroup/selector.go @@ -98,7 +98,11 @@ func (s *Selector) selectedProxy(touch bool) C.Proxy { } func NewSelector(option *GroupCommonOption, providers []provider.ProxyProvider) *Selector { - selected := providers[0].Proxies()[0].Name() + selected := "REJECT" + if len(providers) != 0 && len(providers[0].Proxies()) != 0 { + selected = providers[0].Proxies()[0].Name() + } + return &Selector{ Base: outbound.NewBase(outbound.BaseOption{ Name: option.Name, diff --git a/adapter/parser.go b/adapter/parser.go index 1c4bfa39..3fe87713 100644 --- a/adapter/parser.go +++ b/adapter/parser.go @@ -10,7 +10,7 @@ import ( func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) { decoder := structure.NewDecoder(structure.Option{TagName: "proxy", WeaklyTypedInput: true}) - proxyType, existType := mapping["type"].(string) + proxyType, existType := mapping["type"] if !existType { return nil, fmt.Errorf("missing type") } @@ -19,7 +19,7 @@ func ParseProxy(mapping map[string]any, forceCertVerify bool) (C.Proxy, error) { proxy C.ProxyAdapter err error ) - switch proxyType { + switch proxyType.(string) { case "ss": ssOption := &outbound.ShadowSocksOption{} err = decoder.Decode(mapping, ssOption) diff --git a/adapter/provider/healthcheck.go b/adapter/provider/healthcheck.go index 0664771f..eb7baf34 100644 --- a/adapter/provider/healthcheck.go +++ b/adapter/provider/healthcheck.go @@ -25,10 +25,16 @@ type HealthCheck struct { interval uint lazy bool lastTouch *atomic.Int64 + running *atomic.Bool done chan struct{} } func (hc *HealthCheck) process() { + if hc.running.Load() { + return + } + hc.running.Store(true) + ticker := time.NewTicker(time.Duration(hc.interval) * time.Second) go func() { @@ -84,6 +90,10 @@ func (hc *HealthCheck) check() { } func (hc *HealthCheck) close() { + if !hc.running.Load() { + return + } + hc.running.Store(false) hc.done <- struct{}{} } @@ -94,6 +104,7 @@ func NewHealthCheck(proxies []C.Proxy, url string, interval uint, lazy bool) *He interval: interval, lazy: lazy, lastTouch: atomic.NewInt64(0), + running: atomic.NewBool(false), done: make(chan struct{}, 1), } } diff --git a/adapter/provider/provider.go b/adapter/provider/provider.go index f88210ce..42cc4295 100644 --- a/adapter/provider/provider.go +++ b/adapter/provider/provider.go @@ -234,7 +234,9 @@ func (pf *proxyFilterProvider) HealthCheck() { } func (pf *proxyFilterProvider) Update() error { - var proxies []C.Proxy + pf.healthCheck.close() + + proxies := []C.Proxy{} if pf.filter != nil { for _, proxy := range pf.psd.Proxies() { if !pf.filter.MatchString(proxy.Name()) { @@ -248,6 +250,10 @@ func (pf *proxyFilterProvider) Update() error { pf.proxies = proxies pf.healthCheck.setProxy(proxies) + + if len(proxies) != 0 && pf.healthCheck.auto() { + go pf.healthCheck.process() + } return nil } @@ -286,10 +292,6 @@ func NewProxyFilterProvider(name string, psd *ProxySetProvider, hc *HealthCheck, _ = pd.Update() - if hc.auto() { - go hc.process() - } - wrapper := &ProxyFilterProvider{pd} runtime.SetFinalizer(wrapper, stopProxyFilterProvider) return wrapper diff --git a/config/config.go b/config/config.go index e70315d2..be290006 100644 --- a/config/config.go +++ b/config/config.go @@ -415,11 +415,11 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[ // keep the original order of ProxyGroups in config file for idx, mapping := range groupsConfig { - groupName, existName := mapping["name"].(string) + groupName, existName := mapping["name"] if !existName { return nil, nil, fmt.Errorf("proxy group %d: missing name", idx) } - proxyList = append(proxyList, groupName) + proxyList = append(proxyList, groupName.(string)) } // check if any loop exists and sort the ProxyGroups