feat(forward): add doh support (#8004)

* chore(pkg/proxy): prepare for DoH implementation

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* chore(pkg/proxy): prepare for DoH implementation

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* feat(proxy): implement basic DoH resolution

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* feat(forward): implement DoH forwarding

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* feat(proxy): add basic DoH health checker

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* chore: align http transport with Go's DefaultTransport

and resolve some of the TODOs

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* docs(forward): add basic documentation for DoH

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* chore: add basic tests to cover DoH

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* chore(health): unify default timeout to 1s

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* feat(forward): make doh method configurable

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* chore: remove maxIdleConnsPerHost setting & update docs

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

* chore(forward): reject https upstreams with path

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>

---------

Signed-off-by: Thomas Gosteli <thomas.gosteli@protonmail.ch>
This commit is contained in:
Thomas Gosteli
2026-06-15 02:54:05 +02:00
committed by GitHub
parent 3764620726
commit f2f5b5a1cc
12 changed files with 363 additions and 36 deletions

View File

@@ -6,12 +6,15 @@ package proxy
import (
"context"
"errors"
"fmt"
"io"
"strconv"
"strings"
"sync/atomic"
"time"
"github.com/coredns/coredns/plugin/pkg/doh"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/coredns/coredns/request"
"github.com/miekg/dns"
@@ -102,10 +105,7 @@ func (t *Transport) Dial(proto string) (*persistConn, bool, error) {
return &persistConn{c: conn, created: time.Now()}, false, err
}
// Connect selects an upstream, sends the request and waits for a response.
func (p *Proxy) Connect(_ctx context.Context, state request.Request, opts Options) (*dns.Msg, error) {
start := time.Now()
func (p *Proxy) lookupDNS(_ctx context.Context, state request.Request, opts Options) (*dns.Msg, error) {
var proto string
switch {
case opts.ForceTCP: // TCP flag has precedence over UDP flag
@@ -172,11 +172,55 @@ func (p *Proxy) Connect(_ctx context.Context, state request.Request, opts Option
break
}
}
p.transport.Yield(pc)
return ret, nil
}
func (p *Proxy) lookupDoH(ctx context.Context, state request.Request, _ Options) (*dns.Msg, error) {
req, err := doh.NewRequestWithContext(ctx, p.dohMethod, p.addr, state.Req)
if err != nil {
return nil, err
}
resp, err := p.transport.httpClient.Do(req)
if err != nil {
return nil, err
}
// ResponseToMsg always closes the body via defer resp.Body.Close().
ret, err := doh.ResponseToMsg(resp)
if err != nil {
return nil, err
}
return ret, nil
}
// Connect selects an upstream, sends the request and waits for a response.
func (p *Proxy) Connect(ctx context.Context, state request.Request, opts Options) (*dns.Msg, error) {
start := time.Now()
originId := state.Req.Id
var (
ret *dns.Msg
err error
)
switch p.protocol {
case transport.HTTPS:
ret, err = p.lookupDoH(ctx, state, opts)
case transport.DNS, transport.TLS:
ret, err = p.lookupDNS(ctx, state, opts)
default:
return nil, fmt.Errorf("transport %s not supported to proxy", p.protocol)
}
if err != nil {
return nil, err
}
// recovery the origin Id after upstream.
ret.Id = originId
p.transport.Yield(pc)
rc, ok := dns.RcodeToString[ret.Rcode]
if !ok {
rc = strconv.Itoa(ret.Rcode)