github.com/chwjbn/xclash@v0.2.0/adapter/outboundgroup/relay.go (about)

     1  package outboundgroup
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     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 Relay struct {
    16  	*outbound.Base
    17  	single    *singledo.Single
    18  	providers []provider.ProxyProvider
    19  }
    20  
    21  // DialContext implements C.ProxyAdapter
    22  func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    23  	var proxies []C.Proxy
    24  	for _, proxy := range r.proxies(metadata, true) {
    25  		if proxy.Type() != C.Direct {
    26  			proxies = append(proxies, proxy)
    27  		}
    28  	}
    29  
    30  	switch len(proxies) {
    31  	case 0:
    32  		return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
    33  	case 1:
    34  		return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
    35  	}
    36  
    37  	first := proxies[0]
    38  	last := proxies[len(proxies)-1]
    39  
    40  	c, err := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
    41  	if err != nil {
    42  		return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
    43  	}
    44  	tcpKeepAlive(c)
    45  
    46  	var currentMeta *C.Metadata
    47  	for _, proxy := range proxies[1:] {
    48  		currentMeta, err = addrToMetadata(proxy.Addr())
    49  		if err != nil {
    50  			return nil, err
    51  		}
    52  
    53  		c, err = first.StreamConn(c, currentMeta)
    54  		if err != nil {
    55  			return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
    56  		}
    57  
    58  		first = proxy
    59  	}
    60  
    61  	c, err = last.StreamConn(c, metadata)
    62  	if err != nil {
    63  		return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
    64  	}
    65  
    66  	return outbound.NewConn(c, r), nil
    67  }
    68  
    69  // MarshalJSON implements C.ProxyAdapter
    70  func (r *Relay) MarshalJSON() ([]byte, error) {
    71  	var all []string
    72  	for _, proxy := range r.rawProxies(false) {
    73  		all = append(all, proxy.Name())
    74  	}
    75  	return json.Marshal(map[string]interface{}{
    76  		"type": r.Type().String(),
    77  		"all":  all,
    78  	})
    79  }
    80  
    81  func (r *Relay) rawProxies(touch bool) []C.Proxy {
    82  	elm, _, _ := r.single.Do(func() (interface{}, error) {
    83  		return getProvidersProxies(r.providers, touch), nil
    84  	})
    85  
    86  	return elm.([]C.Proxy)
    87  }
    88  
    89  func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
    90  	proxies := r.rawProxies(touch)
    91  
    92  	for n, proxy := range proxies {
    93  		subproxy := proxy.Unwrap(metadata)
    94  		for subproxy != nil {
    95  			proxies[n] = subproxy
    96  			subproxy = subproxy.Unwrap(metadata)
    97  		}
    98  	}
    99  
   100  	return proxies
   101  }
   102  
   103  func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
   104  	return &Relay{
   105  		Base: outbound.NewBase(outbound.BaseOption{
   106  			Name:        option.Name,
   107  			Type:        C.Relay,
   108  			Interface:   option.Interface,
   109  			RoutingMark: option.RoutingMark,
   110  		}),
   111  		single:    singledo.NewSingle(defaultGetProxiesDuration),
   112  		providers: providers,
   113  	}
   114  }