mihomo/dns/doh.go

206 lines
4.7 KiB
Go
Raw Normal View History

2019-06-28 12:29:08 +08:00
package dns
import (
"bytes"
"context"
"crypto/tls"
2020-02-09 17:02:48 +08:00
"github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver"
2022-07-11 12:37:27 +08:00
tls2 "github.com/Dreamacro/clash/component/tls"
2022-07-16 19:52:51 +08:00
"github.com/Dreamacro/clash/log"
"github.com/lucas-clemente/quic-go"
"github.com/lucas-clemente/quic-go/http3"
2019-06-28 12:29:08 +08:00
D "github.com/miekg/dns"
"go.uber.org/atomic"
"io"
2022-07-06 20:53:34 +08:00
"io/ioutil"
"net"
"net/http"
"strconv"
2019-06-28 12:29:08 +08:00
)
const (
// dotMimeType is the DoH mimetype that should be used.
dotMimeType = "application/dns-message"
)
type dohClient struct {
2022-07-06 20:53:34 +08:00
url string
transport http.RoundTripper
2019-06-28 12:29:08 +08:00
}
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
return dc.ExchangeContext(context.Background(), m)
}
func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
// https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
// In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request.
newM := *m
newM.Id = 0
req, err := dc.newRequest(&newM)
2019-06-28 12:29:08 +08:00
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
msg, err = dc.doRequest(req)
2021-10-11 21:05:38 +08:00
if err == nil {
msg.Id = m.Id
}
return
2019-06-28 12:29:08 +08:00
}
// newRequest returns a new DoH request given a dns.Msg.
func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
buf, err := m.Pack()
if err != nil {
return nil, err
}
2020-06-01 13:43:26 +08:00
req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf))
2019-06-28 12:29:08 +08:00
if err != nil {
return req, err
}
req.Header.Set("content-type", dotMimeType)
req.Header.Set("accept", dotMimeType)
return req, nil
}
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
2022-07-06 20:53:34 +08:00
client := &http.Client{Transport: dc.transport}
2019-06-28 12:29:08 +08:00
resp, err := client.Do(req)
if err != nil {
2022-07-16 19:52:51 +08:00
log.Errorln("doh %v", err)
2022-07-06 21:25:25 +08:00
return nil, err
2019-06-28 12:29:08 +08:00
}
2019-06-28 12:29:08 +08:00
defer resp.Body.Close()
2021-10-09 20:35:06 +08:00
buf, err := io.ReadAll(resp.Body)
2019-06-28 12:29:08 +08:00
if err != nil {
return nil, err
}
2022-07-06 20:53:34 +08:00
msg = &D.Msg{}
2019-06-28 12:29:08 +08:00
err = msg.Unpack(buf)
return msg, err
}
func newDoHClient(url string, r *Resolver, preferH3 bool, proxyAdapter string) *dohClient {
return &dohClient{
2022-07-06 20:53:34 +08:00
url: url,
transport: newDohTransport(r, preferH3, proxyAdapter),
}
}
type dohTransport struct {
*http.Transport
h3 *http3.RoundTripper
preferH3 bool
canUseH3 atomic.Bool
}
func newDohTransport(r *Resolver, preferH3 bool, proxyAdapter string) *dohTransport {
dohT := &dohTransport{
Transport: &http.Transport{
2020-06-01 13:43:26 +08:00
ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil {
return nil, err
}
if proxyAdapter == "" {
return dialer.DialContext(ctx, "tcp", net.JoinHostPort(ip.String(), port))
} else {
return dialContextExtra(ctx, proxyAdapter, "tcp", ip, port)
2021-11-17 16:03:47 +08:00
}
},
2022-07-10 20:44:24 +08:00
TLSClientConfig: tls2.GetDefaultTLSConfig(),
},
2022-07-06 20:53:34 +08:00
preferH3: preferH3,
}
2022-07-06 20:53:34 +08:00
dohT.canUseH3.Store(preferH3)
if preferH3 {
dohT.h3 = &http3.RoundTripper{
Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveIPWithResolver(host, r)
if err != nil {
return nil, err
}
2022-07-16 19:52:51 +08:00
if proxyAdapter == "" {
return quic.DialAddrEarlyContext(ctx, net.JoinHostPort(ip.String(), port), tlsCfg, cfg)
} else {
if conn, err := dialContextExtra(ctx, proxyAdapter, "udp", ip, port); err == nil {
portInt, err := strconv.Atoi(port)
if err != nil {
return nil, err
}
udpAddr := net.UDPAddr{
IP: net.ParseIP(ip.String()),
Port: portInt,
}
return quic.DialEarlyContext(ctx, conn.(net.PacketConn), &udpAddr, host, tlsCfg, cfg)
} else {
return nil, err
}
}
},
2022-07-10 20:44:24 +08:00
TLSClientConfig: tls2.GetDefaultTLSConfig(),
2022-07-06 20:53:34 +08:00
}
}
return dohT
}
func (doh *dohTransport) RoundTrip(req *http.Request) (*http.Response, error) {
var resp *http.Response
var err error
var bodyBytes []byte
2022-07-06 21:25:25 +08:00
var h3Err bool
var fallbackErr bool
defer func() {
2022-07-15 21:54:57 +08:00
if doh.preferH3 && (h3Err || fallbackErr) {
2022-07-16 19:52:51 +08:00
doh.canUseH3.Store(doh.preferH3 && (!h3Err || fallbackErr))
2022-07-06 21:25:25 +08:00
}
}()
2022-07-06 20:53:34 +08:00
if req.Body != nil {
bodyBytes, err = ioutil.ReadAll(req.Body)
}
2022-07-06 20:53:34 +08:00
req.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
2022-07-16 19:52:51 +08:00
if doh.canUseH3.Load() {
2022-07-06 20:53:34 +08:00
resp, err = doh.h3.RoundTrip(req)
2022-07-06 21:25:25 +08:00
h3Err = err != nil
if !h3Err {
2022-07-06 20:53:34 +08:00
return resp, err
} else {
req.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
}
}
resp, err = doh.Transport.RoundTrip(req)
2022-07-06 21:25:25 +08:00
fallbackErr = err != nil
if fallbackErr {
2022-07-06 20:53:34 +08:00
return resp, err
}
return resp, err
}