github.com/kelleygo/clashcore@v1.0.2/component/dialer/dialer.go (about)

     1  package dialer
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net"
     8  	"net/netip"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/kelleygo/clashcore/component/resolver"
    16  	"github.com/kelleygo/clashcore/constant/features"
    17  	"github.com/kelleygo/clashcore/log"
    18  )
    19  
    20  const (
    21  	DefaultTCPTimeout = 5 * time.Second
    22  	DefaultUDPTimeout = DefaultTCPTimeout
    23  )
    24  
    25  type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
    26  
    27  var (
    28  	dialMux                      sync.Mutex
    29  	IP4PEnable                   bool
    30  	actualSingleStackDialContext = serialSingleStackDialContext
    31  	actualDualStackDialContext   = serialDualStackDialContext
    32  	tcpConcurrent                = false
    33  	fallbackTimeout              = 300 * time.Millisecond
    34  )
    35  
    36  func applyOptions(options ...Option) *option {
    37  	opt := &option{
    38  		interfaceName: DefaultInterface.Load(),
    39  		routingMark:   int(DefaultRoutingMark.Load()),
    40  	}
    41  
    42  	for _, o := range DefaultOptions {
    43  		o(opt)
    44  	}
    45  
    46  	for _, o := range options {
    47  		o(opt)
    48  	}
    49  
    50  	return opt
    51  }
    52  
    53  func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
    54  	opt := applyOptions(options...)
    55  
    56  	if opt.network == 4 || opt.network == 6 {
    57  		if strings.Contains(network, "tcp") {
    58  			network = "tcp"
    59  		} else {
    60  			network = "udp"
    61  		}
    62  
    63  		network = fmt.Sprintf("%s%d", network, opt.network)
    64  	}
    65  
    66  	ips, port, err := parseAddr(ctx, network, address, opt.resolver)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  
    71  	switch network {
    72  	case "tcp4", "tcp6", "udp4", "udp6":
    73  		return actualSingleStackDialContext(ctx, network, ips, port, opt)
    74  	case "tcp", "udp":
    75  		return actualDualStackDialContext(ctx, network, ips, port, opt)
    76  	default:
    77  		return nil, ErrorInvalidedNetworkStack
    78  	}
    79  }
    80  
    81  func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
    82  	if features.CMFA && DefaultSocketHook != nil {
    83  		return listenPacketHooked(ctx, network, address)
    84  	}
    85  
    86  	cfg := applyOptions(options...)
    87  
    88  	lc := &net.ListenConfig{}
    89  	if cfg.interfaceName != "" {
    90  		bind := bindIfaceToListenConfig
    91  		if cfg.fallbackBind {
    92  			bind = fallbackBindIfaceToListenConfig
    93  		}
    94  		addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
    95  		if err != nil {
    96  			return nil, err
    97  		}
    98  		address = addr
    99  	}
   100  	if cfg.addrReuse {
   101  		addrReuseToListenConfig(lc)
   102  	}
   103  	if cfg.routingMark != 0 {
   104  		bindMarkToListenConfig(cfg.routingMark, lc, network, address)
   105  	}
   106  
   107  	return lc.ListenPacket(ctx, network, address)
   108  }
   109  
   110  func SetTcpConcurrent(concurrent bool) {
   111  	dialMux.Lock()
   112  	defer dialMux.Unlock()
   113  	tcpConcurrent = concurrent
   114  	if concurrent {
   115  		actualSingleStackDialContext = concurrentSingleStackDialContext
   116  		actualDualStackDialContext = concurrentDualStackDialContext
   117  	} else {
   118  		actualSingleStackDialContext = serialSingleStackDialContext
   119  		actualDualStackDialContext = serialDualStackDialContext
   120  	}
   121  }
   122  
   123  func GetTcpConcurrent() bool {
   124  	dialMux.Lock()
   125  	defer dialMux.Unlock()
   126  	return tcpConcurrent
   127  }
   128  
   129  func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
   130  	if features.CMFA && DefaultSocketHook != nil {
   131  		return dialContextHooked(ctx, network, destination, port)
   132  	}
   133  
   134  	var address string
   135  	if IP4PEnable {
   136  		destination, port = lookupIP4P(destination, port)
   137  	}
   138  	address = net.JoinHostPort(destination.String(), port)
   139  
   140  	netDialer := opt.netDialer
   141  	switch netDialer.(type) {
   142  	case nil:
   143  		netDialer = &net.Dialer{}
   144  	case *net.Dialer:
   145  		_netDialer := *netDialer.(*net.Dialer)
   146  		netDialer = &_netDialer // make a copy
   147  	default:
   148  		return netDialer.DialContext(ctx, network, address)
   149  	}
   150  
   151  	dialer := netDialer.(*net.Dialer)
   152  	if opt.interfaceName != "" {
   153  		bind := bindIfaceToDialer
   154  		if opt.fallbackBind {
   155  			bind = fallbackBindIfaceToDialer
   156  		}
   157  		if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
   158  			return nil, err
   159  		}
   160  	}
   161  	if opt.routingMark != 0 {
   162  		bindMarkToDialer(opt.routingMark, dialer, network, destination)
   163  	}
   164  	if opt.mpTcp {
   165  		setMultiPathTCP(dialer)
   166  	}
   167  	if opt.tfo {
   168  		return dialTFO(ctx, *dialer, network, address)
   169  	}
   170  	return dialer.DialContext(ctx, network, address)
   171  }
   172  
   173  func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   174  	return serialDialContext(ctx, network, ips, port, opt)
   175  }
   176  
   177  func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   178  	return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
   179  }
   180  
   181  func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   182  	return parallelDialContext(ctx, network, ips, port, opt)
   183  }
   184  
   185  func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   186  	if opt.prefer != 4 && opt.prefer != 6 {
   187  		return parallelDialContext(ctx, network, ips, port, opt)
   188  	}
   189  	return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
   190  }
   191  
   192  func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   193  	ipv4s, ipv6s := resolver.SortationAddr(ips)
   194  	if len(ipv4s) == 0 && len(ipv6s) == 0 {
   195  		return nil, ErrorNoIpAddress
   196  	}
   197  
   198  	preferIPVersion := opt.prefer
   199  	fallbackTicker := time.NewTicker(fallbackTimeout)
   200  	defer fallbackTicker.Stop()
   201  
   202  	results := make(chan dialResult)
   203  	returned := make(chan struct{})
   204  	defer close(returned)
   205  
   206  	var wg sync.WaitGroup
   207  
   208  	racer := func(ips []netip.Addr, isPrimary bool) {
   209  		defer wg.Done()
   210  		result := dialResult{isPrimary: isPrimary}
   211  		defer func() {
   212  			select {
   213  			case results <- result:
   214  			case <-returned:
   215  				if result.Conn != nil && result.error == nil {
   216  					_ = result.Conn.Close()
   217  				}
   218  			}
   219  		}()
   220  		result.Conn, result.error = dialFn(ctx, network, ips, port, opt)
   221  	}
   222  
   223  	if len(ipv4s) != 0 {
   224  		wg.Add(1)
   225  		go racer(ipv4s, preferIPVersion != 6)
   226  	}
   227  
   228  	if len(ipv6s) != 0 {
   229  		wg.Add(1)
   230  		go racer(ipv6s, preferIPVersion != 4)
   231  	}
   232  
   233  	go func() {
   234  		wg.Wait()
   235  		close(results)
   236  	}()
   237  
   238  	var fallback dialResult
   239  	var errs []error
   240  
   241  loop:
   242  	for {
   243  		select {
   244  		case <-fallbackTicker.C:
   245  			if fallback.error == nil && fallback.Conn != nil {
   246  				return fallback.Conn, nil
   247  			}
   248  		case res, ok := <-results:
   249  			if !ok {
   250  				break loop
   251  			}
   252  			if res.error == nil {
   253  				if res.isPrimary {
   254  					return res.Conn, nil
   255  				}
   256  				fallback = res
   257  			} else {
   258  				if res.isPrimary {
   259  					errs = append([]error{fmt.Errorf("connect failed: %w", res.error)}, errs...)
   260  				} else {
   261  					errs = append(errs, fmt.Errorf("connect failed: %w", res.error))
   262  				}
   263  			}
   264  		}
   265  	}
   266  
   267  	if fallback.error == nil && fallback.Conn != nil {
   268  		return fallback.Conn, nil
   269  	}
   270  	return nil, errors.Join(errs...)
   271  }
   272  
   273  func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   274  	if len(ips) == 0 {
   275  		return nil, ErrorNoIpAddress
   276  	}
   277  	results := make(chan dialResult)
   278  	returned := make(chan struct{})
   279  	defer close(returned)
   280  	racer := func(ctx context.Context, ip netip.Addr) {
   281  		result := dialResult{isPrimary: true, ip: ip}
   282  		defer func() {
   283  			select {
   284  			case results <- result:
   285  			case <-returned:
   286  				if result.Conn != nil && result.error == nil {
   287  					_ = result.Conn.Close()
   288  				}
   289  			}
   290  		}()
   291  		result.Conn, result.error = dialContext(ctx, network, ip, port, opt)
   292  	}
   293  
   294  	for _, ip := range ips {
   295  		go racer(ctx, ip)
   296  	}
   297  	var errs []error
   298  	for i := 0; i < len(ips); i++ {
   299  		res := <-results
   300  		if res.error == nil {
   301  			return res.Conn, nil
   302  		}
   303  		errs = append(errs, res.error)
   304  	}
   305  
   306  	if len(errs) > 0 {
   307  		return nil, errors.Join(errs...)
   308  	}
   309  	return nil, os.ErrDeadlineExceeded
   310  }
   311  
   312  func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
   313  	if len(ips) == 0 {
   314  		return nil, ErrorNoIpAddress
   315  	}
   316  	var errs []error
   317  	for _, ip := range ips {
   318  		if conn, err := dialContext(ctx, network, ip, port, opt); err == nil {
   319  			return conn, nil
   320  		} else {
   321  			errs = append(errs, err)
   322  		}
   323  	}
   324  	return nil, errors.Join(errs...)
   325  }
   326  
   327  type dialResult struct {
   328  	ip netip.Addr
   329  	net.Conn
   330  	error
   331  	isPrimary bool
   332  }
   333  
   334  func parseAddr(ctx context.Context, network, address string, preferResolver resolver.Resolver) ([]netip.Addr, string, error) {
   335  	host, port, err := net.SplitHostPort(address)
   336  	if err != nil {
   337  		return nil, "-1", err
   338  	}
   339  
   340  	var ips []netip.Addr
   341  	switch network {
   342  	case "tcp4", "udp4":
   343  		if preferResolver == nil {
   344  			ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
   345  		} else {
   346  			ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
   347  		}
   348  	case "tcp6", "udp6":
   349  		if preferResolver == nil {
   350  			ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
   351  		} else {
   352  			ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
   353  		}
   354  	default:
   355  		if preferResolver == nil {
   356  			ips, err = resolver.LookupIPProxyServerHost(ctx, host)
   357  		} else {
   358  			ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
   359  		}
   360  	}
   361  	if err != nil {
   362  		return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
   363  	}
   364  	for i, ip := range ips {
   365  		if ip.Is4In6() {
   366  			ips[i] = ip.Unmap()
   367  		}
   368  	}
   369  	return ips, port, nil
   370  }
   371  
   372  type Dialer struct {
   373  	Opt option
   374  }
   375  
   376  func (d Dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
   377  	return DialContext(ctx, network, address, WithOption(d.Opt))
   378  }
   379  
   380  func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
   381  	opt := WithOption(d.Opt)
   382  	if rAddrPort.Addr().Unmap().IsLoopback() {
   383  		// avoid "The requested address is not valid in its context."
   384  		opt = WithInterface("")
   385  	}
   386  	return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, opt)
   387  }
   388  
   389  func NewDialer(options ...Option) Dialer {
   390  	opt := applyOptions(options...)
   391  	return Dialer{Opt: *opt}
   392  }
   393  
   394  func GetIP4PEnable(enableIP4PConvert bool) {
   395  	IP4PEnable = enableIP4PConvert
   396  }
   397  
   398  // kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
   399  
   400  func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
   401  	ip := addr.AsSlice()
   402  	if ip[0] == 0x20 && ip[1] == 0x01 &&
   403  		ip[2] == 0x00 && ip[3] == 0x00 {
   404  		addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
   405  		port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
   406  		log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
   407  		return addr, port
   408  	}
   409  	return addr, port
   410  }