github.com/igoogolx/clash@v1.19.8/component/dialer/dialer.go (about)

     1  package dialer
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net"
     7  
     8  	"github.com/igoogolx/clash/component/resolver"
     9  )
    10  
    11  func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
    12  	switch network {
    13  	case "tcp4", "tcp6", "udp4", "udp6":
    14  		host, port, err := net.SplitHostPort(address)
    15  		if err != nil {
    16  			return nil, err
    17  		}
    18  
    19  		var ip net.IP
    20  		switch network {
    21  		case "tcp4", "udp4":
    22  			ip, err = resolver.ResolveIPv4(host)
    23  		default:
    24  			ip, err = resolver.ResolveIPv6(host)
    25  		}
    26  		if err != nil {
    27  			return nil, err
    28  		}
    29  
    30  		return dialContext(ctx, network, ip, port, options)
    31  	case "tcp", "udp":
    32  		return dualStackDialContext(ctx, network, address, options)
    33  	default:
    34  		return nil, errors.New("network invalid")
    35  	}
    36  }
    37  
    38  func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
    39  	cfg := &option{
    40  		interfaceName: DefaultInterface.Load(),
    41  		routingMark:   int(DefaultRoutingMark.Load()),
    42  	}
    43  
    44  	for _, o := range DefaultOptions {
    45  		o(cfg)
    46  	}
    47  
    48  	for _, o := range options {
    49  		o(cfg)
    50  	}
    51  
    52  	lc := &net.ListenConfig{}
    53  	if cfg.interfaceName != "" {
    54  		var (
    55  			addr string
    56  			err  error
    57  		)
    58  		if cfg.fallbackBind {
    59  			addr, err = fallbackBindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
    60  		} else {
    61  			addr, err = bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
    62  		}
    63  		if err != nil {
    64  			return nil, err
    65  		}
    66  		address = addr
    67  	}
    68  	if cfg.addrReuse {
    69  		addrReuseToListenConfig(lc)
    70  	}
    71  	if cfg.routingMark != 0 {
    72  		bindMarkToListenConfig(cfg.routingMark, lc, network, address)
    73  	}
    74  
    75  	return lc.ListenPacket(ctx, network, address)
    76  }
    77  
    78  func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) {
    79  	opt := &option{
    80  		interfaceName: DefaultInterface.Load(),
    81  		routingMark:   int(DefaultRoutingMark.Load()),
    82  	}
    83  
    84  	for _, o := range DefaultOptions {
    85  		o(opt)
    86  	}
    87  
    88  	for _, o := range options {
    89  		o(opt)
    90  	}
    91  
    92  	dialer := &net.Dialer{}
    93  	if opt.interfaceName != "" {
    94  		if opt.fallbackBind {
    95  			if err := fallbackBindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
    96  				return nil, err
    97  			}
    98  		} else {
    99  			if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
   100  				return nil, err
   101  			}
   102  		}
   103  	}
   104  	if opt.routingMark != 0 {
   105  		bindMarkToDialer(opt.routingMark, dialer, network, destination)
   106  	}
   107  
   108  	return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
   109  }
   110  
   111  func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) {
   112  	host, port, err := net.SplitHostPort(address)
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	returned := make(chan struct{})
   118  	defer close(returned)
   119  
   120  	type dialResult struct {
   121  		net.Conn
   122  		error
   123  		resolved bool
   124  		ipv6     bool
   125  		done     bool
   126  	}
   127  	results := make(chan dialResult)
   128  	var primary, fallback dialResult
   129  
   130  	startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
   131  		result := dialResult{ipv6: ipv6, done: true}
   132  		defer func() {
   133  			select {
   134  			case results <- result:
   135  			case <-returned:
   136  				if result.Conn != nil {
   137  					result.Conn.Close()
   138  				}
   139  			}
   140  		}()
   141  
   142  		var ip net.IP
   143  		if ipv6 {
   144  			ip, result.error = resolver.ResolveIPv6(host)
   145  		} else {
   146  			ip, result.error = resolver.ResolveIPv4(host)
   147  		}
   148  		if result.error != nil {
   149  			return
   150  		}
   151  		result.resolved = true
   152  
   153  		result.Conn, result.error = dialContext(ctx, network, ip, port, options)
   154  	}
   155  
   156  	go startRacer(ctx, network+"4", host, false)
   157  	go startRacer(ctx, network+"6", host, true)
   158  
   159  	for res := range results {
   160  		if res.error == nil {
   161  			return res.Conn, nil
   162  		}
   163  
   164  		if !res.ipv6 {
   165  			primary = res
   166  		} else {
   167  			fallback = res
   168  		}
   169  
   170  		if primary.done && fallback.done {
   171  			if primary.resolved {
   172  				return nil, primary.error
   173  			} else if fallback.resolved {
   174  				return nil, fallback.error
   175  			} else {
   176  				return nil, primary.error
   177  			}
   178  		}
   179  	}
   180  
   181  	return nil, errors.New("never touched")
   182  }