feat(ednsdiag): add DoQ/DoH3/DNSCrypt transports, proxy support, probe & compare
This commit is contained in:
@@ -2,11 +2,17 @@ package edns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type queryExchange func(context.Context, Provider, []byte, QueryOptions) ([]byte, TransportInfo, dnsCryptPeerInfo, error)
|
||||
|
||||
func Query(ctx context.Context, options QueryOptions) Result {
|
||||
return queryWithExchange(ctx, options, exchangeProtocol)
|
||||
}
|
||||
|
||||
func queryWithExchange(ctx context.Context, options QueryOptions, exchange queryExchange) Result {
|
||||
wire, query, transactionID, err := BuildQuery(options.Name, options.RecordType)
|
||||
result := Result{
|
||||
SchemaVersion: 1,
|
||||
@@ -30,41 +36,76 @@ func Query(ctx context.Context, options QueryOptions) Result {
|
||||
result.Error = &ErrorInfo{Class: "input", Message: err.Error()}
|
||||
return result
|
||||
}
|
||||
if options.EndpointURL != "" {
|
||||
if options.Protocol != "doh" {
|
||||
result.Error = &ErrorInfo{Class: "input", Message: "custom --url applies only to DoH"}
|
||||
return result
|
||||
}
|
||||
parsed, err := url.Parse(options.EndpointURL)
|
||||
if err != nil || parsed.Scheme != "https" || parsed.Hostname() == "" {
|
||||
result.Error = &ErrorInfo{Class: "input", Message: fmt.Sprintf("invalid DoH endpoint URL %q", options.EndpointURL)}
|
||||
return result
|
||||
}
|
||||
provider = Provider{ID: "custom", Profile: "custom", DoHURL: options.EndpointURL}
|
||||
if err := ValidateProxyURL(options.Proxy); err != nil {
|
||||
result.Error = &ErrorInfo{Class: "input", Message: err.Error()}
|
||||
return result
|
||||
}
|
||||
if options.Proxy != "" && options.Protocol != "doh" && options.Protocol != "dot" {
|
||||
result.Error = &ErrorInfo{Class: "unsupported", Message: fmt.Sprintf("proxying is not available for protocol %q", options.Protocol)}
|
||||
return result
|
||||
}
|
||||
result.Resolver = ResolverInfo{Provider: provider.ID, Profile: provider.Profile}
|
||||
|
||||
var response []byte
|
||||
switch options.Protocol {
|
||||
case "doh":
|
||||
result.Resolver.Endpoint = provider.DoHURL
|
||||
response, result.Transport, err = exchangeDoH(ctx, provider, wire, options.Method)
|
||||
case "dot":
|
||||
result.Resolver.Endpoint = provider.DoTAddr
|
||||
response, result.Transport, err = exchangeDoT(ctx, provider, wire)
|
||||
default:
|
||||
err = fmt.Errorf("protocol %q is not available; run ednsdiag capabilities", options.Protocol)
|
||||
endpoint, err := provider.Endpoint(options.Protocol)
|
||||
if err != nil {
|
||||
class := "input"
|
||||
if IsUnsupported(err) {
|
||||
class = "unsupported"
|
||||
}
|
||||
result.Error = &ErrorInfo{Class: class, Message: err.Error()}
|
||||
return result
|
||||
}
|
||||
result.Resolver.Endpoint = endpoint
|
||||
if options.Protocol == "doh" || options.Protocol == "doh3" || options.Protocol == "doq" {
|
||||
binary.BigEndian.PutUint16(wire[:2], 0)
|
||||
transactionID = 0
|
||||
}
|
||||
|
||||
response, transport, peer, err := exchange(ctx, provider, wire, options)
|
||||
result.Transport = transport
|
||||
if err != nil {
|
||||
result.Error = &ErrorInfo{Class: "transport", Message: err.Error()}
|
||||
return result
|
||||
}
|
||||
if options.Protocol == "dnscrypt" {
|
||||
result.Resolver.Endpoint = peer.ServerAddress
|
||||
result.Resolver.AuthenticationName = peer.ProviderName
|
||||
result.Resolver.CertificateSerial = peer.CertificateSerial
|
||||
}
|
||||
|
||||
result.DNS, err = ParseResponse(response, transactionID, query)
|
||||
if err != nil {
|
||||
result.Error = &ErrorInfo{Class: "protocol", Message: err.Error()}
|
||||
return result
|
||||
}
|
||||
applyHTTPAge(&result.DNS, result.Transport.HTTPAgeSeconds)
|
||||
result.Completed = true
|
||||
return result
|
||||
}
|
||||
|
||||
func exchangeProtocol(ctx context.Context, provider Provider, wire []byte, options QueryOptions) ([]byte, TransportInfo, dnsCryptPeerInfo, error) {
|
||||
var response []byte
|
||||
var transport TransportInfo
|
||||
var peer dnsCryptPeerInfo
|
||||
var err error
|
||||
switch options.Protocol {
|
||||
case "doh":
|
||||
response, transport, err = exchangeDoH(ctx, provider, wire, options.Method, options.Proxy)
|
||||
case "dot":
|
||||
response, transport, err = exchangeDoT(ctx, provider, wire, options.Proxy)
|
||||
case "doq":
|
||||
response, transport, err = exchangeDoQ(ctx, provider, wire)
|
||||
case "doh3":
|
||||
response, transport, err = exchangeDoH3(ctx, provider, wire, options.Method)
|
||||
case "dnscrypt":
|
||||
response, transport, peer, err = exchangeDNSCrypt(ctx, provider.DNSCryptStamp, wire)
|
||||
default:
|
||||
err = fmt.Errorf("protocol %q is not available; run ednsdiag capabilities", options.Protocol)
|
||||
}
|
||||
return response, transport, peer, err
|
||||
}
|
||||
|
||||
func Probe(ctx context.Context, options QueryOptions) Result {
|
||||
result := Query(ctx, options)
|
||||
result.Operation = "probe"
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user