github.com/igoogolx/clash@v1.19.8/dns/client.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	C "github.com/igoogolx/clash/constant"
     8  	"math/rand"
     9  	"net"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/igoogolx/clash/component/dialer"
    14  	"github.com/igoogolx/clash/component/resolver"
    15  
    16  	D "github.com/miekg/dns"
    17  )
    18  
    19  type client struct {
    20  	*D.Client
    21  	r         *Resolver
    22  	port      string
    23  	host      string
    24  	iface     string
    25  	getDialer func() (C.Proxy, error)
    26  }
    27  
    28  func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
    29  	return c.ExchangeContext(context.Background(), m)
    30  }
    31  
    32  func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
    33  	var (
    34  		ip  net.IP
    35  		err error
    36  	)
    37  	if c.r == nil {
    38  		// a default ip dns
    39  		if ip = net.ParseIP(c.host); ip == nil {
    40  			return nil, fmt.Errorf("dns %s not a valid ip", c.host)
    41  		}
    42  	} else {
    43  		ips, err := resolver.LookupIPWithResolver(ctx, c.host, c.r)
    44  		if err != nil {
    45  			return nil, fmt.Errorf("use default dns resolve failed: %w", err)
    46  		} else if len(ips) == 0 {
    47  			return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
    48  		}
    49  		ip = ips[rand.Intn(len(ips))]
    50  	}
    51  
    52  	network := C.UDP
    53  	if strings.HasPrefix(c.Client.Net, "tcp") {
    54  		network = C.TCP
    55  	}
    56  
    57  	options := []dialer.Option{}
    58  	if c.iface != "" {
    59  		options = append(options, dialer.WithInterface(c.iface))
    60  	}
    61  
    62  	numPort, err := strconv.Atoi(c.port)
    63  
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	var conn net.Conn
    68  	connDial, err := c.getDialer()
    69  	if err != nil {
    70  		return nil, err
    71  	}
    72  	if network == C.TCP {
    73  		conn, err = connDial.DialContext(ctx, &C.Metadata{
    74  			NetWork: network,
    75  			SrcIP:   nil,
    76  			DstIP:   ip,
    77  			SrcPort: 0,
    78  			DstPort: C.Port(numPort),
    79  			Host:    "",
    80  		}, options...)
    81  	} else {
    82  		conn, err = dialer.DialContext(ctx, "udp", net.JoinHostPort(ip.String(), c.port), options...)
    83  	}
    84  
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	defer conn.Close()
    89  
    90  	// miekg/dns ExchangeContext doesn't respond to context cancel.
    91  	// this is a workaround
    92  	type result struct {
    93  		msg *D.Msg
    94  		err error
    95  	}
    96  	ch := make(chan result, 1)
    97  	go func() {
    98  		if strings.HasSuffix(c.Client.Net, "tls") {
    99  			conn = tls.Client(conn, c.Client.TLSConfig)
   100  		}
   101  
   102  		msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
   103  			Conn:         conn,
   104  			UDPSize:      c.Client.UDPSize,
   105  			TsigSecret:   c.Client.TsigSecret,
   106  			TsigProvider: c.Client.TsigProvider,
   107  		})
   108  
   109  		ch <- result{msg, err}
   110  	}()
   111  
   112  	select {
   113  	case <-ctx.Done():
   114  		return nil, ctx.Err()
   115  	case ret := <-ch:
   116  		return ret.msg, ret.err
   117  	}
   118  }