github.com/chwjbn/xclash@v0.2.0/component/dialer/dialer.go (about)

     1  package dialer
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"net"
     7  
     8  	"github.com/chwjbn/xclash/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  	}
    42  
    43  	for _, o := range DefaultOptions {
    44  		o(cfg)
    45  	}
    46  
    47  	for _, o := range options {
    48  		o(cfg)
    49  	}
    50  
    51  	lc := &net.ListenConfig{}
    52  	if cfg.interfaceName != "" {
    53  		addr, err := bindIfaceToListenConfig(cfg.interfaceName, lc, network, address)
    54  		if err != nil {
    55  			return nil, err
    56  		}
    57  		address = addr
    58  	}
    59  	if cfg.addrReuse {
    60  		addrReuseToListenConfig(lc)
    61  	}
    62  	if cfg.routingMark != 0 {
    63  		bindMarkToListenConfig(cfg.routingMark, lc, network, address)
    64  	}
    65  
    66  	return lc.ListenPacket(ctx, network, address)
    67  }
    68  
    69  func dialContext(ctx context.Context, network string, destination net.IP, port string, options []Option) (net.Conn, error) {
    70  	opt := &option{
    71  		interfaceName: DefaultInterface.Load(),
    72  	}
    73  
    74  	for _, o := range DefaultOptions {
    75  		o(opt)
    76  	}
    77  
    78  	for _, o := range options {
    79  		o(opt)
    80  	}
    81  
    82  	dialer := &net.Dialer{}
    83  	if opt.interfaceName != "" {
    84  		if err := bindIfaceToDialer(opt.interfaceName, dialer, network, destination); err != nil {
    85  			return nil, err
    86  		}
    87  	}
    88  	if opt.routingMark != 0 {
    89  		bindMarkToDialer(opt.routingMark, dialer, network, destination)
    90  	}
    91  
    92  	return dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
    93  }
    94  
    95  func dualStackDialContext(ctx context.Context, network, address string, options []Option) (net.Conn, error) {
    96  	host, port, err := net.SplitHostPort(address)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	returned := make(chan struct{})
   102  	defer close(returned)
   103  
   104  	type dialResult struct {
   105  		net.Conn
   106  		error
   107  		resolved bool
   108  		ipv6     bool
   109  		done     bool
   110  	}
   111  	results := make(chan dialResult)
   112  	var primary, fallback dialResult
   113  
   114  	startRacer := func(ctx context.Context, network, host string, ipv6 bool) {
   115  		result := dialResult{ipv6: ipv6, done: true}
   116  		defer func() {
   117  			select {
   118  			case results <- result:
   119  			case <-returned:
   120  				if result.Conn != nil {
   121  					result.Conn.Close()
   122  				}
   123  			}
   124  		}()
   125  
   126  		var ip net.IP
   127  		if ipv6 {
   128  			ip, result.error = resolver.ResolveIPv6(host)
   129  		} else {
   130  			ip, result.error = resolver.ResolveIPv4(host)
   131  		}
   132  		if result.error != nil {
   133  			return
   134  		}
   135  		result.resolved = true
   136  
   137  		result.Conn, result.error = dialContext(ctx, network, ip, port, options)
   138  	}
   139  
   140  	go startRacer(ctx, network+"4", host, false)
   141  	go startRacer(ctx, network+"6", host, true)
   142  
   143  	for res := range results {
   144  		if res.error == nil {
   145  			return res.Conn, nil
   146  		}
   147  
   148  		if !res.ipv6 {
   149  			primary = res
   150  		} else {
   151  			fallback = res
   152  		}
   153  
   154  		if primary.done && fallback.done {
   155  			if primary.resolved {
   156  				return nil, primary.error
   157  			} else if fallback.resolved {
   158  				return nil, fallback.error
   159  			} else {
   160  				return nil, primary.error
   161  			}
   162  		}
   163  	}
   164  
   165  	return nil, errors.New("never touched")
   166  }