Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #36 from projectdiscovery/feature-adding-doh
Adding support for DOH (json web api + RFC8484)
- Loading branch information
Showing
5 changed files
with
249 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
package doh | ||
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/miekg/dns" | ||
"github.com/projectdiscovery/retryablehttp-go" | ||
) | ||
|
||
type Client struct { | ||
DefaultResolver Resolver | ||
httpClient *retryablehttp.Client | ||
} | ||
|
||
func NewWithOptions(options Options) *Client { | ||
return &Client{DefaultResolver: options.DefaultResolver, httpClient: options.httpClient} | ||
} | ||
|
||
func New() *Client { | ||
return NewWithOptions(Options{DefaultResolver: Cloudflare, httpClient: retryablehttp.NewClient(retryablehttp.DefaultOptionsSingle)}) | ||
} | ||
|
||
func (c *Client) Query(name string, question QuestionType) (*Response, error) { | ||
return c.QueryWithResolver(c.DefaultResolver, name, question) | ||
} | ||
|
||
func (c *Client) QueryWithResolver(r Resolver, name string, question QuestionType) (*Response, error) { | ||
return c.QueryWithJsonAPI(r, name, question) | ||
} | ||
|
||
func (c *Client) QueryWithJsonAPI(r Resolver, name string, question QuestionType) (*Response, error) { | ||
req, err := retryablehttp.NewRequest(http.MethodGet, r.URL, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
req.Header.Set("Accept", "application/dns-json") | ||
q := req.URL.Query() | ||
q.Add("name", name) | ||
q.Add("type", question.ToString()) | ||
req.URL.RawQuery = q.Encode() | ||
|
||
resp, err := c.httpClient.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.Body == nil { | ||
return nil, errors.New("empty response body") | ||
} | ||
|
||
var response Response | ||
|
||
err = json.NewDecoder(resp.Body).Decode(&response) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &response, nil | ||
} | ||
|
||
func (c *Client) QueryWithDOH(method Method, r Resolver, name string, question uint16) (*dns.Msg, error) { | ||
msg := &dns.Msg{} | ||
msg.Id = 0 | ||
msg.Question = make([]dns.Question, 1) | ||
msg.Question[0] = dns.Question{ | ||
Name: dns.Fqdn(name), | ||
Qtype: question, | ||
Qclass: dns.ClassINET, | ||
} | ||
return c.QueryWithDOHMsg(method, r, msg) | ||
} | ||
|
||
func (c *Client) QueryWithDOHMsg(method Method, r Resolver, msg *dns.Msg) (*dns.Msg, error) { | ||
packedMsg, err := msg.Pack() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
var body []byte | ||
var dnsParam string | ||
switch method { | ||
case MethodPost: | ||
dnsParam = "" | ||
body = packedMsg | ||
case MethodGet: | ||
dnsParam = base64.RawURLEncoding.EncodeToString(packedMsg) | ||
body = nil | ||
default: | ||
return nil, errors.New("unsupported method") | ||
} | ||
req, err := retryablehttp.NewRequest(string(method), r.URL, bytes.NewReader(body)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
req.Header.Set("Accept", "application/dns-message") | ||
if dnsParam != "" { | ||
q := req.URL.Query() | ||
q.Add("dns", dnsParam) | ||
req.URL.RawQuery = q.Encode() | ||
} else if len(body) > 0 { | ||
req.Header.Set("Content-Type", "application/dns-message") | ||
} | ||
|
||
resp, err := c.httpClient.Do(req) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
|
||
if resp.Body == nil { | ||
return nil, errors.New("empty response body") | ||
} | ||
|
||
respBodyBytes, err := ioutil.ReadAll(resp.Body) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
respMsg := &dns.Msg{} | ||
if err := respMsg.Unpack(respBodyBytes); err != nil { | ||
return nil, err | ||
} | ||
return respMsg, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package doh | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/miekg/dns" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestConsistentResolve(t *testing.T) { | ||
client := New() | ||
var lastAnswer string | ||
for i := 0; i < 10; i++ { | ||
d, err := client.Query("example.com", A) | ||
require.Nil(t, err, "could not resolve dns") | ||
if lastAnswer == "" { | ||
lastAnswer = d.Answer[0].Data | ||
} else { | ||
require.Equal(t, lastAnswer, d.Answer[0].Data, "got another data from previous") | ||
} | ||
} | ||
} | ||
|
||
func TestResolvers(t *testing.T) { | ||
client := New() | ||
d, err := client.QueryWithDOH(MethodGet, OpenDNS, "www.example.com", dns.TypeA) | ||
require.Nil(t, err, "could not resolve dns") | ||
require.NotNil(t, d, "could not retrieve data") | ||
d, err = client.QueryWithDOH(MethodPost, OpenDNS, "www.example.com", dns.TypeA) | ||
require.Nil(t, err, "could not resolve dns") | ||
require.NotNil(t, d, "could not retrieve data") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package doh | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
|
||
retryablehttp "github.com/projectdiscovery/retryablehttp-go" | ||
) | ||
|
||
type Options struct { | ||
DefaultResolver Resolver | ||
httpClient *retryablehttp.Client | ||
} | ||
|
||
type Resolver struct { | ||
Name string | ||
URL string | ||
} | ||
|
||
var ( | ||
Cloudflare = Resolver{Name: "Cloudflare", URL: "https://cloudflare-dns.com/dns-query"} | ||
Google = Resolver{Name: "Google", URL: "https://dns.google.com/resolve"} | ||
Quad9 = Resolver{Name: "Cloudflare", URL: "https://dns.quad9.net:5053/dns-query"} | ||
PowerDNS = Resolver{Name: "PowerDNS", URL: "https://doh.powerdns.org/dns-query"} | ||
OpenDNS = Resolver{Name: "OpenDNS", URL: "https://doh.opendns.com/dns-query"} | ||
) | ||
|
||
type QuestionType string | ||
|
||
func (q QuestionType) ToString() string { | ||
return fmt.Sprint(q) | ||
} | ||
|
||
const ( | ||
A QuestionType = "A" | ||
AAAA QuestionType = "AAAA" | ||
MX QuestionType = "MX" | ||
NS QuestionType = "NS" | ||
SOA QuestionType = "SOA" | ||
PTR QuestionType = "PTR" | ||
CNAME QuestionType = "CNAME" | ||
) | ||
|
||
type Response struct { | ||
Status int `json:"Status"` | ||
TC bool `json:"TC"` | ||
RD bool `json:"RD"` | ||
RA bool `json:"RA"` | ||
AD bool `json:"AD"` | ||
CD bool `json:"CD"` | ||
Question []Question `json:"Question"` | ||
Answer []Answer `json:"Answer"` | ||
Comment string | ||
} | ||
|
||
type Question struct { | ||
Name string `json:"name"` | ||
Type int `json:"type"` | ||
} | ||
type Answer struct { | ||
Name string `json:"name"` | ||
Type int `json:"type"` | ||
TTL int `json:"TTL"` | ||
Data string `json:"data"` | ||
} | ||
|
||
type Method string | ||
|
||
const ( | ||
MethodGet Method = http.MethodGet | ||
MethodPost Method = http.MethodPost | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters