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 }