github.com/yaling888/clash@v1.53.0/adapter/provider/provider.go (about)

     1  package provider
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/md5"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"runtime"
    11  	"sync"
    12  	"time"
    13  
    14  	regexp "github.com/dlclark/regexp2"
    15  	"github.com/phuslu/log"
    16  	"github.com/samber/lo"
    17  	"gopkg.in/yaml.v3"
    18  
    19  	"github.com/yaling888/clash/adapter"
    20  	"github.com/yaling888/clash/adapter/outbound"
    21  	"github.com/yaling888/clash/common/convert"
    22  	"github.com/yaling888/clash/common/singledo"
    23  	"github.com/yaling888/clash/common/structure"
    24  	"github.com/yaling888/clash/component/resolver"
    25  	C "github.com/yaling888/clash/constant"
    26  	types "github.com/yaling888/clash/constant/provider"
    27  	"github.com/yaling888/clash/tunnel/statistic"
    28  )
    29  
    30  var (
    31  	group  = &singledo.Group[[]C.Proxy]{}
    32  	reject = adapter.NewProxy(outbound.NewReject())
    33  )
    34  
    35  const (
    36  	ReservedName = "default"
    37  )
    38  
    39  type ProxySchema struct {
    40  	Proxies []C.RawProxy `yaml:"proxies"`
    41  }
    42  
    43  var _ types.ProxyProvider = (*ProxySetProvider)(nil)
    44  
    45  type ProxySetProvider struct {
    46  	healthCheck *HealthCheck
    47  	proxies     []C.Proxy
    48  	groupNames  []string
    49  	globalFCV   bool
    50  	tmCheck     *time.Timer
    51  
    52  	mux  sync.Mutex // guards following fields
    53  	hash [16]byte   // config file hash
    54  	*fetcher[[]C.Proxy]
    55  }
    56  
    57  func (pp *ProxySetProvider) MarshalJSON() ([]byte, error) {
    58  	return json.Marshal(map[string]any{
    59  		"name":         pp.Name(),
    60  		"type":         pp.Type().String(),
    61  		"vehicleType":  pp.VehicleType().String(),
    62  		"proxies":      pp.Proxies(),
    63  		"subscription": pp.subscription(),
    64  		"updatedAt":    pp.updatedAt,
    65  	})
    66  }
    67  
    68  func (pp *ProxySetProvider) Name() string {
    69  	return pp.name
    70  }
    71  
    72  func (pp *ProxySetProvider) HealthCheck() {
    73  	pp.healthCheck.checkAll()
    74  }
    75  
    76  func (pp *ProxySetProvider) Update() error {
    77  	defer runtime.GC()
    78  
    79  	pp.mux.Lock()
    80  
    81  	buf, err := os.ReadFile(C.Path.Config())
    82  	if err != nil {
    83  		pp.mux.Unlock()
    84  		return err
    85  	}
    86  
    87  	hash := md5.Sum(buf)
    88  	if !bytes.Equal(pp.hash[:], hash[:]) {
    89  		pp.mux.Unlock()
    90  
    91  		rawCfg := struct {
    92  			ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
    93  		}{}
    94  
    95  		if err = yaml.Unmarshal(buf, &rawCfg); err != nil {
    96  			return err
    97  		}
    98  
    99  		decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true})
   100  
   101  		schema := &proxyProviderSchema{ForceCertVerify: pp.globalFCV}
   102  
   103  		for name, mapping := range rawCfg.ProxyProvider {
   104  			if name == pp.name {
   105  				if err := decoder.Decode(mapping, schema); err != nil {
   106  					return err
   107  				}
   108  				break
   109  			}
   110  		}
   111  
   112  		vehicle, err := newVehicle(schema)
   113  		if err != nil {
   114  			return err
   115  		}
   116  
   117  		option := adapter.ProxyOption{
   118  			ForceCertVerify: schema.ForceCertVerify,
   119  			ForceUDP:        schema.UDP,
   120  			DisableUDP:      schema.DisableUDP,
   121  			DisableDNS:      schema.DisableDNS,
   122  			RandomHost:      schema.RandomHost,
   123  			PrefixName:      schema.PrefixName,
   124  			AutoCipher:      true,
   125  		}
   126  
   127  		pp.mux.Lock()
   128  
   129  		_, err = newOrUpdateFetcher(pp.name, schema.Interval, schema.Filter, vehicle, nil, pp.globalFCV, option, pp)
   130  		if err != nil {
   131  			pp.mux.Unlock()
   132  			return err
   133  		}
   134  
   135  		pp.hash = hash
   136  	}
   137  	pp.mux.Unlock()
   138  
   139  	elm, same, err := pp.fetcher.Update()
   140  	if err != nil {
   141  		return err
   142  	}
   143  	if same {
   144  		log.Info().Str("name", pp.Name()).Msg("[Provider] proxies doesn't change")
   145  		return nil
   146  	}
   147  
   148  	pp.onUpdate(elm)
   149  	return nil
   150  }
   151  
   152  func (pp *ProxySetProvider) Initial() error {
   153  	proxies, err := pp.fetcher.Initial()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	if proxies == nil {
   159  		name := pp.name + "-" + "Reject"
   160  		proxies = append(proxies, adapter.NewProxy(outbound.NewRejectByName(name)))
   161  	}
   162  	pp.onUpdate(proxies)
   163  	return nil
   164  }
   165  
   166  func (pp *ProxySetProvider) Type() types.ProviderType {
   167  	return types.Proxy
   168  }
   169  
   170  func (pp *ProxySetProvider) Proxies() []C.Proxy {
   171  	return pp.proxies
   172  }
   173  
   174  func (pp *ProxySetProvider) Touch() {
   175  	pp.healthCheck.touch()
   176  }
   177  
   178  func (pp *ProxySetProvider) Finalize() {
   179  	if pp.tmCheck != nil {
   180  		pp.tmCheck.Stop()
   181  		pp.tmCheck = nil
   182  	}
   183  	pp.healthCheck.close()
   184  	_ = pp.fetcher.Destroy()
   185  }
   186  
   187  func (pp *ProxySetProvider) setProxies(proxies []C.Proxy) {
   188  	old := pp.proxies
   189  	pp.proxies = proxies
   190  	pp.healthCheck.setProxy(proxies)
   191  
   192  	for _, name := range pp.groupNames {
   193  		group.Forget(name)
   194  	}
   195  
   196  	if len(old) != 0 {
   197  		names := lo.Map(old, func(item C.Proxy, _ int) string {
   198  			p := item.(C.ProxyAdapter)
   199  			name := p.Name()
   200  			go func() {
   201  				p.Cleanup()
   202  				resolver.RemoveCache(name)
   203  			}()
   204  			return name
   205  		})
   206  		statistic.DefaultManager.KickOut(names...)
   207  		go pp.healthCheck.checkAll()
   208  	} else {
   209  		pp.tmCheck = time.AfterFunc(45*time.Second, func() {
   210  			pp.healthCheck.checkAll()
   211  			pp.tmCheck = nil
   212  		})
   213  	}
   214  }
   215  
   216  func (pp *ProxySetProvider) addGroupName(name string) {
   217  	pp.groupNames = append(pp.groupNames, name)
   218  }
   219  
   220  func (pp *ProxySetProvider) subscription() *Subscription {
   221  	if s, ok := pp.vehicle.(interface{ Subscription() *Subscription }); ok {
   222  		return s.Subscription()
   223  	}
   224  	return nil
   225  }
   226  
   227  func NewProxySetProvider(
   228  	name string,
   229  	interval time.Duration,
   230  	filter string,
   231  	vehicle types.Vehicle,
   232  	hc *HealthCheck,
   233  	globalForceCertVerify bool,
   234  	option adapter.ProxyOption,
   235  ) (*ProxySetProvider, error) {
   236  	return newOrUpdateFetcher(name, interval, filter, vehicle, hc, globalForceCertVerify, option, nil)
   237  }
   238  
   239  func newOrUpdateFetcher(
   240  	name string,
   241  	interval time.Duration,
   242  	filter string,
   243  	vehicle types.Vehicle,
   244  	hc *HealthCheck,
   245  	globalForceCertVerify bool,
   246  	option adapter.ProxyOption,
   247  	pd *ProxySetProvider,
   248  ) (*ProxySetProvider, error) {
   249  	var filterReg *regexp.Regexp
   250  	if filter != "" {
   251  		f, err := regexp.Compile(filter, 0)
   252  		if err != nil {
   253  			return nil, fmt.Errorf("invalid filter regex: %w", err)
   254  		}
   255  		filterReg = f
   256  	}
   257  
   258  	if pd == nil {
   259  		if hc.auto() {
   260  			go hc.process()
   261  		}
   262  
   263  		pd = &ProxySetProvider{
   264  			proxies:     []C.Proxy{},
   265  			healthCheck: hc,
   266  			globalFCV:   globalForceCertVerify,
   267  		}
   268  	} else {
   269  		_ = pd.fetcher.Destroy()
   270  	}
   271  
   272  	pd.fetcher = newFetcher[[]C.Proxy](
   273  		name,
   274  		interval,
   275  		vehicle,
   276  		proxiesParseAndFilter(filterReg, option),
   277  		proxiesOnUpdate(pd),
   278  	)
   279  
   280  	return pd, nil
   281  }
   282  
   283  var _ types.ProxyProvider = (*CompatibleProvider)(nil)
   284  
   285  type CompatibleProvider struct {
   286  	name        string
   287  	proxies     []C.Proxy
   288  	providers   []types.ProxyProvider
   289  	healthCheck *HealthCheck
   290  	filterRegx  *regexp.Regexp
   291  	tmCheck     *time.Timer
   292  
   293  	hasProxy    bool
   294  	hasProvider bool
   295  }
   296  
   297  func (cp *CompatibleProvider) MarshalJSON() ([]byte, error) {
   298  	return json.Marshal(map[string]any{
   299  		"name":        cp.Name(),
   300  		"type":        cp.Type().String(),
   301  		"vehicleType": cp.VehicleType().String(),
   302  		"proxies":     cp.Proxies(),
   303  	})
   304  }
   305  
   306  func (cp *CompatibleProvider) Name() string {
   307  	return cp.name
   308  }
   309  
   310  func (cp *CompatibleProvider) HealthCheck() {
   311  	cp.healthCheck.checkAll()
   312  }
   313  
   314  func (cp *CompatibleProvider) Update() error {
   315  	return nil
   316  }
   317  
   318  func (cp *CompatibleProvider) Initial() error {
   319  	cp.Forget()
   320  	if cp.hasProxy && !cp.hasProvider {
   321  		cp.healthCheckWait()
   322  	} else if len(cp.Proxies()) == 0 {
   323  		return errors.New("provider need one proxy at least")
   324  	}
   325  	return nil
   326  }
   327  
   328  func (cp *CompatibleProvider) VehicleType() types.VehicleType {
   329  	return types.Compatible
   330  }
   331  
   332  func (cp *CompatibleProvider) Type() types.ProviderType {
   333  	return types.Proxy
   334  }
   335  
   336  func (cp *CompatibleProvider) Proxies() []C.Proxy {
   337  	if !cp.hasProvider {
   338  		return cp.proxies
   339  	}
   340  
   341  	proxies, _, hitCache := group.Do(cp.name, func() ([]C.Proxy, error) {
   342  		var proxies []C.Proxy
   343  		if cp.filterRegx != nil {
   344  			proxies = lo.FlatMap(
   345  				cp.providers,
   346  				func(provider types.ProxyProvider, _ int) []C.Proxy {
   347  					return lo.Filter(
   348  						provider.Proxies(),
   349  						func(proxy C.Proxy, _ int) bool {
   350  							rs, _ := cp.filterRegx.MatchString(proxy.Name())
   351  							return rs
   352  						})
   353  				})
   354  
   355  			if cp.hasProxy {
   356  				if len(proxies) == 0 {
   357  					return cp.proxies, nil
   358  				}
   359  				proxies = append(cp.proxies, proxies...)
   360  			} else if len(proxies) == 0 {
   361  				proxies = append(proxies, reject)
   362  			}
   363  		} else {
   364  			proxies = lo.FlatMap(
   365  				cp.providers,
   366  				func(pd types.ProxyProvider, _ int) []C.Proxy {
   367  					return pd.Proxies()
   368  				})
   369  
   370  			if cp.hasProxy {
   371  				proxies = append(cp.proxies, proxies...)
   372  			}
   373  		}
   374  
   375  		return proxies, nil
   376  	})
   377  
   378  	if !hitCache {
   379  		cp.healthCheckWait()
   380  	}
   381  
   382  	return proxies
   383  }
   384  
   385  func (cp *CompatibleProvider) Touch() {
   386  	cp.healthCheck.touch()
   387  }
   388  
   389  func (cp *CompatibleProvider) SetProxies(proxies []C.Proxy) {
   390  	cp.proxies = proxies
   391  	cp.hasProxy = len(cp.proxies) != 0
   392  }
   393  
   394  func (cp *CompatibleProvider) SetProviders(providers []types.ProxyProvider) {
   395  	for _, elem := range providers {
   396  		if e, ok := elem.(*ProxySetProvider); ok {
   397  			e.addGroupName(cp.Name())
   398  		}
   399  	}
   400  	cp.providers = providers
   401  	cp.hasProvider = len(cp.providers) != 0
   402  }
   403  
   404  func (cp *CompatibleProvider) Forget() {
   405  	group.Forget(cp.name)
   406  }
   407  
   408  func (cp *CompatibleProvider) Finalize() {
   409  	if cp.tmCheck != nil {
   410  		cp.tmCheck.Stop()
   411  		cp.tmCheck = nil
   412  	}
   413  	cp.healthCheck.close()
   414  	cp.providers = nil
   415  	cp.Forget()
   416  }
   417  
   418  func (cp *CompatibleProvider) healthCheckWait() {
   419  	if cp.tmCheck != nil || !cp.healthCheck.auto() {
   420  		return
   421  	}
   422  	cp.tmCheck = time.AfterFunc(30*time.Second, func() {
   423  		if cp.healthCheck.auto() {
   424  			cp.healthCheck.checkAll()
   425  		}
   426  		cp.tmCheck = nil
   427  	})
   428  }
   429  
   430  func NewCompatibleProvider(name string, hc *HealthCheck, filterRegx *regexp.Regexp) (*CompatibleProvider, error) {
   431  	if hc.auto() {
   432  		go hc.process()
   433  	}
   434  
   435  	pd := &CompatibleProvider{
   436  		name:        name,
   437  		healthCheck: hc,
   438  		filterRegx:  filterRegx,
   439  	}
   440  
   441  	hc.setProxyFn(func() []C.Proxy {
   442  		return pd.Proxies()
   443  	})
   444  
   445  	return pd, nil
   446  }
   447  
   448  func proxiesOnUpdate(pd *ProxySetProvider) func([]C.Proxy) {
   449  	return func(elm []C.Proxy) {
   450  		pd.setProxies(elm)
   451  	}
   452  }
   453  
   454  func proxiesParseAndFilter(filterReg *regexp.Regexp, option adapter.ProxyOption) parser[[]C.Proxy] {
   455  	return func(buf []byte) ([]C.Proxy, error) {
   456  		schema := &ProxySchema{}
   457  
   458  		if err := yaml.Unmarshal(buf, schema); err != nil {
   459  			proxies, err1 := convert.ConvertsV2Ray(buf)
   460  			if err1 != nil {
   461  				proxies, err1 = convert.ConvertsWireGuard(buf)
   462  			}
   463  			if err1 != nil {
   464  				return nil, errors.New("parse proxy provider failure, invalid data format")
   465  			}
   466  			schema.Proxies = lo.Map(proxies, func(m map[string]any, _ int) C.RawProxy {
   467  				return C.RawProxy{M: m}
   468  			})
   469  		}
   470  
   471  		if len(schema.Proxies) == 0 {
   472  			return nil, errors.New("file must have a `proxies` field")
   473  		}
   474  
   475  		proxies := make([]C.Proxy, 0)
   476  		for idx, ps := range schema.Proxies {
   477  			ps.Init()
   478  			mapping := ps.M
   479  			name, ok := mapping["name"].(string)
   480  			if ok && filterReg != nil {
   481  				matched, err := filterReg.MatchString(name)
   482  				if err != nil {
   483  					return nil, fmt.Errorf("match filter regex failed: %w", err)
   484  				}
   485  				if !matched {
   486  					continue
   487  				}
   488  			}
   489  
   490  			if option.PrefixName != "" {
   491  				mapping["name"] = option.PrefixName + name
   492  			}
   493  
   494  			proxy, err := adapter.ParseProxy(mapping, option)
   495  			if err != nil {
   496  				return nil, fmt.Errorf("proxy %s[index: %d] error: %w", name, idx, err)
   497  			}
   498  			proxies = append(proxies, proxy)
   499  		}
   500  
   501  		if len(proxies) == 0 {
   502  			if filterReg != nil {
   503  				return nil, errors.New("doesn't match any proxy, please check your filter")
   504  			}
   505  			return nil, errors.New("file doesn't have any proxy")
   506  		}
   507  
   508  		return proxies, nil
   509  	}
   510  }