feat(ednsdiag): add DoQ/DoH3/DNSCrypt transports, proxy support, probe & compare
This commit is contained in:
@@ -11,28 +11,46 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxDNSMessageSize = 65535
|
||||
|
||||
func exchangeDoH(ctx context.Context, provider Provider, wire []byte, method string) ([]byte, TransportInfo, error) {
|
||||
client := newDoHClient(provider.DoHURL)
|
||||
return exchangeDoHWithClient(ctx, client, provider.DoHURL, wire, method)
|
||||
func exchangeDoH(ctx context.Context, provider Provider, wire []byte, method, explicitProxy string) ([]byte, TransportInfo, error) {
|
||||
client, proxyLabel, err := newDoHClient(provider.DoHURL, explicitProxy)
|
||||
if err != nil {
|
||||
return nil, TransportInfo{Protocol: "doh", Encrypted: true, Bootstrap: "system_resolver"}, err
|
||||
}
|
||||
response, info, err := exchangeDoHWithClient(ctx, client, provider.DoHURL, wire, method)
|
||||
info.Proxy = proxyLabel
|
||||
return response, info, err
|
||||
}
|
||||
|
||||
func newDoHClient(endpoint string) *http.Client {
|
||||
origin, _ := url.Parse(endpoint)
|
||||
func newDoHClient(endpoint, explicitProxy string) (*http.Client, string, error) {
|
||||
return newDoHClientWithTLSConfig(endpoint, explicitProxy, &tls.Config{MinVersion: tls.VersionTLS12})
|
||||
}
|
||||
|
||||
func newDoHClientWithTLSConfig(endpoint, explicitProxy string, tlsConfig *tls.Config) (*http.Client, string, error) {
|
||||
origin, err := url.Parse(endpoint)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("parse DoH endpoint: %w", err)
|
||||
}
|
||||
proxyURL, err := resolveProxy(origin, explicitProxy)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("select DoH proxy: %w", err)
|
||||
}
|
||||
transport := &http.Transport{
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: (&net.Dialer{Timeout: 5 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
|
||||
TLSClientConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
},
|
||||
ForceAttemptHTTP2: true,
|
||||
DialContext: (&net.Dialer{Timeout: 5 * time.Second, KeepAlive: 30 * time.Second}).DialContext,
|
||||
TLSClientConfig: tlsConfig.Clone(),
|
||||
TLSHandshakeTimeout: 5 * time.Second,
|
||||
}
|
||||
return &http.Client{
|
||||
if proxyURL != nil {
|
||||
transport.Proxy = http.ProxyURL(proxyURL)
|
||||
}
|
||||
client := &http.Client{
|
||||
Transport: transport,
|
||||
CheckRedirect: func(request *http.Request, via []*http.Request) error {
|
||||
if len(via) >= 3 {
|
||||
@@ -47,12 +65,17 @@ func newDoHClient(endpoint string) *http.Client {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
return client, proxyDisplayURL(proxyURL), nil
|
||||
}
|
||||
|
||||
func exchangeDoHWithClient(ctx context.Context, client *http.Client, endpoint string, wire []byte, method string) ([]byte, TransportInfo, error) {
|
||||
return exchangeHTTPSDNSWithClient(ctx, client, endpoint, wire, method, "doh")
|
||||
}
|
||||
|
||||
func exchangeHTTPSDNSWithClient(ctx context.Context, client *http.Client, endpoint string, wire []byte, method, protocol string) ([]byte, TransportInfo, error) {
|
||||
started := time.Now()
|
||||
info := TransportInfo{
|
||||
Protocol: "doh",
|
||||
Protocol: protocol,
|
||||
Encrypted: true,
|
||||
Bootstrap: "system_resolver",
|
||||
}
|
||||
@@ -94,12 +117,27 @@ func exchangeDoHWithClient(ctx context.Context, client *http.Client, endpoint st
|
||||
defer response.Body.Close()
|
||||
|
||||
info.HTTPVersion = response.Proto
|
||||
if age := response.Header.Get("Age"); age != "" {
|
||||
parsedAge, err := strconv.ParseInt(age, 10, 64)
|
||||
if err != nil || parsedAge < 0 {
|
||||
return nil, info, fmt.Errorf("DoH server returned invalid Age header %q", age)
|
||||
}
|
||||
info.HTTPAgeSeconds = parsedAge
|
||||
}
|
||||
if response.TLS == nil || len(response.TLS.VerifiedChains) == 0 {
|
||||
return nil, info, fmt.Errorf("DoH server TLS identity was not verified")
|
||||
}
|
||||
info.ServerAuthenticated = true
|
||||
info.TLSVersion = tlsVersionName(response.TLS.Version)
|
||||
info.ALPN = response.TLS.NegotiatedProtocol
|
||||
if protocol == "doh3" {
|
||||
if response.ProtoMajor != 3 {
|
||||
return nil, info, fmt.Errorf("DoH3 server used unexpected HTTP version %q", response.Proto)
|
||||
}
|
||||
if info.ALPN != "h3" {
|
||||
return nil, info, fmt.Errorf("DoH3 server negotiated unexpected ALPN protocol %q", info.ALPN)
|
||||
}
|
||||
}
|
||||
|
||||
if response.StatusCode < 200 || response.StatusCode > 299 {
|
||||
return nil, info, fmt.Errorf("DoH server returned HTTP status %d", response.StatusCode)
|
||||
|
||||
Reference in New Issue
Block a user