github.com/chwjbn/xclash@v0.2.0/adapter/outboundgroup/urltest.go (about) 1 package outboundgroup 2 3 import ( 4 "context" 5 "encoding/json" 6 "time" 7 8 "github.com/chwjbn/xclash/adapter/outbound" 9 "github.com/chwjbn/xclash/common/singledo" 10 "github.com/chwjbn/xclash/component/dialer" 11 C "github.com/chwjbn/xclash/constant" 12 "github.com/chwjbn/xclash/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() (interface{}, 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, _, _ := u.fastSingle.Do(func() (interface{}, 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 99 return elm.(C.Proxy) 100 } 101 102 // SupportUDP implements C.ProxyAdapter 103 func (u *URLTest) SupportUDP() bool { 104 if u.disableUDP { 105 return false 106 } 107 108 return u.fast(false).SupportUDP() 109 } 110 111 // MarshalJSON implements C.ProxyAdapter 112 func (u *URLTest) MarshalJSON() ([]byte, error) { 113 var all []string 114 for _, proxy := range u.proxies(false) { 115 all = append(all, proxy.Name()) 116 } 117 return json.Marshal(map[string]interface{}{ 118 "type": u.Type().String(), 119 "now": u.Now(), 120 "all": all, 121 }) 122 } 123 124 func parseURLTestOption(config map[string]interface{}) []urlTestOption { 125 opts := []urlTestOption{} 126 127 // tolerance 128 if elm, ok := config["tolerance"]; ok { 129 if tolerance, ok := elm.(int); ok { 130 opts = append(opts, urlTestWithTolerance(uint16(tolerance))) 131 } 132 } 133 134 return opts 135 } 136 137 func NewURLTest(option *GroupCommonOption, providers []provider.ProxyProvider, options ...urlTestOption) *URLTest { 138 urlTest := &URLTest{ 139 Base: outbound.NewBase(outbound.BaseOption{ 140 Name: option.Name, 141 Type: C.URLTest, 142 Interface: option.Interface, 143 RoutingMark: option.RoutingMark, 144 }), 145 single: singledo.NewSingle(defaultGetProxiesDuration), 146 fastSingle: singledo.NewSingle(time.Second * 10), 147 providers: providers, 148 disableUDP: option.DisableUDP, 149 } 150 151 for _, option := range options { 152 option(urlTest) 153 } 154 155 return urlTest 156 }