mirror of
https://github.com/coredns/coredns.git
synced 2026-06-15 13:40:11 -04:00
* 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>
261 lines
7.4 KiB
Go
261 lines
7.4 KiB
Go
package dnsserver
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"fmt"
|
|
stdlog "log"
|
|
"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"
|
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
|
"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/pires/go-proxyproto"
|
|
"golang.org/x/net/netutil"
|
|
)
|
|
|
|
const (
|
|
// DefaultHTTPSMaxConnections is the default maximum number of concurrent connections.
|
|
DefaultHTTPSMaxConnections = 200
|
|
)
|
|
|
|
// ServerHTTPS represents an instance of a DNS-over-HTTPS server.
|
|
type ServerHTTPS struct {
|
|
*Server
|
|
httpsServer *http.Server
|
|
listenAddr net.Addr
|
|
tlsConfig *tls.Config
|
|
validRequest func(*http.Request) bool
|
|
maxConnections int
|
|
}
|
|
|
|
// loggerAdapter is a simple adapter around CoreDNS logger made to implement io.Writer in order to log errors from HTTP server
|
|
type loggerAdapter struct {
|
|
}
|
|
|
|
func (l *loggerAdapter) Write(p []byte) (n int, err error) {
|
|
clog.Debug(string(p))
|
|
return len(p), nil
|
|
}
|
|
|
|
// HTTPRequestKey is the context key for the HTTP request when processing DNS-over-HTTPS.
|
|
// Plugins can access the original HTTP request to retrieve headers, client IP, and metadata.
|
|
type HTTPRequestKey struct{}
|
|
|
|
// NewServerHTTPS returns a new CoreDNS HTTPS server and compiles all plugins in to it.
|
|
func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
|
|
s, err := NewServer(addr, group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// The *tls* plugin must make sure that multiple conflicting
|
|
// TLS configuration returns an error: it can only be specified once.
|
|
var tlsConfig *tls.Config
|
|
for _, z := range s.zones {
|
|
for _, conf := range z {
|
|
// Should we error if some configs *don't* have TLS?
|
|
tlsConfig = conf.TLSConfig
|
|
}
|
|
}
|
|
|
|
// http/2 is recommended when using DoH. We need to specify it in next protos
|
|
// or the upgrade won't happen.
|
|
if tlsConfig != nil {
|
|
tlsConfig.NextProtos = []string{"h2", "http/1.1"}
|
|
}
|
|
|
|
// Use a custom request validation func or use the standard DoH path check.
|
|
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 }
|
|
}
|
|
|
|
srv := &http.Server{
|
|
ReadTimeout: s.ReadTimeout,
|
|
WriteTimeout: s.WriteTimeout,
|
|
IdleTimeout: s.IdleTimeout,
|
|
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
|
|
}
|
|
maxConnections := DefaultHTTPSMaxConnections
|
|
if len(group) > 0 && group[0] != nil && group[0].MaxHTTPSConnections != nil {
|
|
maxConnections = *group[0].MaxHTTPSConnections
|
|
}
|
|
|
|
sh := &ServerHTTPS{
|
|
Server: s,
|
|
tlsConfig: tlsConfig,
|
|
httpsServer: srv,
|
|
validRequest: validator,
|
|
maxConnections: maxConnections,
|
|
}
|
|
sh.httpsServer.Handler = sh
|
|
|
|
return sh, nil
|
|
}
|
|
|
|
// Compile-time check to ensure ServerHTTPS implements the caddy.GracefulServer interface
|
|
var _ caddy.GracefulServer = &ServerHTTPS{}
|
|
|
|
// Serve implements caddy.TCPServer interface.
|
|
func (s *ServerHTTPS) Serve(l net.Listener) error {
|
|
s.m.Lock()
|
|
s.listenAddr = l.Addr()
|
|
s.m.Unlock()
|
|
|
|
// Wrap listener to limit concurrent connections (before TLS)
|
|
if s.maxConnections > 0 {
|
|
l = netutil.LimitListener(l, s.maxConnections)
|
|
}
|
|
|
|
if s.tlsConfig != nil {
|
|
l = tls.NewListener(l, s.tlsConfig)
|
|
}
|
|
|
|
return s.httpsServer.Serve(l)
|
|
}
|
|
|
|
// ServePacket implements caddy.UDPServer interface.
|
|
func (s *ServerHTTPS) ServePacket(_p net.PacketConn) error { return nil }
|
|
|
|
// Listen implements caddy.TCPServer interface.
|
|
func (s *ServerHTTPS) Listen() (net.Listener, error) {
|
|
l, err := reuseport.Listen("tcp", s.Addr[len(transport.HTTPS+"://"):])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if s.connPolicy != nil {
|
|
l = &proxyproto.Listener{Listener: l, ConnPolicy: s.connPolicy}
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// ListenPacket implements caddy.UDPServer interface.
|
|
func (s *ServerHTTPS) ListenPacket() (net.PacketConn, error) { return nil, nil }
|
|
|
|
// OnStartupComplete lists the sites served by this server
|
|
// and any relevant information, assuming Quiet is false.
|
|
func (s *ServerHTTPS) OnStartupComplete() {
|
|
if Quiet {
|
|
return
|
|
}
|
|
|
|
out := startUpZones(transport.HTTPS+"://", s.Addr, s.zones)
|
|
if out != "" {
|
|
fmt.Print(out)
|
|
}
|
|
}
|
|
|
|
// Stop stops the server. It blocks until the server is totally stopped.
|
|
func (s *ServerHTTPS) Stop() error {
|
|
s.m.Lock()
|
|
defer s.m.Unlock()
|
|
if s.httpsServer != nil {
|
|
s.httpsServer.Shutdown(context.Background())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// localAddr returns the per-connection local address, or s.listenAddr as fallback.
|
|
func (s *ServerHTTPS) localAddr(r *http.Request) net.Addr {
|
|
if addr, ok := r.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
|
|
return addr
|
|
}
|
|
return s.listenAddr
|
|
}
|
|
|
|
// ServeHTTP is the handler that gets the HTTP request and converts to the dns format, calls the plugin
|
|
// chain, converts it back and write it to the client.
|
|
func (s *ServerHTTPS) 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
|
|
}
|
|
|
|
// Create a DoHWriter with the correct addresses in it.
|
|
h, p, _ := net.SplitHostPort(r.RemoteAddr)
|
|
port, _ := strconv.Atoi(p)
|
|
dw := &DoHWriter{
|
|
laddr: s.localAddr(r),
|
|
raddr: &net.TCPAddr{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)
|
|
}
|
|
}
|
|
|
|
// We just call the normal chain handler - all error handling is done there.
|
|
// We should expect a packet to be returned that we can send to the client.
|
|
|
|
// Propagate HTTP request context to DNS processing chain. This ensures that
|
|
// HTTP request timeouts, cancellations, and other context values are properly
|
|
// inherited by the DNS processing pipeline.
|
|
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)
|
|
|
|
// See section 4.2.1 of RFC 8484.
|
|
// We are using code 500 to indicate an unexpected situation when the chain
|
|
// handler has not provided any response message.
|
|
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 *ServerHTTPS) countResponse(status int) {
|
|
vars.HTTPSResponsesCount.WithLabelValues(s.Addr, strconv.Itoa(status)).Inc()
|
|
}
|
|
|
|
// Shutdown stops the server (non gracefully).
|
|
func (s *ServerHTTPS) Shutdown() error {
|
|
if s.httpsServer != nil {
|
|
s.httpsServer.Shutdown(context.Background())
|
|
}
|
|
return nil
|
|
}
|