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

     1  package outboundgroup
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/metacubex/mihomo/adapter/outbound"
    11  	"github.com/metacubex/mihomo/common/atomic"
    12  	"github.com/metacubex/mihomo/common/utils"
    13  	C "github.com/metacubex/mihomo/constant"
    14  	"github.com/metacubex/mihomo/constant/provider"
    15  	types "github.com/metacubex/mihomo/constant/provider"
    16  	"github.com/metacubex/mihomo/log"
    17  	"github.com/metacubex/mihomo/tunnel"
    18  
    19  	"github.com/dlclark/regexp2"
    20  )
    21  
    22  type GroupBase struct {
    23  	*outbound.Base
    24  	filterRegs       []*regexp2.Regexp
    25  	excludeFilterReg *regexp2.Regexp
    26  	excludeTypeArray []string
    27  	providers        []provider.ProxyProvider
    28  	failedTestMux    sync.Mutex
    29  	failedTimes      int
    30  	failedTime       time.Time
    31  	failedTesting    atomic.Bool
    32  	proxies          [][]C.Proxy
    33  	versions         []atomic.Uint32
    34  	TestTimeout      int
    35  	maxFailedTimes   int
    36  }
    37  
    38  type GroupBaseOption struct {
    39  	outbound.BaseOption
    40  	filter         string
    41  	excludeFilter  string
    42  	excludeType    string
    43  	TestTimeout    int
    44  	maxFailedTimes int
    45  	providers      []provider.ProxyProvider
    46  }
    47  
    48  func NewGroupBase(opt GroupBaseOption) *GroupBase {
    49  	var excludeFilterReg *regexp2.Regexp
    50  	if opt.excludeFilter != "" {
    51  		excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
    52  	}
    53  	var excludeTypeArray []string
    54  	if opt.excludeType != "" {
    55  		excludeTypeArray = strings.Split(opt.excludeType, "|")
    56  	}
    57  
    58  	var filterRegs []*regexp2.Regexp
    59  	if opt.filter != "" {
    60  		for _, filter := range strings.Split(opt.filter, "`") {
    61  			filterReg := regexp2.MustCompile(filter, regexp2.None)
    62  			filterRegs = append(filterRegs, filterReg)
    63  		}
    64  	}
    65  
    66  	gb := &GroupBase{
    67  		Base:             outbound.NewBase(opt.BaseOption),
    68  		filterRegs:       filterRegs,
    69  		excludeFilterReg: excludeFilterReg,
    70  		excludeTypeArray: excludeTypeArray,
    71  		providers:        opt.providers,
    72  		failedTesting:    atomic.NewBool(false),
    73  		TestTimeout:      opt.TestTimeout,
    74  		maxFailedTimes:   opt.maxFailedTimes,
    75  	}
    76  
    77  	if gb.TestTimeout == 0 {
    78  		gb.TestTimeout = 5000
    79  	}
    80  	if gb.maxFailedTimes == 0 {
    81  		gb.maxFailedTimes = 5
    82  	}
    83  
    84  	gb.proxies = make([][]C.Proxy, len(opt.providers))
    85  	gb.versions = make([]atomic.Uint32, len(opt.providers))
    86  
    87  	return gb
    88  }
    89  
    90  func (gb *GroupBase) Touch() {
    91  	for _, pd := range gb.providers {
    92  		pd.Touch()
    93  	}
    94  }
    95  
    96  func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
    97  	var proxies []C.Proxy
    98  	if len(gb.filterRegs) == 0 {
    99  		for _, pd := range gb.providers {
   100  			if touch {
   101  				pd.Touch()
   102  			}
   103  			proxies = append(proxies, pd.Proxies()...)
   104  		}
   105  	} else {
   106  		for i, pd := range gb.providers {
   107  			if touch {
   108  				pd.Touch()
   109  			}
   110  
   111  			if pd.VehicleType() == types.Compatible {
   112  				gb.versions[i].Store(pd.Version())
   113  				gb.proxies[i] = pd.Proxies()
   114  				continue
   115  			}
   116  
   117  			version := gb.versions[i].Load()
   118  			if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
   119  				var (
   120  					proxies    []C.Proxy
   121  					newProxies []C.Proxy
   122  				)
   123  
   124  				proxies = pd.Proxies()
   125  				proxiesSet := map[string]struct{}{}
   126  				for _, filterReg := range gb.filterRegs {
   127  					for _, p := range proxies {
   128  						name := p.Name()
   129  						if mat, _ := filterReg.MatchString(name); mat {
   130  							if _, ok := proxiesSet[name]; !ok {
   131  								proxiesSet[name] = struct{}{}
   132  								newProxies = append(newProxies, p)
   133  							}
   134  						}
   135  					}
   136  				}
   137  
   138  				gb.proxies[i] = newProxies
   139  			}
   140  		}
   141  
   142  		for _, p := range gb.proxies {
   143  			proxies = append(proxies, p...)
   144  		}
   145  	}
   146  
   147  	if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
   148  		var newProxies []C.Proxy
   149  		proxiesSet := map[string]struct{}{}
   150  		for _, filterReg := range gb.filterRegs {
   151  			for _, p := range proxies {
   152  				name := p.Name()
   153  				if mat, _ := filterReg.MatchString(name); mat {
   154  					if _, ok := proxiesSet[name]; !ok {
   155  						proxiesSet[name] = struct{}{}
   156  						newProxies = append(newProxies, p)
   157  					}
   158  				}
   159  			}
   160  		}
   161  		for _, p := range proxies { // add not matched proxies at the end
   162  			name := p.Name()
   163  			if _, ok := proxiesSet[name]; !ok {
   164  				proxiesSet[name] = struct{}{}
   165  				newProxies = append(newProxies, p)
   166  			}
   167  		}
   168  		proxies = newProxies
   169  	}
   170  	if gb.excludeTypeArray != nil {
   171  		var newProxies []C.Proxy
   172  		for _, p := range proxies {
   173  			mType := p.Type().String()
   174  			flag := false
   175  			for i := range gb.excludeTypeArray {
   176  				if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
   177  					flag = true
   178  					break
   179  				}
   180  
   181  			}
   182  			if flag {
   183  				continue
   184  			}
   185  			newProxies = append(newProxies, p)
   186  		}
   187  		proxies = newProxies
   188  	}
   189  
   190  	if gb.excludeFilterReg != nil {
   191  		var newProxies []C.Proxy
   192  		for _, p := range proxies {
   193  			name := p.Name()
   194  			if mat, _ := gb.excludeFilterReg.MatchString(name); mat {
   195  				continue
   196  			}
   197  			newProxies = append(newProxies, p)
   198  		}
   199  		proxies = newProxies
   200  	}
   201  
   202  	if len(proxies) == 0 {
   203  		return append(proxies, tunnel.Proxies()["COMPATIBLE"])
   204  	}
   205  
   206  	return proxies
   207  }
   208  
   209  func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
   210  	var wg sync.WaitGroup
   211  	var lock sync.Mutex
   212  	mp := map[string]uint16{}
   213  	proxies := gb.GetProxies(false)
   214  	for _, proxy := range proxies {
   215  		proxy := proxy
   216  		wg.Add(1)
   217  		go func() {
   218  			delay, err := proxy.URLTest(ctx, url, expectedStatus)
   219  			if err == nil {
   220  				lock.Lock()
   221  				mp[proxy.Name()] = delay
   222  				lock.Unlock()
   223  			}
   224  
   225  			wg.Done()
   226  		}()
   227  	}
   228  	wg.Wait()
   229  
   230  	if len(mp) == 0 {
   231  		return mp, fmt.Errorf("get delay: all proxies timeout")
   232  	} else {
   233  		return mp, nil
   234  	}
   235  }
   236  
   237  func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
   238  	if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass || adapterType == C.RejectDrop {
   239  		return
   240  	}
   241  
   242  	if strings.Contains(err.Error(), "connection refused") {
   243  		go gb.healthCheck()
   244  		return
   245  	}
   246  
   247  	go func() {
   248  		gb.failedTestMux.Lock()
   249  		defer gb.failedTestMux.Unlock()
   250  
   251  		gb.failedTimes++
   252  		if gb.failedTimes == 1 {
   253  			log.Debugln("ProxyGroup: %s first failed", gb.Name())
   254  			gb.failedTime = time.Now()
   255  		} else {
   256  			if time.Since(gb.failedTime) > time.Duration(gb.TestTimeout)*time.Millisecond {
   257  				gb.failedTimes = 0
   258  				return
   259  			}
   260  
   261  			log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
   262  			if gb.failedTimes >= gb.maxFailedTimes {
   263  				log.Warnln("because %s failed multiple times, active health check", gb.Name())
   264  				gb.healthCheck()
   265  			}
   266  		}
   267  	}()
   268  }
   269  
   270  func (gb *GroupBase) healthCheck() {
   271  	if gb.failedTesting.Load() {
   272  		return
   273  	}
   274  
   275  	gb.failedTesting.Store(true)
   276  	wg := sync.WaitGroup{}
   277  	for _, proxyProvider := range gb.providers {
   278  		wg.Add(1)
   279  		proxyProvider := proxyProvider
   280  		go func() {
   281  			defer wg.Done()
   282  			proxyProvider.HealthCheck()
   283  		}()
   284  	}
   285  
   286  	wg.Wait()
   287  	gb.failedTesting.Store(false)
   288  	gb.failedTimes = 0
   289  }
   290  
   291  func (gb *GroupBase) onDialSuccess() {
   292  	if !gb.failedTesting.Load() {
   293  		gb.failedTimes = 0
   294  	}
   295  }