safing-portmaster/resolver/resolver-https.go
Vladimir Stoilov bdc3792d21 DoT support for domain name only
Configed resolvers skip ther own domains
2022-07-20 16:06:24 +02:00

140 lines
3 KiB
Go

package resolver
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"strconv"
"github.com/miekg/dns"
)
// HttpsResolver is a resolver using just a single tcp connection with pipelining.
type HttpsResolver struct {
BasicResolverConn
Client *http.Client
}
// HttpsQuery holds the query information for a httpsResolverConn.
type HttpsQuery struct {
Query *Query
Response chan *dns.Msg
}
// MakeCacheRecord creates an RRCache record from a reply.
func (tq *HttpsQuery) MakeCacheRecord(reply *dns.Msg, resolverInfo *ResolverInfo) *RRCache {
return &RRCache{
Domain: tq.Query.FQDN,
Question: tq.Query.QType,
RCode: reply.Rcode,
Answer: reply.Answer,
Ns: reply.Ns,
Extra: reply.Extra,
Resolver: resolverInfo.Copy(),
}
}
// NewHTTPSResolver returns a new HttpsResolver.
func NewHTTPSResolver(resolver *Resolver) *HttpsResolver {
tr := &http.Transport{}
if resolver.ServerAddress != "" {
tr = &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: resolver.VerifyDomain,
// TODO: use portbase rng
},
}
}
client := &http.Client{Transport: tr}
newResolver := &HttpsResolver{
BasicResolverConn: BasicResolverConn{
resolver: resolver,
},
Client: client,
}
newResolver.BasicResolverConn.init()
return newResolver
}
// Query executes the given query against the resolver.
func (hr *HttpsResolver) Query(ctx context.Context, q *Query) (*RRCache, error) {
// Do not resolve domain names that are needed to initialize a resolver
if hr.resolver.Info.IP == nil {
if _, ok := resolverInitDomains[q.FQDN[:len(q.FQDN)-1]]; ok {
return nil, ErrContinue
}
}
dnsQuery := new(dns.Msg)
dnsQuery.SetQuestion(q.FQDN, uint16(q.QType))
// Pack query and convert to base64 string
buf, err := dnsQuery.Pack()
if err != nil {
return nil, err
}
b64dns := base64.RawStdEncoding.EncodeToString(buf)
// Set the host, if we dont have IP address just use the domain
host := hr.resolver.ServerAddress
if host == "" {
host = net.JoinHostPort(hr.resolver.VerifyDomain, strconv.Itoa(int(hr.resolver.Info.Port)))
}
// Build and execute http reuqest
url := &url.URL{
Scheme: "https",
Host: host,
Path: hr.resolver.Path,
ForceQuery: true,
RawQuery: fmt.Sprintf("dns=%s", b64dns),
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url.String(), nil)
if err != nil {
return nil, err
}
resp, err := hr.Client.Do(request)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// Try to read the result
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
reply := new(dns.Msg)
err = reply.Unpack(body)
if err != nil {
return nil, err
}
newRecord := &RRCache{
Domain: q.FQDN,
Question: q.QType,
RCode: reply.Rcode,
Answer: reply.Answer,
Ns: reply.Ns,
Extra: reply.Extra,
Resolver: hr.resolver.Info.Copy(),
}
// TODO: check if reply.Answer is valid
return newRecord, nil
}