diff options
author | 2018-07-07 08:22:07 +0100 | |
---|---|---|
committer | 2018-07-07 08:22:07 +0100 | |
commit | 30a788fd3a3afe51945554d301386665eca705f2 (patch) | |
tree | 59c1b68cd159838ebb58747bc359add5a576dfca /plugin/pkg | |
parent | bc5090123487653ce502d5801007d03dbb6a2ba7 (diff) | |
download | coredns-30a788fd3a3afe51945554d301386665eca705f2.tar.gz coredns-30a788fd3a3afe51945554d301386665eca705f2.tar.zst coredns-30a788fd3a3afe51945554d301386665eca705f2.zip |
Doh: put in pkg/doh (#1946)
* DoH: put in pkg/doh
Factor out the DoH stuff into its own package, add function to request
a DoH response. This can be used by forward (and maybe proxy) to
implement DoH client support.
Signed-off-by: Miek Gieben <miek@miek.nl>
* lint
Signed-off-by: Miek Gieben <miek@miek.nl>
* ... and make it compile
Signed-off-by: Miek Gieben <miek@miek.nl>
Diffstat (limited to 'plugin/pkg')
-rw-r--r-- | plugin/pkg/doh/doh.go | 119 | ||||
-rw-r--r-- | plugin/pkg/doh/doh_test.go | 52 |
2 files changed, 171 insertions, 0 deletions
diff --git a/plugin/pkg/doh/doh.go b/plugin/pkg/doh/doh.go new file mode 100644 index 000000000..e0a398e9c --- /dev/null +++ b/plugin/pkg/doh/doh.go @@ -0,0 +1,119 @@ +package doh + +import ( + "bytes" + "encoding/base64" + "fmt" + "io" + "io/ioutil" + "net/http" + + "github.com/miekg/dns" +) + +// MimeType is the DoH mimetype that should be used. +const MimeType = "application/dns-message" + +// Path is the URL path that should be used. +const Path = "/dns-query" + +// NewRequest returns a new DoH request given a method, URL (without any paths, so exclude /dns-query) and dns.Msg. +func NewRequest(method, url string, m *dns.Msg) (*http.Request, error) { + buf, err := m.Pack() + if err != nil { + return nil, err + } + + switch method { + case http.MethodGet: + b64 := base64.RawURLEncoding.EncodeToString(buf) + + req, err := http.NewRequest(http.MethodGet, "https://"+url+Path+"?dns="+b64, nil) + if err != nil { + return req, err + } + + req.Header.Set("content-type", MimeType) + req.Header.Set("accept", MimeType) + return req, nil + + case http.MethodPost: + req, err := http.NewRequest(http.MethodPost, "https://"+url+Path+"?bla=foo:443", bytes.NewReader(buf)) + if err != nil { + return req, err + } + + req.Header.Set("content-type", MimeType) + req.Header.Set("accept", MimeType) + return req, nil + + default: + return nil, fmt.Errorf("method not allowed: %s", method) + } + +} + +// ResponseToMsg converts a http.Repsonse to a dns message. +func ResponseToMsg(resp *http.Response) (*dns.Msg, error) { + defer resp.Body.Close() + + return toMsg(resp.Body) +} + +// RequestToMsg converts a http.Request to a dns message. +func RequestToMsg(req *http.Request) (*dns.Msg, error) { + switch req.Method { + case http.MethodGet: + return requestToMsgGet(req) + + case http.MethodPost: + return requestToMsgPost(req) + + default: + return nil, fmt.Errorf("method not allowed: %s", req.Method) + } + +} + +// requestToMsgPost extracts the dns message from the request body. +func requestToMsgPost(req *http.Request) (*dns.Msg, error) { + defer req.Body.Close() + return toMsg(req.Body) +} + +// requestToMsgGet extract the dns message from the GET request. +func requestToMsgGet(req *http.Request) (*dns.Msg, error) { + values := req.URL.Query() + b64, ok := values["dns"] + if !ok { + return nil, fmt.Errorf("no 'dns' query parameter found") + } + if len(b64) != 1 { + return nil, fmt.Errorf("multiple 'dns' query values found") + } + return base64ToMsg(b64[0]) +} + +func toMsg(r io.ReadCloser) (*dns.Msg, error) { + buf, err := ioutil.ReadAll(r) + if err != nil { + return nil, err + } + m := new(dns.Msg) + err = m.Unpack(buf) + return m, err +} + +func base64ToMsg(b64 string) (*dns.Msg, error) { + buf, err := b64Enc.DecodeString(b64) + if err != nil { + return nil, err + } + + m := new(dns.Msg) + err = m.Unpack(buf) + + return m, err +} + +var b64Enc = base64.RawURLEncoding diff --git a/plugin/pkg/doh/doh_test.go b/plugin/pkg/doh/doh_test.go new file mode 100644 index 000000000..449166151 --- /dev/null +++ b/plugin/pkg/doh/doh_test.go @@ -0,0 +1,52 @@ +package doh + +import ( + "net/http" + "testing" + + "github.com/miekg/dns" +) + +func TestPostRequest(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion("example.org.", dns.TypeDNSKEY) + + req, err := NewRequest(http.MethodPost, "https://example.org:443", m) + if err != nil { + t.Errorf("Failure to make request: %s", err) + } + + m, err = RequestToMsg(req) + if err != nil { + t.Fatalf("Failure to get message from request: %s", err) + } + + if x := m.Question[0].Name; x != "example.org." { + t.Errorf("Qname expected %s, got %s", "example.org.", x) + } + if x := m.Question[0].Qtype; x != dns.TypeDNSKEY { + t.Errorf("Qname expected %d, got %d", x, dns.TypeDNSKEY) + } +} + +func TestGetRequest(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion("example.org.", dns.TypeDNSKEY) + + req, err := NewRequest(http.MethodGet, "https://example.org:443", m) + if err != nil { + t.Errorf("Failure to make request: %s", err) + } + + m, err = RequestToMsg(req) + if err != nil { + t.Fatalf("Failure to get message from request: %s", err) + } + + if x := m.Question[0].Name; x != "example.org." { + t.Errorf("Qname expected %s, got %s", "example.org.", x) + } + if x := m.Question[0].Qtype; x != dns.TypeDNSKEY { + t.Errorf("Qname expected %d, got %d", x, dns.TypeDNSKEY) + } +} |