nameserver_doh.go 7.0 KB

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