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 }