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  }