parser.go 6.5 KB

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