nameserver_doh.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  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, serveStale bool, serveExpiredTTL uint32, 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, serveStale, serveExpiredTTL),
  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. if noResponseErrCh != nil {
  122. noResponseErrCh <- errors.New("tries to resolve itself!", s.Name())
  123. }
  124. return
  125. }
  126. // 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
  127. // 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
  128. reqs := buildReqMsgs(domain, option, s.newReqID, genEDNS0Options(s.clientIP, int(crypto.RandBetween(100, 300))))
  129. var deadline time.Time
  130. if d, ok := ctx.Deadline(); ok {
  131. deadline = d
  132. } else {
  133. deadline = time.Now().Add(time.Second * 5)
  134. }
  135. for _, req := range reqs {
  136. go func(r *dnsRequest) {
  137. // generate new context for each req, using same context
  138. // may cause reqs all aborted if any one encounter an error
  139. dnsCtx := ctx
  140. // reserve internal dns server requested Inbound
  141. if inbound := session.InboundFromContext(ctx); inbound != nil {
  142. dnsCtx = session.ContextWithInbound(dnsCtx, inbound)
  143. }
  144. dnsCtx = session.ContextWithContent(dnsCtx, &session.Content{
  145. Protocol: "https",
  146. SkipDNSResolve: true,
  147. })
  148. // forced to use mux for DOH
  149. // dnsCtx = session.ContextWithMuxPreferred(dnsCtx, true)
  150. var cancel context.CancelFunc
  151. dnsCtx, cancel = context.WithDeadline(dnsCtx, deadline)
  152. defer cancel()
  153. b, err := dns.PackMessage(r.msg)
  154. if err != nil {
  155. errors.LogErrorInner(ctx, err, "failed to pack dns query for ", domain)
  156. if noResponseErrCh != nil {
  157. noResponseErrCh <- err
  158. }
  159. return
  160. }
  161. resp, err := s.dohHTTPSContext(dnsCtx, b.Bytes())
  162. if err != nil {
  163. errors.LogErrorInner(ctx, err, "failed to retrieve response for ", domain)
  164. if noResponseErrCh != nil {
  165. noResponseErrCh <- err
  166. }
  167. return
  168. }
  169. rec, err := parseResponse(resp)
  170. if err != nil {
  171. errors.LogErrorInner(ctx, err, "failed to handle DOH response for ", domain)
  172. if noResponseErrCh != nil {
  173. noResponseErrCh <- err
  174. }
  175. return
  176. }
  177. s.cacheController.updateIP(r, rec)
  178. }(req)
  179. }
  180. }
  181. func (s *DoHNameServer) dohHTTPSContext(ctx context.Context, b []byte) ([]byte, error) {
  182. body := bytes.NewBuffer(b)
  183. req, err := http.NewRequest("POST", s.dohURL, body)
  184. if err != nil {
  185. return nil, err
  186. }
  187. req.Header.Add("Accept", "application/dns-message")
  188. req.Header.Add("Content-Type", "application/dns-message")
  189. req.Header.Set("X-Padding", strings.Repeat("X", int(crypto.RandBetween(100, 1000))))
  190. hc := s.httpClient
  191. resp, err := hc.Do(req.WithContext(ctx))
  192. if err != nil {
  193. return nil, err
  194. }
  195. defer resp.Body.Close()
  196. if resp.StatusCode != http.StatusOK {
  197. io.Copy(io.Discard, resp.Body) // flush resp.Body so that the conn is reusable
  198. return nil, fmt.Errorf("DOH server returned code %d", resp.StatusCode)
  199. }
  200. return io.ReadAll(resp.Body)
  201. }
  202. // QueryIP implements Server.
  203. func (s *DoHNameServer) QueryIP(ctx context.Context, domain string, option dns_feature.IPOption) ([]net.IP, uint32, error) { // nolint: dupl
  204. fqdn := Fqdn(domain)
  205. sub4, sub6 := s.cacheController.registerSubscribers(fqdn, option)
  206. defer closeSubscribers(sub4, sub6)
  207. queryOption := option
  208. if s.cacheController.disableCache {
  209. errors.LogDebug(ctx, "DNS cache is disabled. Querying IP for ", domain, " at ", s.Name())
  210. } else {
  211. ips, ttl, isARecordExpired, isAAAARecordExpired, err := s.cacheController.findIPsForDomain(fqdn, option)
  212. if sub4 != nil && !isARecordExpired {
  213. sub4.Close()
  214. sub4 = nil
  215. queryOption.IPv4Enable = false
  216. }
  217. if sub6 != nil && !isAAAARecordExpired {
  218. sub6.Close()
  219. sub6 = nil
  220. queryOption.IPv6Enable = false
  221. }
  222. if !go_errors.Is(err, errRecordNotFound) {
  223. if ttl > 0 {
  224. errors.LogDebugInner(ctx, err, s.Name(), " cache HIT ", domain, " -> ", ips)
  225. log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSCacheHit, Elapsed: 0, Error: err})
  226. return ips, uint32(ttl), err
  227. }
  228. if s.cacheController.serveStale && (s.cacheController.serveExpiredTTL == 0 || s.cacheController.serveExpiredTTL < ttl) {
  229. errors.LogDebugInner(ctx, err, s.Name(), " cache OPTIMISTE ", domain, " -> ", ips)
  230. s.sendQuery(ctx, nil, fqdn, queryOption)
  231. return ips, 1, err
  232. }
  233. }
  234. }
  235. noResponseErrCh := make(chan error, 2)
  236. s.sendQuery(ctx, noResponseErrCh, fqdn, queryOption)
  237. start := time.Now()
  238. if sub4 != nil {
  239. select {
  240. case <-ctx.Done():
  241. return nil, 0, ctx.Err()
  242. case err := <-noResponseErrCh:
  243. return nil, 0, err
  244. case <-sub4.Wait():
  245. sub4.Close()
  246. }
  247. }
  248. if sub6 != nil {
  249. select {
  250. case <-ctx.Done():
  251. return nil, 0, ctx.Err()
  252. case err := <-noResponseErrCh:
  253. return nil, 0, err
  254. case <-sub6.Wait():
  255. sub6.Close()
  256. }
  257. }
  258. ips, ttl, _, _, err := s.cacheController.findIPsForDomain(fqdn, option)
  259. log.Record(&log.DNSLog{Server: s.Name(), Domain: domain, Result: ips, Status: log.DNSQueried, Elapsed: time.Since(start), Error: err})
  260. var rTTL uint32
  261. if ttl <= 0 {
  262. rTTL = 1
  263. } else {
  264. rTTL = uint32(ttl)
  265. }
  266. return ips, rTTL, err
  267. }