urltest.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package outboundgroup
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "sync"
  8. "time"
  9. "github.com/metacubex/mihomo/adapter/outbound"
  10. "github.com/metacubex/mihomo/common/callback"
  11. N "github.com/metacubex/mihomo/common/net"
  12. "github.com/metacubex/mihomo/common/singledo"
  13. "github.com/metacubex/mihomo/common/utils"
  14. "github.com/metacubex/mihomo/component/dialer"
  15. C "github.com/metacubex/mihomo/constant"
  16. "github.com/metacubex/mihomo/constant/provider"
  17. )
  18. type urlTestOption func(*URLTest)
  19. func urlTestWithTolerance(tolerance uint16) urlTestOption {
  20. return func(u *URLTest) {
  21. u.tolerance = tolerance
  22. }
  23. }
  24. type URLTest struct {
  25. *GroupBase
  26. selected string
  27. testUrl string
  28. expectedStatus string
  29. tolerance uint16
  30. disableUDP bool
  31. Hidden bool
  32. Icon string
  33. fastNode C.Proxy
  34. fastSingle *singledo.Single[C.Proxy]
  35. }
  36. func (u *URLTest) Now() string {
  37. return u.fast(false).Name()
  38. }
  39. func (u *URLTest) Set(name string) error {
  40. var p C.Proxy
  41. for _, proxy := range u.GetProxies(false) {
  42. if proxy.Name() == name {
  43. p = proxy
  44. break
  45. }
  46. }
  47. if p == nil {
  48. return errors.New("proxy not exist")
  49. }
  50. u.selected = name
  51. u.fast(false)
  52. return nil
  53. }
  54. func (u *URLTest) ForceSet(name string) {
  55. u.selected = name
  56. }
  57. // DialContext implements C.ProxyAdapter
  58. func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) {
  59. proxy := u.fast(true)
  60. c, err = proxy.DialContext(ctx, metadata, u.Base.DialOptions(opts...)...)
  61. if err == nil {
  62. c.AppendToChains(u)
  63. } else {
  64. u.onDialFailed(proxy.Type(), err)
  65. }
  66. if N.NeedHandshake(c) {
  67. c = callback.NewFirstWriteCallBackConn(c, func(err error) {
  68. if err == nil {
  69. u.onDialSuccess()
  70. } else {
  71. u.onDialFailed(proxy.Type(), err)
  72. }
  73. })
  74. }
  75. return c, err
  76. }
  77. // ListenPacketContext implements C.ProxyAdapter
  78. func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
  79. proxy := u.fast(true)
  80. pc, err := proxy.ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
  81. if err == nil {
  82. pc.AppendToChains(u)
  83. }
  84. return pc, err
  85. }
  86. // Unwrap implements C.ProxyAdapter
  87. func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
  88. return u.fast(touch)
  89. }
  90. func (u *URLTest) fast(touch bool) C.Proxy {
  91. proxies := u.GetProxies(touch)
  92. if u.selected != "" {
  93. for _, proxy := range proxies {
  94. if !proxy.AliveForTestUrl(u.testUrl) {
  95. continue
  96. }
  97. if proxy.Name() == u.selected {
  98. u.fastNode = proxy
  99. return proxy
  100. }
  101. }
  102. }
  103. elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
  104. fast := proxies[0]
  105. minDelay := fast.LastDelayForTestUrl(u.testUrl)
  106. fastNotExist := true
  107. for _, proxy := range proxies[1:] {
  108. if u.fastNode != nil && proxy.Name() == u.fastNode.Name() {
  109. fastNotExist = false
  110. }
  111. if !proxy.AliveForTestUrl(u.testUrl) {
  112. continue
  113. }
  114. delay := proxy.LastDelayForTestUrl(u.testUrl)
  115. if delay < minDelay {
  116. fast = proxy
  117. minDelay = delay
  118. }
  119. }
  120. // tolerance
  121. if u.fastNode == nil || fastNotExist || !u.fastNode.AliveForTestUrl(u.testUrl) || u.fastNode.LastDelayForTestUrl(u.testUrl) > fast.LastDelayForTestUrl(u.testUrl)+u.tolerance {
  122. u.fastNode = fast
  123. }
  124. return u.fastNode, nil
  125. })
  126. if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again
  127. u.Touch()
  128. }
  129. return elm
  130. }
  131. // SupportUDP implements C.ProxyAdapter
  132. func (u *URLTest) SupportUDP() bool {
  133. if u.disableUDP {
  134. return false
  135. }
  136. return u.fast(false).SupportUDP()
  137. }
  138. // IsL3Protocol implements C.ProxyAdapter
  139. func (u *URLTest) IsL3Protocol(metadata *C.Metadata) bool {
  140. return u.fast(false).IsL3Protocol(metadata)
  141. }
  142. // MarshalJSON implements C.ProxyAdapter
  143. func (u *URLTest) MarshalJSON() ([]byte, error) {
  144. all := []string{}
  145. for _, proxy := range u.GetProxies(false) {
  146. all = append(all, proxy.Name())
  147. }
  148. return json.Marshal(map[string]any{
  149. "type": u.Type().String(),
  150. "now": u.Now(),
  151. "all": all,
  152. "testUrl": u.testUrl,
  153. "expectedStatus": u.expectedStatus,
  154. "fixed": u.selected,
  155. "hidden": u.Hidden,
  156. "icon": u.Icon,
  157. })
  158. }
  159. func (u *URLTest) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
  160. var wg sync.WaitGroup
  161. var lock sync.Mutex
  162. mp := map[string]uint16{}
  163. proxies := u.GetProxies(false)
  164. for _, proxy := range proxies {
  165. proxy := proxy
  166. wg.Add(1)
  167. go func() {
  168. delay, err := proxy.URLTest(ctx, u.testUrl, expectedStatus)
  169. if err == nil {
  170. lock.Lock()
  171. mp[proxy.Name()] = delay
  172. lock.Unlock()
  173. }
  174. wg.Done()
  175. }()
  176. }
  177. wg.Wait()
  178. if len(mp) == 0 {
  179. return mp, fmt.Errorf("get delay: all proxies timeout")
  180. } else {
  181. return mp, nil
  182. }
  183. }
  184. func parseURLTestOption(config map[string]any) []urlTestOption {
  185. opts := []urlTestOption{}
  186. // tolerance
  187. if elm, ok := config["tolerance"]; ok {
  188. if tolerance, ok := elm.(int); ok {
  189. opts = append(opts, urlTestWithTolerance(uint16(tolerance)))
  190. }
  191. }
  192. return opts
  193. }
  194. func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest {
  195. urlTest := &URLTest{
  196. GroupBase: NewGroupBase(GroupBaseOption{
  197. outbound.BaseOption{
  198. Name: option.Name,
  199. Type: C.URLTest,
  200. Interface: option.Interface,
  201. RoutingMark: option.RoutingMark,
  202. },
  203. option.Filter,
  204. option.ExcludeFilter,
  205. option.ExcludeType,
  206. option.TestTimeout,
  207. option.MaxFailedTimes,
  208. providers,
  209. }),
  210. fastSingle: singledo.NewSingle[C.Proxy](time.Second * 10),
  211. disableUDP: option.DisableUDP,
  212. testUrl: option.URL,
  213. expectedStatus: option.ExpectedStatus,
  214. Hidden: option.Hidden,
  215. Icon: option.Icon,
  216. }
  217. for _, option := range options {
  218. option(urlTest)
  219. }
  220. return urlTest
  221. }