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  }