Files
coredns/core/dnsserver/server_https3.go
Yong Tang 3342b414e3 plugin/cache: allow cache TTLs above default 3600s (#8134)
* plugin/cache: allow cache TTLs above default 3600s

This change allows the cache plugin to honor configured maximum TTL values above the default 3600s limit. Default behavior remains unchanged
This PR fixes 7846

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

* Keep MinimalTTL

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>

---------

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
2026-06-06 07:48:26 +03:00

233 lines
6.1 KiB
Go

package dnsserver
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"strconv"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin/metrics/vars"
"github.com/coredns/coredns/plugin/pkg/dnsutil"
"github.com/coredns/coredns/plugin/pkg/doh"
cproxyproto "github.com/coredns/coredns/plugin/pkg/proxyproto"
"github.com/coredns/coredns/plugin/pkg/response"
"github.com/coredns/coredns/plugin/pkg/reuseport"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/miekg/dns"
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)
const (
// DefaultHTTPS3MaxStreams is the default maximum number of concurrent QUIC streams per connection.
DefaultHTTPS3MaxStreams = 256
// DefaultHTTPS3MaxHeaderBytes limits HTTP/3 header memory before requests reach the DoH handler.
DefaultHTTPS3MaxHeaderBytes = 16 << 10 // 16 KiB
)
// ServerHTTPS3 represents a DNS-over-HTTP/3 server.
type ServerHTTPS3 struct {
*Server
httpsServer *http3.Server
listenAddr net.Addr
tlsConfig *tls.Config
quicConfig *quic.Config
validRequest func(*http.Request) bool
maxStreams int
}
// NewServerHTTPS3 builds the HTTP/3 (DoH3) server.
func NewServerHTTPS3(addr string, group []*Config) (*ServerHTTPS3, error) {
s, err := NewServer(addr, group)
if err != nil {
return nil, err
}
// Extract TLS config (CoreDNS guarantees it is consistent)
var tlsConfig *tls.Config
for _, z := range s.zones {
for _, conf := range z {
tlsConfig = conf.TLSConfig
}
}
if tlsConfig == nil {
return nil, fmt.Errorf("DoH3 requires TLS, no TLS config found")
}
// HTTP/3 requires ALPN "h3"
tlsConfig.NextProtos = []string{"h3"}
// Request validator
var validator func(*http.Request) bool
for _, z := range s.zones {
for _, conf := range z {
validator = conf.HTTPRequestValidateFunc
}
}
if validator == nil {
validator = func(r *http.Request) bool { return r.URL.Path == doh.Path }
}
maxStreams := DefaultHTTPS3MaxStreams
if len(group) > 0 && group[0] != nil && group[0].MaxHTTPS3Streams != nil {
maxStreams = *group[0].MaxHTTPS3Streams
}
// QUIC transport config with stream limits (0 means use QUIC default)
qconf := &quic.Config{
MaxIdleTimeout: s.IdleTimeout,
Allow0RTT: true,
}
if maxStreams > 0 {
qconf.MaxIncomingStreams = int64(maxStreams)
qconf.MaxIncomingUniStreams = int64(maxStreams)
}
h3srv := &http3.Server{
Handler: nil, // set after constructing ServerHTTPS3
TLSConfig: tlsConfig,
EnableDatagrams: true,
QUICConfig: qconf,
MaxHeaderBytes: DefaultHTTPS3MaxHeaderBytes,
// Logger: stdlog.New(&loggerAdapter{}, "", 0), TODO: Fix it
}
sh := &ServerHTTPS3{
Server: s,
tlsConfig: tlsConfig,
httpsServer: h3srv,
quicConfig: qconf,
validRequest: validator,
maxStreams: maxStreams,
}
h3srv.Handler = sh
return sh, nil
}
var _ caddy.GracefulServer = &ServerHTTPS3{}
// ListenPacket opens the UDP socket for QUIC.
func (s *ServerHTTPS3) ListenPacket() (net.PacketConn, error) {
p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.HTTPS3+"://"):])
if err != nil {
return nil, err
}
if s.connPolicy != nil {
p = &cproxyproto.PacketConn{PacketConn: p, ConnPolicy: s.connPolicy}
}
return p, nil
}
// ServePacket starts serving QUIC+HTTP/3 on an existing UDP socket.
func (s *ServerHTTPS3) ServePacket(pc net.PacketConn) error {
s.m.Lock()
s.listenAddr = pc.LocalAddr()
s.m.Unlock()
// Serve HTTP/3 over QUIC
return s.httpsServer.Serve(pc)
}
// Listen function not used in HTTP/3, but defined for compatibility
func (s *ServerHTTPS3) Listen() (net.Listener, error) { return nil, nil }
func (s *ServerHTTPS3) Serve(_l net.Listener) error { return nil }
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *ServerHTTPS3) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones(transport.HTTPS3+"://", s.Addr, s.zones)
if out != "" {
fmt.Print(out)
}
}
// Stop graceful shutdown. It blocks until the server is totally stopped.
func (s *ServerHTTPS3) Stop() error {
s.m.Lock()
defer s.m.Unlock()
if s.httpsServer != nil {
return s.httpsServer.Shutdown(context.Background())
}
return nil
}
// Shutdown stops the server (non gracefully).
func (s *ServerHTTPS3) Shutdown() error {
if s.httpsServer != nil {
s.httpsServer.Shutdown(context.Background())
}
return nil
}
// ServeHTTP is the handler for the DoH3 requests
func (s *ServerHTTPS3) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !s.validRequest(r) {
http.Error(w, "", http.StatusNotFound)
s.countResponse(http.StatusNotFound)
return
}
msg, raw, err := doh.RequestToMsgWire(r)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
s.countResponse(http.StatusBadRequest)
return
}
// from HTTP request → DNS writer
h, p, _ := net.SplitHostPort(r.RemoteAddr)
port, _ := strconv.Atoi(p)
dw := &DoHWriter{
laddr: s.listenAddr,
raddr: &net.UDPAddr{IP: net.ParseIP(h), Port: port},
request: r,
}
if tsig := msg.IsTsig(); tsig != nil {
if s.tsigSecret == nil {
dw.tsigStatus = dns.ErrSecret
} else if secret, ok := s.tsigSecret[tsig.Hdr.Name]; !ok {
dw.tsigStatus = dns.ErrSecret
} else {
dw.tsigStatus = dns.TsigVerify(raw, secret, "", false)
}
}
ctx := context.WithValue(r.Context(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
ctx = context.WithValue(ctx, HTTPRequestKey{}, r)
s.ServeDNS(ctx, dw, msg)
if dw.Msg == nil {
http.Error(w, "No response", http.StatusInternalServerError)
s.countResponse(http.StatusInternalServerError)
return
}
buf, _ := dw.Msg.Pack()
mt, _ := response.Typify(dw.Msg, time.Now().UTC())
age := dnsutil.MinimalTTLWithMaximum(dw.Msg, mt, dnsutil.MaximumDefaultTTL)
w.Header().Set("Content-Type", doh.MimeType)
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%d", uint32(age.Seconds())))
w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
w.WriteHeader(http.StatusOK)
s.countResponse(http.StatusOK)
w.Write(buf)
}
func (s *ServerHTTPS3) countResponse(status int) {
vars.HTTPS3ResponsesCount.WithLabelValues(s.Addr, strconv.Itoa(status)).Inc()
}