dohclient.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tsdial
  4. import (
  5. "bytes"
  6. "context"
  7. "errors"
  8. "fmt"
  9. "io"
  10. "net"
  11. "net/http"
  12. "time"
  13. "tailscale.com/net/dnscache"
  14. )
  15. // dohConn is a net.PacketConn suitable for returning from
  16. // net.Dialer.Dial to send DNS queries over PeerAPI to exit nodes'
  17. // ExitDNS DoH proxy service.
  18. type dohConn struct {
  19. ctx context.Context
  20. baseURL string
  21. hc *http.Client // if nil, default is used
  22. dnsCache *dnscache.MessageCache
  23. rbuf bytes.Buffer
  24. }
  25. var (
  26. _ net.Conn = (*dohConn)(nil)
  27. _ net.PacketConn = (*dohConn)(nil) // be a PacketConn to change net.Resolver semantics
  28. )
  29. func (*dohConn) Close() error { return nil }
  30. func (*dohConn) LocalAddr() net.Addr { return todoAddr{} }
  31. func (*dohConn) RemoteAddr() net.Addr { return todoAddr{} }
  32. func (*dohConn) SetDeadline(t time.Time) error { return nil }
  33. func (*dohConn) SetReadDeadline(t time.Time) error { return nil }
  34. func (*dohConn) SetWriteDeadline(t time.Time) error { return nil }
  35. func (c *dohConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
  36. return c.Write(p)
  37. }
  38. func (c *dohConn) ReadFrom(p []byte) (n int, addr net.Addr, err error) {
  39. n, err = c.Read(p)
  40. return n, todoAddr{}, err
  41. }
  42. func (c *dohConn) Read(p []byte) (n int, err error) {
  43. return c.rbuf.Read(p)
  44. }
  45. func (c *dohConn) Write(packet []byte) (n int, err error) {
  46. if c.dnsCache != nil {
  47. err := c.dnsCache.ReplyFromCache(&c.rbuf, packet)
  48. if err == nil {
  49. // Cache hit.
  50. // TODO(bradfitz): add clientmetric
  51. return len(packet), nil
  52. }
  53. c.rbuf.Reset()
  54. }
  55. req, err := http.NewRequestWithContext(c.ctx, "POST", c.baseURL, bytes.NewReader(packet))
  56. if err != nil {
  57. return 0, err
  58. }
  59. const dohType = "application/dns-message"
  60. req.Header.Set("Content-Type", dohType)
  61. hc := c.hc
  62. if hc == nil {
  63. hc = http.DefaultClient
  64. }
  65. hres, err := hc.Do(req)
  66. if err != nil {
  67. return 0, err
  68. }
  69. defer hres.Body.Close()
  70. if hres.StatusCode != 200 {
  71. return 0, errors.New(hres.Status)
  72. }
  73. if ct := hres.Header.Get("Content-Type"); ct != dohType {
  74. return 0, fmt.Errorf("unexpected response Content-Type %q", ct)
  75. }
  76. _, err = io.Copy(&c.rbuf, hres.Body)
  77. if err != nil {
  78. return 0, err
  79. }
  80. if c.dnsCache != nil {
  81. c.dnsCache.AddCacheEntry(packet, c.rbuf.Bytes())
  82. }
  83. return len(packet), nil
  84. }
  85. type todoAddr struct{}
  86. func (todoAddr) Network() string { return "unused" }
  87. func (todoAddr) String() string { return "unused-todoAddr" }