github.com/chwjbn/xclash@v0.2.0/dns/client.go (about)

     1  package dns
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"fmt"
     7  	"net"
     8  	"strings"
     9  
    10  	"github.com/chwjbn/xclash/component/dialer"
    11  	"github.com/chwjbn/xclash/component/resolver"
    12  
    13  	D "github.com/miekg/dns"
    14  )
    15  
    16  type client struct {
    17  	*D.Client
    18  	r     *Resolver
    19  	port  string
    20  	host  string
    21  	iface string
    22  }
    23  
    24  func (c *client) Exchange(m *D.Msg) (*D.Msg, error) {
    25  	return c.ExchangeContext(context.Background(), m)
    26  }
    27  
    28  func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
    29  	var (
    30  		ip  net.IP
    31  		err error
    32  	)
    33  	if c.r == nil {
    34  		// a default ip dns
    35  		if ip = net.ParseIP(c.host); ip == nil {
    36  			return nil, fmt.Errorf("dns %s not a valid ip", c.host)
    37  		}
    38  	} else {
    39  		if ip, err = resolver.ResolveIPWithResolver(c.host, c.r); err != nil {
    40  			return nil, fmt.Errorf("use default dns resolve failed: %w", err)
    41  		}
    42  	}
    43  
    44  	network := "udp"
    45  	if strings.HasPrefix(c.Client.Net, "tcp") {
    46  		network = "tcp"
    47  	}
    48  
    49  	options := []dialer.Option{}
    50  	if c.iface != "" {
    51  		options = append(options, dialer.WithInterface(c.iface))
    52  	}
    53  	conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), c.port), options...)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	defer conn.Close()
    58  
    59  	// miekg/dns ExchangeContext doesn't respond to context cancel.
    60  	// this is a workaround
    61  	type result struct {
    62  		msg *D.Msg
    63  		err error
    64  	}
    65  	ch := make(chan result, 1)
    66  	go func() {
    67  		if strings.HasSuffix(c.Client.Net, "tls") {
    68  			conn = tls.Client(conn, c.Client.TLSConfig)
    69  		}
    70  
    71  		msg, _, err := c.Client.ExchangeWithConn(m, &D.Conn{
    72  			Conn:         conn,
    73  			UDPSize:      c.Client.UDPSize,
    74  			TsigSecret:   c.Client.TsigSecret,
    75  			TsigProvider: c.Client.TsigProvider,
    76  		})
    77  
    78  		ch <- result{msg, err}
    79  	}()
    80  
    81  	select {
    82  	case <-ctx.Done():
    83  		return nil, ctx.Err()
    84  	case ret := <-ch:
    85  		return ret.msg, ret.err
    86  	}
    87  }