github.com/kelleygo/clashcore@v1.0.2/dns/client.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"net"
     8  	"net/netip"
     9  	"strings"
    10  
    11  	"github.com/kelleygo/clashcore/component/ca"
    12  	"github.com/kelleygo/clashcore/component/dialer"
    13  	"github.com/kelleygo/clashcore/component/resolver"
    14  	C "github.com/kelleygo/clashcore/constant"
    15  	"github.com/kelleygo/clashcore/log"
    16  
    17  	D "github.com/miekg/dns"
    18  	"github.com/zhangyunhao116/fastrand"
    19  )
    20  
    21  type client struct {
    22  	*D.Client
    23  	r            *Resolver
    24  	port         string
    25  	host         string
    26  	iface        string
    27  	proxyAdapter C.ProxyAdapter
    28  	proxyName    string
    29  	addr         string
    30  }
    31  
    32  var _ dnsClient = (*client)(nil)
    33  
    34  // Address implements dnsClient
    35  func (c *client) Address() string {
    36  	if len(c.addr) != 0 {
    37  		return c.addr
    38  	}
    39  	schema := "udp"
    40  	if strings.HasPrefix(c.Client.Net, "tcp") {
    41  		schema = "tcp"
    42  		if strings.HasSuffix(c.Client.Net, "tls") {
    43  			schema = "tls"
    44  		}
    45  	}
    46  
    47  	c.addr = fmt.Sprintf("%s://%s", schema, net.JoinHostPort(c.host, c.port))
    48  	return c.addr
    49  }
    50  
    51  func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
    52  	var (
    53  		ip  netip.Addr
    54  		err error
    55  	)
    56  	if c.r == nil {
    57  		// a default ip dns
    58  		if ip, err = netip.ParseAddr(c.host); err != nil {
    59  			return nil, fmt.Errorf("dns %s not a valid ip", c.host)
    60  		}
    61  	} else {
    62  		ips, err := resolver.LookupIPWithResolver(ctx, c.host, c.r)
    63  		if err != nil {
    64  			return nil, fmt.Errorf("use default dns resolve failed: %w", err)
    65  		} else if len(ips) == 0 {
    66  			return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host)
    67  		}
    68  		ip = ips[fastrand.Intn(len(ips))]
    69  	}
    70  
    71  	network := "udp"
    72  	if strings.HasPrefix(c.Client.Net, "tcp") {
    73  		network = "tcp"
    74  	}
    75  
    76  	var options []dialer.Option
    77  	if c.iface != "" {
    78  		options = append(options, dialer.WithInterface(c.iface))
    79  	}
    80  
    81  	dialHandler := getDialHandler(c.r, c.proxyAdapter, c.proxyName, options...)
    82  	addr := net.JoinHostPort(ip.String(), c.port)
    83  	conn, err := dialHandler(ctx, network, addr)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  	defer func() {
    88  		_ = conn.Close()
    89  	}()
    90  
    91  	// miekg/dns ExchangeContext doesn't respond to context cancel.
    92  	// this is a workaround
    93  	type result struct {
    94  		msg *D.Msg
    95  		err error
    96  	}
    97  	ch := make(chan result, 1)
    98  	go func() {
    99  		if strings.HasSuffix(c.Client.Net, "tls") {
   100  			conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
   101  		}
   102  
   103  		dConn := &D.Conn{
   104  			Conn:         conn,
   105  			UDPSize:      c.Client.UDPSize,
   106  			TsigSecret:   c.Client.TsigSecret,
   107  			TsigProvider: c.Client.TsigProvider,
   108  		}
   109  
   110  		msg, _, err := c.Client.ExchangeWithConn(m, dConn)
   111  
   112  		// Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)!
   113  		if msg != nil && msg.Truncated && c.Client.Net == "" {
   114  			tcpClient := *c.Client // copy a client
   115  			tcpClient.Net = "tcp"
   116  			network = "tcp"
   117  			log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String())
   118  			dConn.Conn, err = dialHandler(ctx, network, addr)
   119  			if err != nil {
   120  				ch <- result{msg, err}
   121  				return
   122  			}
   123  			defer func() {
   124  				_ = conn.Close()
   125  			}()
   126  			msg, _, err = tcpClient.ExchangeWithConn(m, dConn)
   127  		}
   128  
   129  		ch <- result{msg, err}
   130  	}()
   131  
   132  	select {
   133  	case <-ctx.Done():
   134  		return nil, ctx.Err()
   135  	case ret := <-ch:
   136  		return ret.msg, ret.err
   137  	}
   138  }