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 }