nameserver_doh.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. package dns
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/tls"
  6. go_errors "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "strings"
  12. "time"
  13. utls "github.com/refraction-networking/utls"
  14. "github.com/xtls/xray-core/common"
  15. "github.com/xtls/xray-core/common/crypto"
  16. "github.com/xtls/xray-core/common/errors"
  17. "github.com/xtls/xray-core/common/log"
  18. "github.com/xtls/xray-core/common/net"
  19. "github.com/xtls/xray-core/common/net/cnc"
  20. "github.com/xtls/xray-core/common/protocol/dns"
  21. "github.com/xtls/xray-core/common/session"
  22. dns_feature "github.com/xtls/xray-core/features/dns"
  23. "github.com/xtls/xray-core/features/routing"
  24. "github.com/xtls/xray-core/transport/internet"
  25. "golang.org/x/net/http2"
  26. )
  27. // DoHNameServer implemented DNS over HTTPS (RFC8484) Wire Format,
  28. // which is compatible with traditional dns over udp(RFC1035),
  29. // thus most of the DOH implementation is copied from udpns.go
  30. type DoHNameServer struct {
  31. cacheController *CacheController
  32. httpClient *http.Client
  33. dohURL string
  34. clientIP net.IP
  35. }
  36. // NewDoHNameServer creates DOH/DOHL client object for remote/local resolving.
  37. func NewDoHNameServer(url *url.URL, dispatcher routing.Dispatcher, h2c bool, disableCache bool, clientIP net.IP) *DoHNameServer {
  38. url.Scheme = "https"
  39. mode := "DOH"
  40. if dispatcher == nil {
  41. mode = "DOHL"
  42. }
  43. errors.LogInfo(context.Background(), "DNS: created ", mode, " client for ", url.String(), ", with h2c ", h2c)
  44. s := &DoHNameServer{
  45. cacheController: NewCacheController(mode+"//"+url.Host, disableCache),
  46. dohURL: url.String(),
  47. clientIP: clientIP,
  48. }
  49. s.httpClient = &http.Client{
  50. Transport: &http2.Transport{
  51. IdleConnTimeout: net.ConnIdleTimeout,
  52. ReadIdleTimeout: net.ChromeH2KeepAlivePeriod,
  53. DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
  54. dest, err := net.ParseDestination(network + ":" + addr)
  55. if err != nil {
  56. return nil, err
  57. }
  58. var conn net.Conn
  59. if dispatcher != nil {
  60. dnsCtx := toDnsContext(ctx, s.dohURL)
  61. if h2c {
  62. dnsCtx = session.ContextWithMitmAlpn11(dnsCtx, false) // for insurance
  63. dnsCtx = session.ContextWithMitmServerName(dnsCtx, url.Hostname())
  64. }
  65. link, err := dispatcher.Dispatch(dnsCtx, dest)
  66. select {
  67. case <-ctx.Done():
  68. return nil, ctx.Err()
  69. default:
  70. }
  71. if err != nil {
  72. return nil, err
  73. }
  74. cc := common.ChainedClosable{}
  75. if cw, ok := link.Writer.(common.Closable); ok {
  76. cc = append(cc, cw)
  77. }
  78. if cr, ok := link.Reader.(common.Closable); ok {
  79. cc = append(cc, cr)
  80. }
  81. conn = cnc.NewConnection(
  82. cnc.ConnectionInputMulti(link.Writer),
  83. cnc.ConnectionOutputMulti(link.Reader),
  84. cnc.ConnectionOnClose(cc),
  85. )
  86. } else {
  87. log.Record(&log.AccessMessage{
  88. From: "DNS",
  89. To: s.dohURL,
  90. Status: log.AccessAccepted,
  91. Detour: "local",
  92. })
  93. conn, err = internet.DialSystem(ctx, dest, nil)
  94. if err != nil {
  95. return nil, err
  96. }
  97. }
  98. if !h2c {
  99. conn = utls.UClient(conn, &utls.Config{ServerName: url.Hostname()}, utls.HelloChrome_Auto)
  100. if err := conn.(*utls.UConn).HandshakeContext(ctx); err != nil {
  101. return nil, err
  102. }
  103. }
  104. return conn, nil
  105. },
  106. },
  107. }
  108. return s
  109. }
  110. // Name implements Server.
  111. func (s *DoHNameServer) Name() string {
  112. return s.cacheController.name
  113. }
  114. func (s *DoHNameServer) newReqID() uint16 {
  115. return 0
  116. }
  117. func (s *DoHNameServer) sendQuery(ctx context.Context, noResponseErrCh chan<- error, domain string, option dns_feature.IPOption) {
  118. errors.LogInfo(ctx, s.Name(), " querying: ", domain)
  119. if s.Name()+"." == "DOH//"+domain {
  120. errors.LogError(ctx, s.Name(), " tries to resolve itself! Use IP or set \"hosts\" instead.")
  121. noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
  122. return
  123. }
  124. // As we don't want our traffic pattern looks like DoH, we use Random-Length Padding instead of Block-Length Padding recommended in RFC 8467
  125. // Although DoH server like 1.1.1.1 will pad the response to Block-Length 468, at least it is better than no padding for response at all
  126. reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
  127. var deadline time.Time
  128. if d, ok := ctx.Deadline(); ok {
  129. deadline = d
  130. } else {
  131. deadline = time.Now().Add(time.Second * 5)
  132. }
  133. for _, req := range reqs {
  134. go func(r *dnsRequest) {
  135. // generate new context for each req, using same context
  136. // may cause reqs all aborted if any one encounter an error
  137. dnsCtx := ctx
  138. // reserve internal dns server requested Inbound
  139. if inbound := session.InboundFromContext(ctx); inbound != nil {
  140. dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
  141. }
  142. dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
  143. Protocol: "https",
  144. SkipDNSResolve: true,
  145. })
  146. // forced to use mux for DOH
  147. // dnsCtx = session.ContextWithMuxPreferred(dnsCtx, true)
  148. var cancel context.CancelFunc
  149. dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
  150. defer cancel()
  151. b, err := dns.PackMessage(r.msg)
  152. if err != nil {
  153. errors.LogErrorInner(ctx, err, "failed to pack dns query for ", domain)
  154. noResponseErrCh <- err
  155. return
  156. }
  157. resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
  158. if err != nil {
  159. errors.LogErrorInner(ctx, err, "failed to retrieve response for ", domain)
  160. noResponseErrCh <- err
  161. return
  162. }
  163. rec, err := parseResponse(resp)
  164. if err != nil {
  165. errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", domain)
  166. noResponseErrCh <- err
  167. return
  168. }
  169. s.cacheController.updateIP(r, rec)
  170. }(req)
  171. }
  172. }
  173. func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
  174. body := bytes.NewBuffer(b)
  175. req, err := http.NewRequest("POST", s.dohURL, body)
  176. if err != nil {
  177. return nil, err
  178. }
  179. req.Header.Add("Accept", "application/dns-message")
  180. req.Header.Add("Content-Type", "application/dns-message")
  181. req.Header.Set("X-Padding", strings.Repeat("X", int(crypto.RandBetween(100, 1000))))
  182. hc := s.httpClient
  183. resp, err := hc.Do(req.WithContext(ctx))
  184. if err != nil {
  185. return nil, err
  186. }
  187. defer resp.Body.Close()
  188. if resp.StatusCode != http.StatusOK {
  189. io.Copy(io.Discard, resp.Body) // flush resp.Body so that the conn is reusable
  190. return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode)
  191. }
  192. return io.ReadAll(resp.Body)
  193. }
  194. // QueryIP implements Server.
  195. func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { // nolint: dupl
  196. fqdn := Fqdn(domain)
  197. sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
  198. defer closeSubscribers(sub4, sub6)
  199. if s.cacheController.disableCache {
  200. errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
  201. } else {
  202. ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
  203. if !go_errors.Is(err, errRecordNotFound) {
  204. errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
  205. log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
  206. return ips, ttl, err
  207. }
  208. }
  209. noResponseErrCh := make(chan error, 2)
  210. s.sendQuery(ctx, noResponseErrCh, fqdn, option)
  211. start := time.Now()
  212. if sub4 != nil {
  213. select {
  214. case <-ctx.Done():
  215. return nil, 0, ctx.Err()
  216. case err := <-noResponseErrCh:
  217. return nil, 0, err
  218. case <-sub4.Wait():
  219. sub4.Close()
  220. }
  221. }
  222. if sub6 != nil {
  223. select {
  224. case <-ctx.Done():
  225. return nil, 0, ctx.Err()
  226. case err := <-noResponseErrCh:
  227. return nil, 0, err
  228. case <-sub6.Wait():
  229. sub6.Close()
  230. }
  231. }
  232. ips, ttl, err := s.cacheController.findIPsForDomain(fqdn, option)
  233. log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
  234. return ips, ttl, err
  235. }