mirror of
https://github.com/AdguardTeam/AdGuardDNS.git
synced 2026-01-16 19:18:21 -05:00
222 lines
5.8 KiB
Go
222 lines
5.8 KiB
Go
package dnsserver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// JSONMsg represents a *dns.Msg in the JSON format defined here:
|
|
// https://developers.google.com/speed/public-dns/docs/doh/json#dns_response_in_json
|
|
// Note, that we do not implement some parts of it. There is no "Comment" field
|
|
// and there's no "edns_client_subnet".
|
|
type JSONMsg struct {
|
|
Question []JSONQuestion `json:"Question"`
|
|
Answer []JSONAnswer `json:"Answer"`
|
|
Extra []JSONAnswer `json:"Extra"`
|
|
Truncated bool `json:"TC"`
|
|
RecursionDesired bool `json:"RD"`
|
|
RecursionAvailable bool `json:"RA"`
|
|
AuthenticatedData bool `json:"AD"`
|
|
CheckingDisabled bool `json:"CD"`
|
|
Status int `json:"Status"`
|
|
}
|
|
|
|
// JSONQuestion is a part of JSONMsg definition.
|
|
type JSONQuestion struct {
|
|
Name string `json:"name"`
|
|
Type uint16 `json:"type"`
|
|
}
|
|
|
|
// JSONAnswer is a part of JSONMsg definition.
|
|
type JSONAnswer struct {
|
|
Name string `json:"name"`
|
|
Data string `json:"data"`
|
|
TTL uint32 `json:"TTL"`
|
|
Type uint16 `json:"type"`
|
|
Class uint16 `json:"class"`
|
|
}
|
|
|
|
// DNSMsgToJSONMsg converts the *dns.Msg to the JSON format (*JSONMsg).
|
|
func DNSMsgToJSONMsg(m *dns.Msg) (msg *JSONMsg) {
|
|
msg = &JSONMsg{
|
|
Status: m.Rcode,
|
|
Truncated: m.Truncated,
|
|
RecursionDesired: m.RecursionDesired,
|
|
RecursionAvailable: m.RecursionAvailable,
|
|
AuthenticatedData: m.AuthenticatedData,
|
|
CheckingDisabled: m.CheckingDisabled,
|
|
}
|
|
|
|
for _, q := range m.Question {
|
|
msg.Question = append(msg.Question, JSONQuestion{
|
|
Name: q.Name,
|
|
Type: q.Qtype,
|
|
})
|
|
}
|
|
|
|
for _, rr := range m.Answer {
|
|
msg.Answer = append(msg.Answer, rrToJSON(rr))
|
|
}
|
|
|
|
for _, rr := range m.Extra {
|
|
msg.Extra = append(msg.Extra, rrToJSON(rr))
|
|
}
|
|
|
|
return msg
|
|
}
|
|
|
|
// rrToJSON converts the specified rr to JSONAnswer.
|
|
func rrToJSON(rr dns.RR) (j JSONAnswer) {
|
|
hdr := rr.Header()
|
|
|
|
// Extracting the RR value is a bit tricky since miekg/dns does not
|
|
// expose the necessary methods. This way we can benefit from the
|
|
// proper string serialization code that's used inside miekg/dns.
|
|
hdrStr := hdr.String()
|
|
valStr := rr.String()
|
|
data := strings.TrimLeft(strings.TrimPrefix(valStr, hdrStr), " ")
|
|
|
|
return JSONAnswer{
|
|
Name: hdr.Name,
|
|
Type: hdr.Rrtype,
|
|
TTL: hdr.Ttl,
|
|
Class: hdr.Class,
|
|
Data: data,
|
|
}
|
|
}
|
|
|
|
// dnsMsgToJSON converts the *dns.Msg to the JSON format (JSONMsg) and returns
|
|
// it in the serialized form.
|
|
func dnsMsgToJSON(m *dns.Msg) (b []byte, err error) {
|
|
msg := DNSMsgToJSONMsg(m)
|
|
return json.Marshal(msg)
|
|
}
|
|
|
|
// httpRequestToMsgJSON builds a DNS message from the request parameters.
|
|
// We use the same parameters as the ones defined here:
|
|
// https://developers.google.com/speed/public-dns/docs/doh/json#supported_parameters
|
|
// Some parameters are not supported: "ct", "edns_client_subnet".
|
|
func httpRequestToMsgJSON(req *http.Request) (b []byte, err error) {
|
|
q := req.URL.Query()
|
|
|
|
// Query name, the only required parameter.
|
|
name := q.Get("name")
|
|
if name == "" {
|
|
// Indicate that the argument is invalid
|
|
return nil, ErrInvalidArgument
|
|
}
|
|
|
|
// RR type can be represented as a number in [1, 65535] or a
|
|
// canonical string (case-insensitive, such as A or AAAA).
|
|
var t uint16
|
|
t, err = urlQueryParameterToUint16(q, "type", dns.TypeA, dns.StringToType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Query class can be represented as a number in [1, 65535] or a
|
|
// canonical string (case-insensitive).
|
|
var qc uint16
|
|
qc, err = urlQueryParameterToUint16(q, "qc", dns.ClassINET, dns.StringToClass)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The CD (Checking Disabled) flag. Use cd=1, or cd=true to disable
|
|
// DNSSEC validation; use cd=0, cd=false, or no cd parameter to
|
|
// enable DNSSEC validation.
|
|
var cd bool
|
|
cd, err = urlQueryParameterToBoolean(q, "cd", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The DO (DNSSEC OK) flag. Use do=1, or do=true to include DNSSEC
|
|
// records (RRSIG, NSEC, NSEC3); use do=0, do=false, or no do parameter
|
|
// to omit DNSSEC records.
|
|
var do bool
|
|
do, err = urlQueryParameterToBoolean(q, "do", false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Now build a DNS message with all those parameters
|
|
r := &dns.Msg{
|
|
MsgHdr: dns.MsgHdr{
|
|
Id: dns.Id(),
|
|
CheckingDisabled: cd,
|
|
RecursionDesired: true,
|
|
},
|
|
Question: []dns.Question{
|
|
{
|
|
Name: dns.Fqdn(name),
|
|
Qtype: t,
|
|
Qclass: qc,
|
|
},
|
|
},
|
|
}
|
|
|
|
if do {
|
|
r.SetEdns0(dns.MaxMsgSize, do)
|
|
}
|
|
|
|
return r.Pack()
|
|
}
|
|
|
|
// urlQueryParameterToUint16 is a helper function that extracts a uint16 value
|
|
// from a query parameter. See httpRequestToMsgJSON to see how it's used.
|
|
func urlQueryParameterToUint16(
|
|
q url.Values,
|
|
name string,
|
|
defaultValue uint16,
|
|
strValuesMap map[string]uint16,
|
|
) (v uint16, err error) {
|
|
strValue := q.Get(name)
|
|
uintValue, convErr := strconv.ParseUint(strValue, 10, 16)
|
|
switch {
|
|
case strValue == "":
|
|
// use default value if nothing was specified.
|
|
v = defaultValue
|
|
case convErr == nil:
|
|
// use the specified value if it is a valid uint16.
|
|
v = uint16(uintValue)
|
|
default:
|
|
// check if the specified string value is in the lookup map.
|
|
var ok bool
|
|
v, ok = strValuesMap[strings.ToUpper(strValue)]
|
|
if !ok {
|
|
// specified type is invalid.
|
|
return 0, ErrInvalidArgument
|
|
}
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// urlQueryParameterToBoolean is a helper function that extracts a boolean value
|
|
// from a query parameter. See httpRequestToMsgJSON to see how it's used.
|
|
func urlQueryParameterToBoolean(
|
|
q url.Values,
|
|
name string,
|
|
defaultValue bool,
|
|
) (v bool, err error) {
|
|
strValue := q.Get(name)
|
|
switch strValue {
|
|
case "1", "true", "True":
|
|
v = true
|
|
case "0", "false", "False":
|
|
v = false
|
|
case "":
|
|
v = defaultValue
|
|
default:
|
|
return defaultValue, ErrInvalidArgument
|
|
}
|
|
|
|
return v, nil
|
|
}
|