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