github.com/metacubex/mihomo@v1.18.5/adapter/outboundgroup/parser.go (about) 1 package outboundgroup 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/dlclark/regexp2" 9 10 "github.com/metacubex/mihomo/adapter/outbound" 11 "github.com/metacubex/mihomo/adapter/provider" 12 "github.com/metacubex/mihomo/common/structure" 13 "github.com/metacubex/mihomo/common/utils" 14 C "github.com/metacubex/mihomo/constant" 15 types "github.com/metacubex/mihomo/constant/provider" 16 ) 17 18 var ( 19 errFormat = errors.New("format error") 20 errType = errors.New("unsupported type") 21 errMissProxy = errors.New("`use` or `proxies` missing") 22 errDuplicateProvider = errors.New("duplicate provider name") 23 ) 24 25 type GroupCommonOption struct { 26 outbound.BasicOption 27 Name string `group:"name"` 28 Type string `group:"type"` 29 Proxies []string `group:"proxies,omitempty"` 30 Use []string `group:"use,omitempty"` 31 URL string `group:"url,omitempty"` 32 Interval int `group:"interval,omitempty"` 33 TestTimeout int `group:"timeout,omitempty"` 34 MaxFailedTimes int `group:"max-failed-times,omitempty"` 35 Lazy bool `group:"lazy,omitempty"` 36 DisableUDP bool `group:"disable-udp,omitempty"` 37 Filter string `group:"filter,omitempty"` 38 ExcludeFilter string `group:"exclude-filter,omitempty"` 39 ExcludeType string `group:"exclude-type,omitempty"` 40 ExpectedStatus string `group:"expected-status,omitempty"` 41 IncludeAll bool `group:"include-all,omitempty"` 42 IncludeAllProxies bool `group:"include-all-proxies,omitempty"` 43 IncludeAllProviders bool `group:"include-all-providers,omitempty"` 44 Hidden bool `group:"hidden,omitempty"` 45 Icon string `group:"icon,omitempty"` 46 } 47 48 func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, providersMap map[string]types.ProxyProvider, AllProxies []string, AllProviders []string) (C.ProxyAdapter, error) { 49 decoder := structure.NewDecoder(structure.Option{TagName: "group", WeaklyTypedInput: true}) 50 51 groupOption := &GroupCommonOption{ 52 Lazy: true, 53 } 54 if err := decoder.Decode(config, groupOption); err != nil { 55 return nil, errFormat 56 } 57 58 if groupOption.Type == "" || groupOption.Name == "" { 59 return nil, errFormat 60 } 61 62 groupName := groupOption.Name 63 64 providers := []types.ProxyProvider{} 65 66 if groupOption.IncludeAll { 67 groupOption.IncludeAllProviders = true 68 groupOption.IncludeAllProxies = true 69 } 70 71 if groupOption.IncludeAllProviders { 72 groupOption.Use = append(groupOption.Use, AllProviders...) 73 } 74 if groupOption.IncludeAllProxies { 75 if groupOption.Filter != "" { 76 var filterRegs []*regexp2.Regexp 77 for _, filter := range strings.Split(groupOption.Filter, "`") { 78 filterReg := regexp2.MustCompile(filter, regexp2.None) 79 filterRegs = append(filterRegs, filterReg) 80 } 81 for _, p := range AllProxies { 82 for _, filterReg := range filterRegs { 83 if mat, _ := filterReg.MatchString(p); mat { 84 groupOption.Proxies = append(groupOption.Proxies, p) 85 } 86 } 87 } 88 } else { 89 groupOption.Proxies = append(groupOption.Proxies, AllProxies...) 90 } 91 } 92 93 if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 { 94 return nil, fmt.Errorf("%s: %w", groupName, errMissProxy) 95 } 96 97 expectedStatus, err := utils.NewUnsignedRanges[uint16](groupOption.ExpectedStatus) 98 if err != nil { 99 return nil, fmt.Errorf("%s: %w", groupName, err) 100 } 101 102 status := strings.TrimSpace(groupOption.ExpectedStatus) 103 if status == "" { 104 status = "*" 105 } 106 groupOption.ExpectedStatus = status 107 108 if len(groupOption.Use) != 0 { 109 PDs, err := getProviders(providersMap, groupOption.Use) 110 if err != nil { 111 return nil, fmt.Errorf("%s: %w", groupName, err) 112 } 113 114 // if test URL is empty, use the first health check URL of providers 115 if groupOption.URL == "" { 116 for _, pd := range PDs { 117 if pd.HealthCheckURL() != "" { 118 groupOption.URL = pd.HealthCheckURL() 119 break 120 } 121 } 122 if groupOption.URL == "" { 123 groupOption.URL = C.DefaultTestURL 124 } 125 } else { 126 addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval)) 127 } 128 providers = append(providers, PDs...) 129 } 130 131 if len(groupOption.Proxies) != 0 { 132 ps, err := getProxies(proxyMap, groupOption.Proxies) 133 if err != nil { 134 return nil, fmt.Errorf("%s: %w", groupName, err) 135 } 136 137 if _, ok := providersMap[groupName]; ok { 138 return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider) 139 } 140 141 if groupOption.URL == "" { 142 groupOption.URL = C.DefaultTestURL 143 } 144 145 // select don't need auto health check 146 if groupOption.Type != "select" && groupOption.Type != "relay" { 147 if groupOption.Interval == 0 { 148 groupOption.Interval = 300 149 } 150 } 151 152 hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus) 153 154 pd, err := provider.NewCompatibleProvider(groupName, ps, hc) 155 if err != nil { 156 return nil, fmt.Errorf("%s: %w", groupName, err) 157 } 158 159 providers = append([]types.ProxyProvider{pd}, providers...) 160 providersMap[groupName] = pd 161 } 162 163 var group C.ProxyAdapter 164 switch groupOption.Type { 165 case "url-test": 166 opts := parseURLTestOption(config) 167 group = NewURLTest(groupOption, providers, opts...) 168 case "select": 169 group = NewSelector(groupOption, providers) 170 case "fallback": 171 group = NewFallback(groupOption, providers) 172 case "load-balance": 173 strategy := parseStrategy(config) 174 return NewLoadBalance(groupOption, providers, strategy) 175 case "relay": 176 group = NewRelay(groupOption, providers) 177 default: 178 return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) 179 } 180 181 return group, nil 182 } 183 184 func getProxies(mapping map[string]C.Proxy, list []string) ([]C.Proxy, error) { 185 var ps []C.Proxy 186 for _, name := range list { 187 p, ok := mapping[name] 188 if !ok { 189 return nil, fmt.Errorf("'%s' not found", name) 190 } 191 ps = append(ps, p) 192 } 193 return ps, nil 194 } 195 196 func getProviders(mapping map[string]types.ProxyProvider, list []string) ([]types.ProxyProvider, error) { 197 var ps []types.ProxyProvider 198 for _, name := range list { 199 p, ok := mapping[name] 200 if !ok { 201 return nil, fmt.Errorf("'%s' not found", name) 202 } 203 204 if p.VehicleType() == types.Compatible { 205 return nil, fmt.Errorf("proxy group %s can't contains in `use`", name) 206 } 207 ps = append(ps, p) 208 } 209 return ps, nil 210 } 211 212 func addTestUrlToProviders(providers []types.ProxyProvider, url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) { 213 if len(providers) == 0 || len(url) == 0 { 214 return 215 } 216 217 for _, pd := range providers { 218 pd.RegisterHealthCheckTask(url, expectedStatus, filter, interval) 219 } 220 }