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 }