fallback.go 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. package outboundgroup
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "time"
  7. "github.com/metacubex/mihomo/adapter/outbound"
  8. "github.com/metacubex/mihomo/common/callback"
  9. N "github.com/metacubex/mihomo/common/net"
  10. "github.com/metacubex/mihomo/common/utils"
  11. "github.com/metacubex/mihomo/component/dialer"
  12. C "github.com/metacubex/mihomo/constant"
  13. "github.com/metacubex/mihomo/constant/provider"
  14. )
  15. type Fallback struct {
  16. *GroupBase
  17. disableUDP bool
  18. testUrl string
  19. selected string
  20. expectedStatus string
  21. Hidden bool
  22. Icon string
  23. }
  24. func (f *Fallback) Now() string {
  25. proxy := f.findAliveProxy(false)
  26. return proxy.Name()
  27. }
  28. // DialContext implements C.ProxyAdapter
  29. func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
  30. proxy := f.findAliveProxy(true)
  31. c, err := proxy.DialContext(ctx, metadata, f.Base.DialOptions(opts...)...)
  32. if err == nil {
  33. c.AppendToChains(f)
  34. } else {
  35. f.onDialFailed(proxy.Type(), err)
  36. }
  37. if N.NeedHandshake(c) {
  38. c = callback.NewFirstWriteCallBackConn(c, func(err error) {
  39. if err == nil {
  40. f.onDialSuccess()
  41. } else {
  42. f.onDialFailed(proxy.Type(), err)
  43. }
  44. })
  45. }
  46. return c, err
  47. }
  48. // ListenPacketContext implements C.ProxyAdapter
  49. func (f *Fallback) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
  50. proxy := f.findAliveProxy(true)
  51. pc, err := proxy.ListenPacketContext(ctx, metadata, f.Base.DialOptions(opts...)...)
  52. if err == nil {
  53. pc.AppendToChains(f)
  54. }
  55. return pc, err
  56. }
  57. // SupportUDP implements C.ProxyAdapter
  58. func (f *Fallback) SupportUDP() bool {
  59. if f.disableUDP {
  60. return false
  61. }
  62. proxy := f.findAliveProxy(false)
  63. return proxy.SupportUDP()
  64. }
  65. // IsL3Protocol implements C.ProxyAdapter
  66. func (f *Fallback) IsL3Protocol(metadata *C.Metadata) bool {
  67. return f.findAliveProxy(false).IsL3Protocol(metadata)
  68. }
  69. // MarshalJSON implements C.ProxyAdapter
  70. func (f *Fallback) MarshalJSON() ([]byte, error) {
  71. all := []string{}
  72. for _, proxy := range f.GetProxies(false) {
  73. all = append(all, proxy.Name())
  74. }
  75. return json.Marshal(map[string]any{
  76. "type": f.Type().String(),
  77. "now": f.Now(),
  78. "all": all,
  79. "testUrl": f.testUrl,
  80. "expectedStatus": f.expectedStatus,
  81. "fixed": f.selected,
  82. "hidden": f.Hidden,
  83. "icon": f.Icon,
  84. })
  85. }
  86. // Unwrap implements C.ProxyAdapter
  87. func (f *Fallback) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
  88. proxy := f.findAliveProxy(touch)
  89. return proxy
  90. }
  91. func (f *Fallback) findAliveProxy(touch bool) C.Proxy {
  92. proxies := f.GetProxies(touch)
  93. for _, proxy := range proxies {
  94. if len(f.selected) == 0 {
  95. if proxy.AliveForTestUrl(f.testUrl) {
  96. return proxy
  97. }
  98. } else {
  99. if proxy.Name() == f.selected {
  100. if proxy.AliveForTestUrl(f.testUrl) {
  101. return proxy
  102. } else {
  103. f.selected = ""
  104. }
  105. }
  106. }
  107. }
  108. return proxies[0]
  109. }
  110. func (f *Fallback) Set(name string) error {
  111. var p C.Proxy
  112. for _, proxy := range f.GetProxies(false) {
  113. if proxy.Name() == name {
  114. p = proxy
  115. break
  116. }
  117. }
  118. if p == nil {
  119. return errors.New("proxy not exist")
  120. }
  121. f.selected = name
  122. if !p.AliveForTestUrl(f.testUrl) {
  123. ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(5000))
  124. defer cancel()
  125. expectedStatus, _ := utils.NewUnsignedRanges[uint16](f.expectedStatus)
  126. _, _ = p.URLTest(ctx, f.testUrl, expectedStatus)
  127. }
  128. return nil
  129. }
  130. func (f *Fallback) ForceSet(name string) {
  131. f.selected = name
  132. }
  133. func NewFallback(option *GroupCommonOption, providers []provider.ProxyProvider) *Fallback {
  134. return &Fallback{
  135. GroupBase: NewGroupBase(GroupBaseOption{
  136. outbound.BaseOption{
  137. Name: option.Name,
  138. Type: C.Fallback,
  139. Interface: option.Interface,
  140. RoutingMark: option.RoutingMark,
  141. },
  142. option.Filter,
  143. option.ExcludeFilter,
  144. option.ExcludeType,
  145. option.TestTimeout,
  146. option.MaxFailedTimes,
  147. providers,
  148. }),
  149. disableUDP: option.DisableUDP,
  150. testUrl: option.URL,
  151. expectedStatus: option.ExpectedStatus,
  152. Hidden: option.Hidden,
  153. Icon: option.Icon,
  154. }
  155. }