http.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "log"
  8. "net/http"
  9. "strconv"
  10. "sync"
  11. "time"
  12. "www.bamsoftware.com/git/dnstt.git/turbotunnel"
  13. )
  14. // A default Retry-After delay to use when there is no explicit Retry-After
  15. // header in an HTTP response.
  16. const defaultRetryAfter = 10 * time.Second
  17. // The *http.Client shared by instances of HTTPPacketConn. We use this instead
  18. // of http.DefaultClient in order to set a timeout.
  19. var httpClient = &http.Client{Timeout: 1 * time.Minute}
  20. // HTTPPacketConn is an HTTP-based transport for DNS messages, used for DNS over
  21. // HTTPS (DoH). Its WriteTo and ReadFrom methods exchange DNS messages over HTTP
  22. // requests and responses.
  23. //
  24. // HTTPPacketConn deals only with already formatted DNS messages. It does not
  25. // handle encoding information into the messages. That is rather the
  26. // responsibility of DNSPacketConn.
  27. //
  28. // https://tools.ietf.org/html/rfc8484
  29. type HTTPPacketConn struct {
  30. // urlString is the URL to which HTTP requests will be sent, for example
  31. // "https://doh.example/dns-query".
  32. urlString string
  33. // notBefore, if not zero, is a time before which we may not send any
  34. // queries; queries are buffered or dropped until that time. notBefore
  35. // is set when we get a 429 Too Many Requests HTTP response or other
  36. // unexpected status code that causes us to need to slow down. It is set
  37. // according to the Retry-After header if available, otherwise it is set
  38. // to defaultRetryAfter in the future. notBeforeLock controls access to
  39. // notBefore.
  40. notBefore time.Time
  41. notBeforeLock sync.RWMutex
  42. // QueuePacketConn is the direct receiver of ReadFrom and WriteTo calls.
  43. // sendLoop, via send, removes messages from the outgoing queue that
  44. // were placed there by WriteTo, and inserts messages into the incoming
  45. // queue to be returned from ReadFrom.
  46. *turbotunnel.QueuePacketConn
  47. }
  48. // NewHTTPPacketConn creates a new HTTPPacketConn configured to use the HTTP
  49. // server at urlString as a DNS over HTTP resolver. urlString should include any
  50. // necessary path components; e.g., "/dns-query". numSenders is the number of
  51. // concurrent sender-receiver goroutines to run.
  52. func NewHTTPPacketConn(urlString string, numSenders int) (*HTTPPacketConn, error) {
  53. c := &HTTPPacketConn{
  54. urlString: urlString,
  55. QueuePacketConn: turbotunnel.NewQueuePacketConn(turbotunnel.DummyAddr{}, 0),
  56. }
  57. for i := 0; i < numSenders; i++ {
  58. go c.sendLoop()
  59. }
  60. return c, nil
  61. }
  62. // send sends a message in an HTTP request, and queues the body HTTP response to
  63. // be returned from a future call to ReadFrom.
  64. func (c *HTTPPacketConn) send(p []byte) error {
  65. req, err := http.NewRequest("POST", c.urlString, bytes.NewReader(p))
  66. if err != nil {
  67. return err
  68. }
  69. req.Header.Set("Accept", "application/dns-message")
  70. req.Header.Set("Content-Type", "application/dns-message")
  71. req.Header.Set("User-Agent", "") // Disable default "Go-http-client/1.1".
  72. resp, err := httpClient.Do(req)
  73. if err != nil {
  74. return err
  75. }
  76. defer resp.Body.Close()
  77. switch resp.StatusCode {
  78. case http.StatusOK:
  79. if ct := resp.Header.Get("Content-Type"); ct != "application/dns-message" {
  80. return fmt.Errorf("unknown HTTP response Content-Type %+q", ct)
  81. }
  82. body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 64000))
  83. if err == nil {
  84. c.QueuePacketConn.QueueIncoming(body, turbotunnel.DummyAddr{})
  85. }
  86. // Ignore err != nil; don't report an error if we at least
  87. // managed to send.
  88. default:
  89. // We primarily are thinking of 429 Too Many Requests here, but
  90. // any other unexpected response codes will also cause us to
  91. // rate-limit ourselves and emit a log message.
  92. // https://developers.google.com/speed/public-dns/docs/doh/#errors
  93. now := time.Now()
  94. var retryAfter time.Time
  95. if value := resp.Header.Get("Retry-After"); value != "" {
  96. var err error
  97. retryAfter, err = parseRetryAfter(value, now)
  98. if err != nil {
  99. log.Printf("cannot parse Retry-After value %+q", value)
  100. }
  101. }
  102. if retryAfter.IsZero() {
  103. // Supply a default.
  104. retryAfter = now.Add(defaultRetryAfter)
  105. }
  106. if retryAfter.Before(now) {
  107. log.Printf("got %+q, but Retry-After is %v in the past",
  108. resp.Status, now.Sub(retryAfter))
  109. } else {
  110. c.notBeforeLock.Lock()
  111. if retryAfter.Before(c.notBefore) {
  112. log.Printf("got %+q, but Retry-After is %v earlier than already received Retry-After",
  113. resp.Status, c.notBefore.Sub(retryAfter))
  114. } else {
  115. log.Printf("got %+q; ceasing sending for %v",
  116. resp.Status, retryAfter.Sub(now))
  117. c.notBefore = retryAfter
  118. }
  119. c.notBeforeLock.Unlock()
  120. }
  121. }
  122. return nil
  123. }
  124. // sendLoop loops over the contents of the outgoing queue and passes them to
  125. // send. It drops packets while c.notBefore is in the future.
  126. func (c *HTTPPacketConn) sendLoop() {
  127. for p := range c.QueuePacketConn.OutgoingQueue(turbotunnel.DummyAddr{}) {
  128. // Stop sending while we are rate-limiting ourselves (as a
  129. // result of a Retry-After response header, for example).
  130. c.notBeforeLock.RLock()
  131. notBefore := c.notBefore
  132. c.notBeforeLock.RUnlock()
  133. if wait := notBefore.Sub(time.Now()); wait > 0 {
  134. // Drop it.
  135. continue
  136. }
  137. err := c.send(p)
  138. if err != nil {
  139. log.Printf("sendLoop: %v", err)
  140. }
  141. }
  142. }
  143. // parseRetryAfter parses the value of a Retry-After header as an absolute
  144. // time.Time.
  145. func parseRetryAfter(value string, now time.Time) (time.Time, error) {
  146. // May be a date string or an integer number of seconds.
  147. // https://tools.ietf.org/html/rfc7231#section-7.1.3
  148. if t, err := http.ParseTime(value); err == nil {
  149. return t, nil
  150. }
  151. i, err := strconv.ParseUint(value, 10, 32)
  152. if err != nil {
  153. return time.Time{}, err
  154. }
  155. return now.Add(time.Duration(i) * time.Second), nil
  156. }