awl/pkg/resolvers/HTTPS.go
Sam Therapy b1fa25a9a0
All checks were successful
continuous-integration/drone/push Build is passing
feat(dig): Dig +https (#147)
BREAKING CHANGE: DNS-over-HTTPS requests are now dealt with differently

Using +https or -H now implies adding /dns-query (like dig)
Using the implied https:// prefix follows the old behaviour (nothing added or implied)

Reviewed-on: #147
Reviewed-by: grumbulon <grumbulon@grumbulon.xyz>
2022-10-20 21:34:24 +00:00

109 lines
2.4 KiB
Go

// SPDX-License-Identifier: BSD-3-Clause
package resolvers
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"net/http"
"time"
"git.froth.zone/sam/awl/pkg/util"
"github.com/miekg/dns"
)
// HTTPSResolver is for DNS-over-HTTPS queries.
type HTTPSResolver struct {
opts *util.Options
client http.Client
}
var _ Resolver = (*HTTPSResolver)(nil)
// LookUp performs a DNS query.
func (resolver *HTTPSResolver) LookUp(msg *dns.Msg) (util.Response, error) {
var resp util.Response
resolver.client = http.Client{
Timeout: resolver.opts.Request.Timeout,
Transport: &http.Transport{
MaxConnsPerHost: 1,
MaxIdleConns: 1,
MaxIdleConnsPerHost: 1,
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{
//nolint:gosec // This is intentional if the user requests it
InsecureSkipVerify: resolver.opts.TLSNoVerify,
ServerName: resolver.opts.TLSHost,
},
},
}
buf, err := msg.Pack()
if err != nil {
return util.Response{}, fmt.Errorf("doh: packing: %w", err)
}
resolver.opts.Logger.Debug("https: sending HTTPS request")
var method string
if resolver.opts.HTTPSOptions.Get {
method = "GET"
} else {
method = "POST"
}
req, err := http.NewRequest(method, resolver.opts.Request.Server, bytes.NewBuffer(buf))
if err != nil {
return util.Response{}, fmt.Errorf("doh: request creation: %w", err)
}
req.Header.Set("Content-Type", "application/dns-message")
req.Header.Set("Accept", "application/dns-message")
now := time.Now()
res, err := resolver.client.Do(req)
resp.RTT = time.Since(now)
if err != nil {
return util.Response{}, fmt.Errorf("doh: HTTP request: %w", err)
}
if res.StatusCode != http.StatusOK {
return util.Response{}, &ErrHTTPStatus{res.StatusCode}
}
resolver.opts.Logger.Debug("https: reading response")
fullRes, err := io.ReadAll(res.Body)
if err != nil {
return util.Response{}, fmt.Errorf("doh: body read: %w", err)
}
err = res.Body.Close()
if err != nil {
return util.Response{}, fmt.Errorf("doh: body close: %w", err)
}
resolver.opts.Logger.Debug("https: unpacking response")
resp.DNS = &dns.Msg{}
err = resp.DNS.Unpack(fullRes)
if err != nil {
return util.Response{}, fmt.Errorf("doh: dns message unpack: %w", err)
}
return resp, nil
}
// ErrHTTPStatus is returned when DoH returns a bad status code.
type ErrHTTPStatus struct {
code int
}
func (e *ErrHTTPStatus) Error() string {
return fmt.Sprintf("doh server responded with HTTP %d", e.code)
}