github.com/yaling888/clash@v1.53.0/adapter/outboundgroup/relay.go (about)

     1  package outboundgroup
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"time"
     9  
    10  	"github.com/yaling888/clash/adapter"
    11  	"github.com/yaling888/clash/adapter/outbound"
    12  	"github.com/yaling888/clash/common/singledo"
    13  	"github.com/yaling888/clash/component/dialer"
    14  	"github.com/yaling888/clash/component/resolver"
    15  	C "github.com/yaling888/clash/constant"
    16  	"github.com/yaling888/clash/constant/provider"
    17  	"github.com/yaling888/clash/tunnel"
    18  )
    19  
    20  var _ C.ProxyAdapter = (*Relay)(nil)
    21  
    22  type Relay struct {
    23  	*outbound.Base
    24  	disableDNS bool
    25  	single     *singledo.Single[[]C.Proxy]
    26  	providers  []provider.ProxyProvider
    27  }
    28  
    29  // DialContext implements C.ProxyAdapter
    30  func (r *Relay) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
    31  	var proxies []C.Proxy
    32  	for _, proxy := range r.proxies(metadata, true) {
    33  		if proxy.Type() != C.Direct {
    34  			proxies = append(proxies, proxy)
    35  		}
    36  	}
    37  
    38  	length := len(proxies)
    39  
    40  	switch length {
    41  	case 0:
    42  		return outbound.NewDirect().DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
    43  	case 1:
    44  		return proxies[0].DialContext(ctx, metadata, r.Base.DialOptions(opts...)...)
    45  	}
    46  
    47  	timeout := time.Duration(length) * C.DefaultTCPTimeout
    48  	subCtx, cancel := context.WithTimeout(ctx, timeout)
    49  	defer cancel()
    50  	ctx = subCtx
    51  
    52  	c, err := r.streamContext(ctx, proxies, r.Base.DialOptions(opts...)...)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	last := proxies[len(proxies)-1]
    58  	c, err = last.StreamConn(c, metadata)
    59  	if err != nil {
    60  		return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
    61  	}
    62  
    63  	return outbound.NewConn(c, r), nil
    64  }
    65  
    66  // ListenPacketContext implements C.ProxyAdapter
    67  func (r *Relay) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
    68  	var proxies []C.Proxy
    69  	for _, proxy := range r.proxies(metadata, true) {
    70  		if proxy.Type() != C.Direct {
    71  			proxies = append(proxies, proxy)
    72  		}
    73  	}
    74  
    75  	length := len(proxies)
    76  
    77  	switch length {
    78  	case 0:
    79  		return outbound.NewDirect().ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
    80  	case 1:
    81  		proxy := proxies[0]
    82  		if !proxy.SupportUDP() {
    83  			return nil, fmt.Errorf("%s connect error: proxy [%s] UDP is not supported", proxy.Addr(), proxy.Name())
    84  		}
    85  		return proxy.ListenPacketContext(ctx, metadata, r.Base.DialOptions(opts...)...)
    86  	}
    87  
    88  	var (
    89  		firstIndex          = 0
    90  		nextIndex           = 1
    91  		lastUDPOverTCPIndex int
    92  		rawUDPRelay         bool
    93  
    94  		first = proxies[firstIndex]
    95  		last  = proxies[length-1]
    96  
    97  		c   net.Conn
    98  		cc  net.Conn
    99  		err error
   100  	)
   101  
   102  	if !supportPacketConn(last) {
   103  		return nil, fmt.Errorf(
   104  			"%s connect error: proxy [%s] UDP is not supported in relay chains", last.Addr(), last.Name(),
   105  		)
   106  	}
   107  
   108  	timeout := time.Duration(length) * C.DefaultTCPTimeout
   109  	subCtx, cancel := context.WithTimeout(ctx, timeout)
   110  	defer cancel()
   111  	ctx = subCtx
   112  
   113  	rawUDPRelay, lastUDPOverTCPIndex = isRawUDPRelay(proxies)
   114  
   115  	if first.Type() == C.Socks5 {
   116  		cc1, err1 := dialer.DialContext(ctx, "tcp", first.Addr(), r.Base.DialOptions(opts...)...)
   117  		if err1 != nil {
   118  			return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
   119  		}
   120  		cc = cc1
   121  		tcpKeepAlive(cc)
   122  
   123  		var pc net.PacketConn
   124  		pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
   125  		c = outbound.WrapConn(pc)
   126  	} else if rawUDPRelay {
   127  		var pc net.PacketConn
   128  		pc, err = dialer.ListenPacket(ctx, "udp", "", r.Base.DialOptions(opts...)...)
   129  		c = outbound.WrapConn(pc)
   130  	} else {
   131  		firstIndex = lastUDPOverTCPIndex
   132  		nextIndex = firstIndex + 1
   133  		first = proxies[firstIndex]
   134  		c, err = r.streamContext(ctx, proxies[:nextIndex], r.Base.DialOptions(opts...)...)
   135  	}
   136  
   137  	if err != nil {
   138  		return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
   139  	}
   140  
   141  	if nextIndex < length {
   142  		var currentMeta *C.Metadata
   143  		for i, proxy := range proxies[nextIndex:] { // raw udp in loop
   144  			currentMeta, err = addrToMetadata(proxy.Addr())
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  			currentMeta.NetWork = C.UDP
   149  
   150  			if !supportPacketConn(first) {
   151  				return nil, fmt.Errorf(
   152  					"%s connect error: proxy [%s] UDP is not supported in relay chains", first.Addr(), first.Name(),
   153  				)
   154  			}
   155  
   156  			err = resolveDNS(currentMeta)
   157  			if err != nil {
   158  				return nil, fmt.Errorf("can't resolve ip: %w", err)
   159  			}
   160  
   161  			if cc != nil { // socks5
   162  				c, err = streamSocks5PacketConn(first, cc, c, currentMeta)
   163  				cc = nil
   164  			} else {
   165  				c, err = first.StreamPacketConn(c, currentMeta)
   166  			}
   167  
   168  			if err != nil {
   169  				return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
   170  			}
   171  
   172  			if proxy.Type() == C.Socks5 {
   173  				endIndex := nextIndex + i + 1
   174  				cc, err = r.streamContext(ctx, proxies[:endIndex], r.Base.DialOptions(opts...)...)
   175  				if err != nil {
   176  					return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
   177  				}
   178  			}
   179  
   180  			first = proxy
   181  		}
   182  	}
   183  
   184  	if cc != nil {
   185  		c, err = streamSocks5PacketConn(last, cc, c, metadata)
   186  	} else {
   187  		c, err = last.StreamPacketConn(c, metadata)
   188  	}
   189  
   190  	if err != nil {
   191  		return nil, fmt.Errorf("%s connect error: %w", last.Addr(), err)
   192  	}
   193  
   194  	return outbound.NewPacketConn(c.(net.PacketConn), r), nil
   195  }
   196  
   197  // SupportUDP implements C.ProxyAdapter
   198  func (r *Relay) SupportUDP() bool {
   199  	proxies := r.rawProxies(true)
   200  
   201  	l := len(proxies)
   202  
   203  	if l == 0 {
   204  		return true
   205  	}
   206  
   207  	last := proxies[l-1]
   208  
   209  	return isRawUDP(last) || last.SupportUDP()
   210  }
   211  
   212  // DisableDnsResolve implements C.DisableDnsResolve
   213  func (r *Relay) DisableDnsResolve() bool {
   214  	return r.disableDNS
   215  }
   216  
   217  // MarshalJSON implements C.ProxyAdapter
   218  func (r *Relay) MarshalJSON() ([]byte, error) {
   219  	var all []string
   220  	for _, proxy := range r.rawProxies(false) {
   221  		all = append(all, proxy.Name())
   222  	}
   223  	return json.Marshal(map[string]any{
   224  		"type": r.Type().String(),
   225  		"all":  all,
   226  	})
   227  }
   228  
   229  // Cleanup implements C.ProxyAdapter
   230  func (r *Relay) Cleanup() {
   231  	r.single.Reset()
   232  }
   233  
   234  func (r *Relay) rawProxies(touch bool) []C.Proxy {
   235  	elm, _, _ := r.single.Do(func() ([]C.Proxy, error) {
   236  		return getProvidersProxies(r.providers, touch), nil
   237  	})
   238  
   239  	return elm
   240  }
   241  
   242  func (r *Relay) proxies(metadata *C.Metadata, touch bool) []C.Proxy {
   243  	proxies := r.rawProxies(touch)
   244  
   245  	for n, proxy := range proxies {
   246  		subProxy := proxy.Unwrap(metadata)
   247  		for subProxy != nil {
   248  			proxies[n] = subProxy
   249  			subProxy = subProxy.Unwrap(metadata)
   250  		}
   251  	}
   252  
   253  	return proxies
   254  }
   255  
   256  func (r *Relay) streamContext(ctx context.Context, proxies []C.Proxy, opts ...dialer.Option) (net.Conn, error) {
   257  	first := proxies[0]
   258  
   259  	c, err := dialer.DialContext(ctx, "tcp", first.Addr(), opts...)
   260  	if err != nil {
   261  		return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
   262  	}
   263  	tcpKeepAlive(c)
   264  
   265  	if len(proxies) > 1 {
   266  		var currentMeta *C.Metadata
   267  		for _, proxy := range proxies[1:] {
   268  			currentMeta, err = addrToMetadata(proxy.Addr())
   269  			if err != nil {
   270  				return nil, err
   271  			}
   272  
   273  			c, err = first.StreamConn(c, currentMeta)
   274  			if err != nil {
   275  				return nil, fmt.Errorf("%s connect error: %w", first.Addr(), err)
   276  			}
   277  
   278  			first = proxy
   279  		}
   280  	}
   281  
   282  	return c, nil
   283  }
   284  
   285  func streamSocks5PacketConn(proxy C.Proxy, cc, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
   286  	pc, err := proxy.(*adapter.Proxy).ProxyAdapter.(*outbound.Socks5).
   287  		StreamSocks5PacketConn(cc, c.(net.PacketConn), metadata)
   288  	return outbound.WrapConn(pc), err
   289  }
   290  
   291  func isRawUDPRelay(proxies []C.Proxy) (bool, int) {
   292  	var (
   293  		lastIndex           = len(proxies) - 1
   294  		last                = proxies[lastIndex]
   295  		isLastRawUDP        = isRawUDP(last)
   296  		isUDPOverTCP        = false
   297  		lastUDPOverTCPIndex = -1
   298  	)
   299  
   300  	for i := lastIndex; i >= 0; i-- {
   301  		p := proxies[i]
   302  
   303  		isUDPOverTCP = isUDPOverTCP || !isRawUDP(p)
   304  
   305  		if isLastRawUDP && isUDPOverTCP && lastUDPOverTCPIndex == -1 {
   306  			lastUDPOverTCPIndex = i
   307  		}
   308  	}
   309  
   310  	if !isLastRawUDP {
   311  		lastUDPOverTCPIndex = lastIndex
   312  	}
   313  
   314  	return !isUDPOverTCP, lastUDPOverTCPIndex
   315  }
   316  
   317  func isRawUDP(proxy C.ProxyAdapter) bool {
   318  	tp := proxy.Type()
   319  	if ((tp == C.Shadowsocks || tp == C.ShadowsocksR) && proxy.SupportUDP()) || tp == C.WireGuard || tp == C.Socks5 {
   320  		return true
   321  	}
   322  	return false
   323  }
   324  
   325  func supportPacketConn(proxy C.Proxy) bool {
   326  	tp := proxy.Type()
   327  	if (tp == C.Shadowsocks || tp == C.ShadowsocksR) && !proxy.SupportUDP() {
   328  		return false
   329  	}
   330  	return proxy.SupportUDP() || !tunnel.UDPFallbackMatch.Load()
   331  }
   332  
   333  func resolveDNS(metadata *C.Metadata) error {
   334  	if metadata.Host == "" || metadata.Resolved() {
   335  		return nil
   336  	}
   337  
   338  	rAddrs, err := resolver.LookupIP(context.Background(), metadata.Host)
   339  	if err != nil {
   340  		return err
   341  	}
   342  	metadata.DstIP = rAddrs[0]
   343  	return nil
   344  }
   345  
   346  func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
   347  	return &Relay{
   348  		Base: outbound.NewBase(outbound.BaseOption{
   349  			Name:        option.Name,
   350  			Type:        C.Relay,
   351  			Interface:   option.Interface,
   352  			RoutingMark: option.RoutingMark,
   353  		}),
   354  		single:     singledo.NewSingle[[]C.Proxy](defaultGetProxiesDuration),
   355  		providers:  providers,
   356  		disableDNS: option.DisableDNS,
   357  	}
   358  }