github.com/igoogolx/clash@v1.19.8/adapter/outboundgroup/urltest.go (about) 1 package outboundgroup 2 3 import ( 4 "context" 5 "encoding/json" 6 "time" 7 8 "github.com/igoogolx/clash/adapter/outbound" 9 "github.com/igoogolx/clash/common/singledo" 10 "github.com/igoogolx/clash/component/dialer" 11 C "github.com/igoogolx/clash/constant" 12 "github.com/igoogolx/clash/constant/provider" 13 ) 14 15 type urlTestOption func(*URLTest) 16 17 func urlTestWithTolerance(tolerance uint16) urlTestOption { 18 return func(u *URLTest) { 19 u.tolerance = tolerance 20 } 21 } 22 23 type URLTest struct { 24 *outbound.Base 25 tolerance uint16 26 disableUDP bool 27 fastNode C.Proxy 28 single *singledo.Single 29 fastSingle *singledo.Single 30 providers []provider.ProxyProvider 31 } 32 33 func (u *URLTest) Now() string { 34 return u.fast(false).Name() 35 } 36 37 // DialContext implements C.ProxyAdapter 38 func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (c C.Conn, err error) { 39 c, err = u.fast(true).DialContext(ctx, metadata, u.Base.DialOptions(opts...)...) 40 if err == nil { 41 c.AppendToChains(u) 42 } 43 return c, err 44 } 45 46 // ListenPacketContext implements C.ProxyAdapter 47 func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { 48 pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...) 49 if err == nil { 50 pc.AppendToChains(u) 51 } 52 return pc, err 53 } 54 55 // Unwrap implements C.ProxyAdapter 56 func (u *URLTest) Unwrap(metadata *C.Metadata) C.Proxy { 57 return u.fast(true) 58 } 59 60 func (u *URLTest) proxies(touch bool) []C.Proxy { 61 elm, _, _ := u.single.Do(func() (any, error) { 62 return getProvidersProxies(u.providers, touch), nil 63 }) 64 65 return elm.([]C.Proxy) 66 } 67 68 func (u *URLTest) fast(touch bool) C.Proxy { 69 elm, _, shared := u.fastSingle.Do(func() (any, error) { 70 proxies := u.proxies(touch) 71 fast := proxies[0] 72 min := fast.LastDelay() 73 fastNotExist := true 74 75 for _, proxy := range proxies[1:] { 76 if u.fastNode != nil && proxy.Name() == u.fastNode.Name() { 77 fastNotExist = false 78 } 79 80 if !proxy.Alive() { 81 continue 82 } 83 84 delay := proxy.LastDelay() 85 if delay < min { 86 fast = proxy 87 min = delay 88 } 89 } 90 91 // tolerance 92 if u.fastNode == nil || fastNotExist || !u.fastNode.Alive() || u.fastNode.LastDelay() > fast.LastDelay()+u.tolerance { 93 u.fastNode = fast 94 } 95 96 return u.fastNode, nil 97 }) 98 if shared && touch { // a shared fastSingle.Do() may cause providers untouched, so we touch them again 99 touchProviders(u.providers) 100 } 101 102 return elm.(C.Proxy) 103 } 104 105 // SupportUDP implements C.ProxyAdapter 106 func (u *URLTest) SupportUDP() bool { 107 if u.disableUDP { 108 return false 109 } 110 111 return u.fast(false).SupportUDP() 112 } 113 114 // MarshalJSON implements C.ProxyAdapter 115 func (u *URLTest) MarshalJSON() ([]byte, error) { 116 var all []string 117 for _, proxy := range u.proxies(false) { 118 all = append(all, proxy.Name()) 119 } 120 return json.Marshal(map[string]any{ 121 "type": u.Type().String(), 122 "now": u.Now(), 123 "all": all, 124 }) 125 } 126 127 func parseURLTestOption(config map[string]any) []urlTestOption { 128 opts := []urlTestOption{} 129 130 // tolerance 131 if tolerance, ok := config["tolerance"].(int); ok { 132 opts = append(opts, urlTestWithTolerance(uint16(tolerance))) 133 } 134 135 return opts 136 } 137 138 func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { 139 urlTest := &URLTest{ 140 Base: outbound.NewBase(outbound.BaseOption{ 141 Name: option.Name, 142 Type: C.URLTest, 143 Interface: option.Interface, 144 RoutingMark: option.RoutingMark, 145 }), 146 single: singledo.NewSingle(defaultGetProxiesDuration), 147 fastSingle: singledo.NewSingle(time.Second * 10), 148 providers: providers, 149 disableUDP: option.DisableUDP, 150 } 151 152 for _, option := range options { 153 option(urlTest) 154 } 155 156 return urlTest 157 }