github.com/yaling888/clash@v1.53.0/dns/client.go (about) 1 package dns 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "math/rand/v2" 8 "net" 9 "net/netip" 10 "strings" 11 "time" 12 13 D "github.com/miekg/dns" 14 15 "github.com/yaling888/clash/component/dialer" 16 "github.com/yaling888/clash/component/resolver" 17 ) 18 19 var _ dnsClient = (*client)(nil) 20 21 type client struct { 22 *D.Client 23 r *Resolver 24 port string 25 host string 26 iface string 27 proxy string 28 ip string 29 lan bool 30 source string 31 } 32 33 func (c *client) IsLan() bool { 34 return c.lan 35 } 36 37 func (c *client) Exchange(m *D.Msg) (*rMsg, error) { 38 return c.ExchangeContext(context.Background(), m) 39 } 40 41 func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*rMsg, error) { 42 var err error 43 if c.ip == "" { 44 if c.r == nil { 45 return nil, fmt.Errorf("dns %s not a valid ip", c.host) 46 } else { 47 var ips []netip.Addr 48 ips, err = resolver.LookupIPByResolver(context.Background(), c.host, c.r) 49 if err != nil { 50 return nil, fmt.Errorf("use default dns resolve failed: %w", err) 51 } else if len(ips) == 0 { 52 return nil, fmt.Errorf("%w: %s", resolver.ErrIPNotFound, c.host) 53 } 54 ip := ips[rand.IntN(len(ips))] 55 c.ip = ip.String() 56 c.lan = ip.IsLoopback() || ip.IsPrivate() 57 } 58 } 59 60 network := "udp" 61 if strings.HasPrefix(c.Client.Net, "tcp") { 62 network = "tcp" 63 } 64 65 var ( 66 options []dialer.Option 67 conn net.Conn 68 proxy = c.proxy 69 msg = &rMsg{Source: c.source, Lan: c.lan} 70 ) 71 72 if p, ok := resolver.GetProxy(ctx); ok && !c.lan { 73 proxy = p 74 } 75 76 if c.iface != "" { 77 options = append(options, dialer.WithInterface(c.iface)) 78 } 79 80 if proxy != "" { 81 msg.Source += "(" + proxy + ")" 82 conn, err = dialContextByProxyOrInterface(ctx, network, netip.MustParseAddr(c.ip), c.port, proxy, options...) 83 } else { 84 conn, err = dialer.DialContext(ctx, network, net.JoinHostPort(c.ip, c.port), options...) 85 } 86 87 if err != nil { 88 return msg, err 89 } 90 91 if c.Client.Net == "tcp-tls" { 92 conn = tls.Client(conn, c.TLSConfig) 93 } 94 95 co := &D.Conn{ 96 Conn: conn, 97 UDPSize: c.Client.UDPSize, 98 TsigSecret: c.Client.TsigSecret, 99 TsigProvider: c.Client.TsigProvider, 100 } 101 102 defer co.Close() 103 104 // miekg/dns ExchangeContext doesn't respond to context cancel. 105 // this is a workaround 106 type result struct { 107 msg *D.Msg 108 err error 109 } 110 111 ch := make(chan result, 1) 112 113 go func() { 114 msg1, _, err1 := c.Client.ExchangeWithConn(m, co) 115 ch <- result{msg1, err1} 116 }() 117 118 select { 119 case <-ctx.Done(): 120 return msg, ctx.Err() 121 case ret := <-ch: 122 msg.Msg = ret.msg 123 return msg, ret.err 124 } 125 } 126 127 func newClient(nw, addr, proxy, iface string, dhcp bool, r *Resolver) *client { 128 host, port, _ := net.SplitHostPort(addr) 129 var ( 130 ip string 131 lan bool 132 ) 133 if a, err := netip.ParseAddr(host); err == nil { 134 ip = host 135 lan = a.IsLoopback() || a.IsPrivate() 136 } 137 138 var timeout time.Duration 139 if proxy != "" { 140 timeout = proxyTimeout 141 } else { 142 timeout = resolver.DefaultDNSTimeout 143 } 144 145 clientNet := nw 146 if dhcp { 147 clientNet = "dhcp" 148 } else if clientNet == "tcp-tls" { 149 clientNet = "tls" 150 } 151 if clientNet != "" { 152 clientNet += "://" 153 } 154 source := clientNet + addr 155 156 return &client{ 157 Client: &D.Client{ 158 Net: nw, 159 TLSConfig: &tls.Config{ 160 ServerName: host, 161 }, 162 UDPSize: 4096, 163 Timeout: timeout, 164 }, 165 port: port, 166 host: host, 167 ip: ip, 168 iface: iface, 169 proxy: proxy, 170 source: source, 171 lan: lan, 172 r: r, 173 } 174 }