github.com/metacubex/mihomo@v1.18.5/adapter/provider/provider.go (about)

     1  package provider
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/metacubex/mihomo/adapter"
    14  	"github.com/metacubex/mihomo/common/convert"
    15  	"github.com/metacubex/mihomo/common/utils"
    16  	mihomoHttp "github.com/metacubex/mihomo/component/http"
    17  	"github.com/metacubex/mihomo/component/resource"
    18  	C "github.com/metacubex/mihomo/constant"
    19  	types "github.com/metacubex/mihomo/constant/provider"
    20  	"github.com/metacubex/mihomo/log"
    21  	"github.com/metacubex/mihomo/tunnel/statistic"
    22  
    23  	"github.com/dlclark/regexp2"
    24  	"gopkg.in/yaml.v3"
    25  )
    26  
    27  const (
    28  	ReservedName = "default"
    29  )
    30  
    31  type ProxySchema struct {
    32  	Proxies []map[string]any `yaml:"proxies"`
    33  }
    34  
    35  // ProxySetProvider for auto gc
    36  type ProxySetProvider struct {
    37  	*proxySetProvider
    38  }
    39  
    40  type proxySetProvider struct {
    41  	*resource.Fetcher[[]C.Proxy]
    42  	proxies          []C.Proxy
    43  	healthCheck      *HealthCheck
    44  	version          uint32
    45  	subscriptionInfo *SubscriptionInfo
    46  }
    47  
    48  func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
    49  	return json.Marshal(map[string]any{
    50  		"name":             pp.Name(),
    51  		"type":             pp.Type().String(),
    52  		"vehicleType":      pp.VehicleType().String(),
    53  		"proxies":          pp.Proxies(),
    54  		"testUrl":          pp.healthCheck.url,
    55  		"expectedStatus":   pp.healthCheck.expectedStatus.String(),
    56  		"updatedAt":        pp.UpdatedAt,
    57  		"subscriptionInfo": pp.subscriptionInfo,
    58  	})
    59  }
    60  
    61  func (pp *proxySetProvider) Version() uint32 {
    62  	return pp.version
    63  }
    64  
    65  func (pp *proxySetProvider) Name() string {
    66  	return pp.Fetcher.Name()
    67  }
    68  
    69  func (pp *proxySetProvider) HealthCheck() {
    70  	pp.healthCheck.check()
    71  }
    72  
    73  func (pp *proxySetProvider) Update() error {
    74  	elm, same, err := pp.Fetcher.Update()
    75  	if err == nil && !same {
    76  		pp.OnUpdate(elm)
    77  	}
    78  	return err
    79  }
    80  
    81  func (pp *proxySetProvider) Initial() error {
    82  	elm, err := pp.Fetcher.Initial()
    83  	if err != nil {
    84  		return err
    85  	}
    86  	pp.OnUpdate(elm)
    87  	pp.getSubscriptionInfo()
    88  	pp.closeAllConnections()
    89  	return nil
    90  }
    91  
    92  func (pp *proxySetProvider) Type() types.ProviderType {
    93  	return types.Proxy
    94  }
    95  
    96  func (pp *proxySetProvider) Proxies() []C.Proxy {
    97  	return pp.proxies
    98  }
    99  
   100  func (pp *proxySetProvider) Touch() {
   101  	pp.healthCheck.touch()
   102  }
   103  
   104  func (pp *proxySetProvider) HealthCheckURL() string {
   105  	return pp.healthCheck.url
   106  }
   107  
   108  func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
   109  	pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
   110  }
   111  
   112  func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
   113  	pp.proxies = proxies
   114  	pp.healthCheck.setProxy(proxies)
   115  	if pp.healthCheck.auto() {
   116  		go pp.healthCheck.check()
   117  	}
   118  }
   119  
   120  func (pp *proxySetProvider) getSubscriptionInfo() {
   121  	if pp.VehicleType() != types.HTTP {
   122  		return
   123  	}
   124  	go func() {
   125  		ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
   126  		defer cancel()
   127  		resp, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
   128  			http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil, pp.Vehicle().Proxy())
   129  		if err != nil {
   130  			return
   131  		}
   132  		defer resp.Body.Close()
   133  
   134  		userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
   135  		if userInfoStr == "" {
   136  			resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
   137  				http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
   138  			if err != nil {
   139  				return
   140  			}
   141  			defer resp2.Body.Close()
   142  			userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
   143  			if userInfoStr == "" {
   144  				return
   145  			}
   146  		}
   147  		pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
   148  		if err != nil {
   149  			log.Warnln("[Provider] get subscription-userinfo: %e", err)
   150  		}
   151  	}()
   152  }
   153  
   154  func (pp *proxySetProvider) closeAllConnections() {
   155  	statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
   156  		for _, chain := range c.Chains() {
   157  			if chain == pp.Name() {
   158  				_ = c.Close()
   159  				break
   160  			}
   161  		}
   162  		return true
   163  	})
   164  }
   165  
   166  func stopProxyProvider(pd *ProxySetProvider) {
   167  	pd.healthCheck.close()
   168  	_ = pd.Fetcher.Destroy()
   169  }
   170  
   171  func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
   172  	excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
   173  	if err != nil {
   174  		return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
   175  	}
   176  	var excludeTypeArray []string
   177  	if excludeType != "" {
   178  		excludeTypeArray = strings.Split(excludeType, "|")
   179  	}
   180  
   181  	var filterRegs []*regexp2.Regexp
   182  	for _, filter := range strings.Split(filter, "`") {
   183  		filterReg, err := regexp2.Compile(filter, regexp2.None)
   184  		if err != nil {
   185  			return nil, fmt.Errorf("invalid filter regex: %w", err)
   186  		}
   187  		filterRegs = append(filterRegs, filterReg)
   188  	}
   189  
   190  	if hc.auto() {
   191  		go hc.process()
   192  	}
   193  
   194  	pd := &proxySetProvider{
   195  		proxies:     []C.Proxy{},
   196  		healthCheck: hc,
   197  	}
   198  
   199  	fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
   200  	pd.Fetcher = fetcher
   201  	wrapper := &ProxySetProvider{pd}
   202  	runtime.SetFinalizer(wrapper, stopProxyProvider)
   203  	return wrapper, nil
   204  }
   205  
   206  // CompatibleProvider for auto gc
   207  type CompatibleProvider struct {
   208  	*compatibleProvider
   209  }
   210  
   211  type compatibleProvider struct {
   212  	name        string
   213  	healthCheck *HealthCheck
   214  	proxies     []C.Proxy
   215  	version     uint32
   216  }
   217  
   218  func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
   219  	return json.Marshal(map[string]any{
   220  		"name":           cp.Name(),
   221  		"type":           cp.Type().String(),
   222  		"vehicleType":    cp.VehicleType().String(),
   223  		"proxies":        cp.Proxies(),
   224  		"testUrl":        cp.healthCheck.url,
   225  		"expectedStatus": cp.healthCheck.expectedStatus.String(),
   226  	})
   227  }
   228  
   229  func (cp *compatibleProvider) Version() uint32 {
   230  	return cp.version
   231  }
   232  
   233  func (cp *compatibleProvider) Name() string {
   234  	return cp.name
   235  }
   236  
   237  func (cp *compatibleProvider) HealthCheck() {
   238  	cp.healthCheck.check()
   239  }
   240  
   241  func (cp *compatibleProvider) Update() error {
   242  	return nil
   243  }
   244  
   245  func (cp *compatibleProvider) Initial() error {
   246  	if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
   247  		cp.HealthCheck()
   248  	}
   249  	return nil
   250  }
   251  
   252  func (cp *compatibleProvider) VehicleType() types.VehicleType {
   253  	return types.Compatible
   254  }
   255  
   256  func (cp *compatibleProvider) Type() types.ProviderType {
   257  	return types.Proxy
   258  }
   259  
   260  func (cp *compatibleProvider) Proxies() []C.Proxy {
   261  	return cp.proxies
   262  }
   263  
   264  func (cp *compatibleProvider) Touch() {
   265  	cp.healthCheck.touch()
   266  }
   267  
   268  func (cp *compatibleProvider) HealthCheckURL() string {
   269  	return cp.healthCheck.url
   270  }
   271  
   272  func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
   273  	cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
   274  }
   275  
   276  func stopCompatibleProvider(pd *CompatibleProvider) {
   277  	pd.healthCheck.close()
   278  }
   279  
   280  func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
   281  	if len(proxies) == 0 {
   282  		return nil, errors.New("provider need one proxy at least")
   283  	}
   284  
   285  	if hc.auto() {
   286  		go hc.process()
   287  	}
   288  
   289  	pd := &compatibleProvider{
   290  		name:        name,
   291  		proxies:     proxies,
   292  		healthCheck: hc,
   293  	}
   294  
   295  	wrapper := &CompatibleProvider{pd}
   296  	runtime.SetFinalizer(wrapper, stopCompatibleProvider)
   297  	return wrapper, nil
   298  }
   299  
   300  func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
   301  	return func(elm []C.Proxy) {
   302  		pd.setProxies(elm)
   303  		pd.version += 1
   304  		pd.getSubscriptionInfo()
   305  	}
   306  }
   307  
   308  func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] {
   309  	return func(buf []byte) ([]C.Proxy, error) {
   310  		schema := &ProxySchema{}
   311  
   312  		if err := yaml.Unmarshal(buf, schema); err != nil {
   313  			proxies, err1 := convert.ConvertsV2Ray(buf)
   314  			if err1 != nil {
   315  				return nil, fmt.Errorf("%w, %w", err, err1)
   316  			}
   317  			schema.Proxies = proxies
   318  		}
   319  
   320  		if schema.Proxies == nil {
   321  			return nil, errors.New("file must have a `proxies` field")
   322  		}
   323  
   324  		proxies := []C.Proxy{}
   325  		proxiesSet := map[string]struct{}{}
   326  		for _, filterReg := range filterRegs {
   327  			for idx, mapping := range schema.Proxies {
   328  				if nil != excludeTypeArray && len(excludeTypeArray) > 0 {
   329  					mType, ok := mapping["type"]
   330  					if !ok {
   331  						continue
   332  					}
   333  					pType, ok := mType.(string)
   334  					if !ok {
   335  						continue
   336  					}
   337  					flag := false
   338  					for i := range excludeTypeArray {
   339  						if strings.EqualFold(pType, excludeTypeArray[i]) {
   340  							flag = true
   341  							break
   342  						}
   343  
   344  					}
   345  					if flag {
   346  						continue
   347  					}
   348  
   349  				}
   350  				mName, ok := mapping["name"]
   351  				if !ok {
   352  					continue
   353  				}
   354  				name, ok := mName.(string)
   355  				if !ok {
   356  					continue
   357  				}
   358  				if len(excludeFilter) > 0 {
   359  					if mat, _ := excludeFilterReg.MatchString(name); mat {
   360  						continue
   361  					}
   362  				}
   363  				if len(filter) > 0 {
   364  					if mat, _ := filterReg.MatchString(name); !mat {
   365  						continue
   366  					}
   367  				}
   368  				if _, ok := proxiesSet[name]; ok {
   369  					continue
   370  				}
   371  
   372  				if len(dialerProxy) > 0 {
   373  					mapping["dialer-proxy"] = dialerProxy
   374  				}
   375  
   376  				if override.UDP != nil {
   377  					mapping["udp"] = *override.UDP
   378  				}
   379  				if override.Up != nil {
   380  					mapping["up"] = *override.Up
   381  				}
   382  				if override.Down != nil {
   383  					mapping["down"] = *override.Down
   384  				}
   385  				if override.DialerProxy != nil {
   386  					mapping["dialer-proxy"] = *override.DialerProxy
   387  				}
   388  				if override.SkipCertVerify != nil {
   389  					mapping["skip-cert-verify"] = *override.SkipCertVerify
   390  				}
   391  				if override.Interface != nil {
   392  					mapping["interface-name"] = *override.Interface
   393  				}
   394  				if override.RoutingMark != nil {
   395  					mapping["routing-mark"] = *override.RoutingMark
   396  				}
   397  				if override.IPVersion != nil {
   398  					mapping["ip-version"] = *override.IPVersion
   399  				}
   400  				if override.AdditionalPrefix != nil {
   401  					name := mapping["name"].(string)
   402  					mapping["name"] = *override.AdditionalPrefix + name
   403  				}
   404  				if override.AdditionalSuffix != nil {
   405  					name := mapping["name"].(string)
   406  					mapping["name"] = name + *override.AdditionalSuffix
   407  				}
   408  
   409  				proxy, err := adapter.ParseProxy(mapping)
   410  				if err != nil {
   411  					return nil, fmt.Errorf("proxy %d error: %w", idx, err)
   412  				}
   413  
   414  				proxiesSet[name] = struct{}{}
   415  				proxies = append(proxies, proxy)
   416  			}
   417  		}
   418  
   419  		if len(proxies) == 0 {
   420  			if len(filter) > 0 {
   421  				return nil, errors.New("doesn't match any proxy, please check your filter")
   422  			}
   423  			return nil, errors.New("file doesn't have any proxy")
   424  		}
   425  
   426  		return proxies, nil
   427  	}
   428  }