client.go 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package dns
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "fmt"
  6. "net"
  7. "strings"
  8. "github.com/metacubex/mihomo/component/ca"
  9. "github.com/metacubex/mihomo/log"
  10. D "github.com/miekg/dns"
  11. )
  12. type client struct {
  13. *D.Client
  14. port string
  15. host string
  16. dialer *dnsDialer
  17. addr string
  18. }
  19. var _ dnsClient = (*client)(nil)
  20. // Address implements dnsClient
  21. func (c *client) Address() string {
  22. if len(c.addr) != 0 {
  23. return c.addr
  24. }
  25. schema := "udp"
  26. if strings.HasPrefix(c.Client.Net, "tcp") {
  27. schema = "tcp"
  28. if strings.HasSuffix(c.Client.Net, "tls") {
  29. schema = "tls"
  30. }
  31. }
  32. c.addr = fmt.Sprintf("%s://%s", schema, net.JoinHostPort(c.host, c.port))
  33. return c.addr
  34. }
  35. func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
  36. network := "udp"
  37. if strings.HasPrefix(c.Client.Net, "tcp") {
  38. network = "tcp"
  39. }
  40. addr := net.JoinHostPort(c.host, c.port)
  41. conn, err := c.dialer.DialContext(ctx, network, addr)
  42. if err != nil {
  43. return nil, err
  44. }
  45. defer func() {
  46. _ = conn.Close()
  47. }()
  48. // miekg/dns ExchangeContext doesn't respond to context cancel.
  49. // this is a workaround
  50. type result struct {
  51. msg *D.Msg
  52. err error
  53. }
  54. ch := make(chan result, 1)
  55. go func() {
  56. if strings.HasSuffix(c.Client.Net, "tls") {
  57. conn = tls.Client(conn, ca.GetGlobalTLSConfig(c.Client.TLSConfig))
  58. }
  59. dConn := &D.Conn{
  60. Conn: conn,
  61. UDPSize: c.Client.UDPSize,
  62. TsigSecret: c.Client.TsigSecret,
  63. TsigProvider: c.Client.TsigProvider,
  64. }
  65. msg, _, err := c.Client.ExchangeWithConn(m, dConn)
  66. // Resolvers MUST resend queries over TCP if they receive a truncated UDP response (with TC=1 set)!
  67. if msg != nil && msg.Truncated && c.Client.Net == "" {
  68. tcpClient := *c.Client // copy a client
  69. tcpClient.Net = "tcp"
  70. network = "tcp"
  71. log.Debugln("[DNS] Truncated reply from %s:%s for %s over UDP, retrying over TCP", c.host, c.port, m.Question[0].String())
  72. dConn.Conn, err = c.dialer.DialContext(ctx, network, addr)
  73. if err != nil {
  74. ch <- result{msg, err}
  75. return
  76. }
  77. defer func() {
  78. _ = conn.Close()
  79. }()
  80. msg, _, err = tcpClient.ExchangeWithConn(m, dConn)
  81. }
  82. ch <- result{msg, err}
  83. }()
  84. select {
  85. case <-ctx.Done():
  86. return nil, ctx.Err()
  87. case ret := <-ch:
  88. return ret.msg, ret.err
  89. }
  90. }