package resolvers import ( "bytes" "errors" "fmt" "io/ioutil" "net/http" "net/url" "time" "github.com/miekg/dns" ) // DOHResolver represents the config options for setting up a DOH based resolver. type DOHResolver struct { client *http.Client servers []string } type DOHResolverOpts struct { Timeout time.Duration } // NewDOHResolver accepts a list of nameservers and configures a DOH based resolver. func NewDOHResolver(servers []string, opts DOHResolverOpts) (Resolver, error) { if len(servers) == 0 { return nil, errors.New(`no DOH server specified`) } for _, s := range servers { u, err := url.ParseRequestURI(s) if err != nil { return nil, fmt.Errorf("%s is not a valid HTTPS nameserver", s) } if u.Scheme != "https" { return nil, fmt.Errorf("missing https in %s", s) } } httpClient := &http.Client{ Timeout: opts.Timeout, } return &DOHResolver{ client: httpClient, servers: servers, }, nil } func (d *DOHResolver) Lookup(questions []dns.Question) ([]Response, error) { var ( messages = prepareMessages(questions) responses []Response ) for _, msg := range messages { // get the DNS Message in wire format. b, err := msg.Pack() if err != nil { return nil, err } for _, srv := range d.servers { now := time.Now() // Make an HTTP POST request to the DNS server with the DNS message as wire format bytes in the body. resp, err := d.client.Post(srv, "application/dns-message", bytes.NewBuffer(b)) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("error from nameserver %s", resp.Status) } rtt := time.Since(now) // extract the binary response in DNS Message. body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } err = msg.Unpack(body) if err != nil { return nil, err } rsp := Response{ Message: msg, RTT: rtt, Nameserver: srv, } responses = append(responses, rsp) } } return responses, nil }