TUN-8685: Bump coredns dependency
Some checks are pending
Check / check (1.22.x, macos-latest) (push) Waiting to run
Check / check (1.22.x, ubuntu-latest) (push) Waiting to run
Check / check (1.22.x, windows-latest) (push) Waiting to run
Semgrep config / semgrep/ci (push) Waiting to run

Closes TUN-8685
This commit is contained in:
Devin Carr
2024-10-17 13:09:39 -07:00
parent abb3466c31
commit d608a64cc5
127 changed files with 4201 additions and 1747 deletions

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"net/http"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin"
@@ -53,6 +54,11 @@ type Config struct {
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
TLSConfig *tls.Config
// Timeouts for TCP, TLS and HTTPS servers.
ReadTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
// TSIG secrets, [name]key.
TsigSecret map[string]string

View File

@@ -4,13 +4,11 @@ import (
"net"
"net/http"
"github.com/coredns/coredns/plugin/pkg/nonwriter"
"github.com/miekg/dns"
)
// DoHWriter is a nonwriter.Writer that adds more specific LocalAddr and RemoteAddr methods.
// DoHWriter is a dns.ResponseWriter that adds more specific LocalAddr and RemoteAddr methods.
type DoHWriter struct {
nonwriter.Writer
// raddr is the remote's address. This can be optionally set.
raddr net.Addr
// laddr is our address. This can be optionally set.
@@ -18,13 +16,50 @@ type DoHWriter struct {
// request is the HTTP request we're currently handling.
request *http.Request
// Msg is a response to be written to the client.
Msg *dns.Msg
}
// WriteMsg stores the message to be written to the client.
func (d *DoHWriter) WriteMsg(m *dns.Msg) error {
d.Msg = m
return nil
}
// Write stores the message to be written to the client.
func (d *DoHWriter) Write(b []byte) (int, error) {
d.Msg = new(dns.Msg)
return len(b), d.Msg.Unpack(b)
}
// RemoteAddr returns the remote address.
func (d *DoHWriter) RemoteAddr() net.Addr { return d.raddr }
func (d *DoHWriter) RemoteAddr() net.Addr {
return d.raddr
}
// LocalAddr returns the local address.
func (d *DoHWriter) LocalAddr() net.Addr { return d.laddr }
func (d *DoHWriter) LocalAddr() net.Addr {
return d.laddr
}
// Request returns the HTTP request
func (d *DoHWriter) Request() *http.Request { return d.request }
// Request returns the HTTP request.
func (d *DoHWriter) Request() *http.Request {
return d.request
}
// Close no-op implementation.
func (d *DoHWriter) Close() error {
return nil
}
// TsigStatus no-op implementation.
func (d *DoHWriter) TsigStatus() error {
return nil
}
// TsigTimersOnly no-op implementation.
func (d *DoHWriter) TsigTimersOnly(_ bool) {}
// Hijack no-op implementation.
func (d *DoHWriter) Hijack() {}

View File

@@ -0,0 +1,60 @@
package dnsserver
import (
"encoding/binary"
"net"
"github.com/miekg/dns"
"github.com/quic-go/quic-go"
)
type DoQWriter struct {
localAddr net.Addr
remoteAddr net.Addr
stream quic.Stream
Msg *dns.Msg
}
func (w *DoQWriter) Write(b []byte) (int, error) {
b = AddPrefix(b)
return w.stream.Write(b)
}
func (w *DoQWriter) WriteMsg(m *dns.Msg) error {
bytes, err := m.Pack()
if err != nil {
return err
}
_, err = w.Write(bytes)
if err != nil {
return err
}
return w.Close()
}
// Close sends the STREAM FIN signal.
// The server MUST send the response(s) on the same stream and MUST
// indicate, after the last response, through the STREAM FIN
// mechanism that no further data will be sent on that stream.
// See https://www.rfc-editor.org/rfc/rfc9250#section-4.2-7
func (w *DoQWriter) Close() error {
return w.stream.Close()
}
// AddPrefix adds a 2-byte prefix with the DNS message length.
func AddPrefix(b []byte) (m []byte) {
m = make([]byte, 2+len(b))
binary.BigEndian.PutUint16(m, uint16(len(b)))
copy(m[2:], b)
return m
}
// These methods implement the dns.ResponseWriter interface from Go DNS.
func (w *DoQWriter) TsigStatus() error { return nil }
func (w *DoQWriter) TsigTimersOnly(b bool) {}
func (w *DoQWriter) Hijack() {}
func (w *DoQWriter) LocalAddr() net.Addr { return w.localAddr }
func (w *DoQWriter) RemoteAddr() net.Addr { return w.remoteAddr }

View File

@@ -1,7 +1,6 @@
package dnsserver
import (
"flag"
"fmt"
"net"
"time"
@@ -17,12 +16,7 @@ import (
const serverType = "dns"
// Any flags defined here, need to be namespaced to the serverType other
// wise they potentially clash with other server types.
func init() {
flag.StringVar(&Port, serverType+".port", DefaultPort, "Default port")
flag.StringVar(&Port, "p", DefaultPort, "Default port")
caddy.RegisterServerType(serverType, caddy.ServerType{
Directives: func() []string { return Directives },
DefaultInput: func() caddy.Input {
@@ -88,6 +82,8 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
port = Port
case transport.TLS:
port = transport.TLSPort
case transport.QUIC:
port = transport.QUICPort
case transport.GRPC:
port = transport.GRPCPort
case transport.HTTPS:
@@ -147,7 +143,12 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
c.ListenHosts = c.firstConfigInBlock.ListenHosts
c.Debug = c.firstConfigInBlock.Debug
c.Stacktrace = c.firstConfigInBlock.Stacktrace
c.TLSConfig = c.firstConfigInBlock.TLSConfig
// Fork TLSConfig for each encrypted connection
c.TLSConfig = c.firstConfigInBlock.TLSConfig.Clone()
c.ReadTimeout = c.firstConfigInBlock.ReadTimeout
c.WriteTimeout = c.firstConfigInBlock.WriteTimeout
c.IdleTimeout = c.firstConfigInBlock.IdleTimeout
c.TsigSecret = c.firstConfigInBlock.TsigSecret
}
@@ -175,6 +176,13 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
}
servers = append(servers, s)
case transport.QUIC:
s, err := NewServerQUIC(addr, group)
if err != nil {
return nil, err
}
servers = append(servers, s)
case transport.GRPC:
s, err := NewServergRPC(addr, group)
if err != nil {
@@ -221,7 +229,8 @@ func (c *Config) AddPlugin(m plugin.Plugin) {
}
// registerHandler adds a handler to a site's handler registration. Handlers
// use this to announce that they exist to other plugin.
//
// use this to announce that they exist to other plugin.
func (c *Config) registerHandler(h plugin.Handler) {
if c.registry == nil {
c.registry = make(map[string]plugin.Handler)
@@ -287,7 +296,7 @@ func (h *dnsContext) validateZonesAndListeningAddresses() error {
return nil
}
// groupSiteConfigsByListenAddr groups site configs by their listen
// groupConfigsByListenAddr groups site configs by their listen
// (bind) address, so sites that use the same listener can be served
// on the same server instance. The return value maps the listen
// address (what you pass into net.Listen) to the list of site configs.

View File

@@ -44,6 +44,9 @@ type Server struct {
debug bool // disable recover()
stacktrace bool // enable stacktrace in recover error log
classChaos bool // allow non-INET class queries
idleTimeout time.Duration // Idle timeout for TCP
readTimeout time.Duration // Read timeout for TCP
writeTimeout time.Duration // Write timeout for TCP
tsigSecret map[string]string
}
@@ -60,6 +63,9 @@ func NewServer(addr string, group []*Config) (*Server, error) {
Addr: addr,
zones: make(map[string][]*Config),
graceTimeout: 5 * time.Second,
idleTimeout: 10 * time.Second,
readTimeout: 3 * time.Second,
writeTimeout: 5 * time.Second,
tsigSecret: make(map[string]string),
}
@@ -81,6 +87,17 @@ func NewServer(addr string, group []*Config) (*Server, error) {
// append the config to the zone's configs
s.zones[site.Zone] = append(s.zones[site.Zone], site)
// set timeouts
if site.ReadTimeout != 0 {
s.readTimeout = site.ReadTimeout
}
if site.WriteTimeout != 0 {
s.writeTimeout = site.WriteTimeout
}
if site.IdleTimeout != 0 {
s.idleTimeout = site.IdleTimeout
}
// copy tsig secrets
for key, secret := range site.TsigSecret {
s.tsigSecret[key] = secret
@@ -130,11 +147,22 @@ var _ caddy.GracefulServer = &Server{}
// This implements caddy.TCPServer interface.
func (s *Server) Serve(l net.Listener) error {
s.m.Lock()
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
}), TsigSecret: s.tsigSecret}
s.server[tcp] = &dns.Server{Listener: l,
Net: "tcp",
TsigSecret: s.tsigSecret,
MaxTCPQueries: tcpMaxQueries,
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: func() time.Duration {
return s.idleTimeout
},
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[tcp].ActivateAndServe()
@@ -404,6 +432,8 @@ func errorAndMetricsFunc(server string, w dns.ResponseWriter, r *dns.Msg, rc int
const (
tcp = 0
udp = 1
tcpMaxQueries = -1
)
type (

View File

@@ -75,9 +75,9 @@ func NewServerHTTPS(addr string, group []*Config) (*ServerHTTPS, error) {
}
srv := &http.Server{
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: s.idleTimeout,
ErrorLog: stdlog.New(&loggerAdapter{}, "", 0),
}
sh := &ServerHTTPS{

View File

@@ -0,0 +1,346 @@
package dnsserver
import (
"context"
"crypto/tls"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"net"
"github.com/coredns/coredns/plugin/metrics/vars"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/pkg/reuseport"
"github.com/coredns/coredns/plugin/pkg/transport"
"github.com/miekg/dns"
"github.com/quic-go/quic-go"
)
const (
// DoQCodeNoError is used when the connection or stream needs to be
// closed, but there is no error to signal.
DoQCodeNoError quic.ApplicationErrorCode = 0
// DoQCodeInternalError signals that the DoQ implementation encountered
// an internal error and is incapable of pursuing the transaction or the
// connection.
DoQCodeInternalError quic.ApplicationErrorCode = 1
// DoQCodeProtocolError signals that the DoQ implementation encountered
// a protocol error and is forcibly aborting the connection.
DoQCodeProtocolError quic.ApplicationErrorCode = 2
)
// ServerQUIC represents an instance of a DNS-over-QUIC server.
type ServerQUIC struct {
*Server
listenAddr net.Addr
tlsConfig *tls.Config
quicConfig *quic.Config
quicListener *quic.Listener
}
// NewServerQUIC returns a new CoreDNS QUIC server and compiles all plugin in to it.
func NewServerQUIC(addr string, group []*Config) (*ServerQUIC, 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
}
}
if tlsConfig != nil {
tlsConfig.NextProtos = []string{"doq"}
}
var quicConfig *quic.Config
quicConfig = &quic.Config{
MaxIdleTimeout: s.idleTimeout,
MaxIncomingStreams: math.MaxUint16,
MaxIncomingUniStreams: math.MaxUint16,
// Enable 0-RTT by default for all connections on the server-side.
Allow0RTT: true,
}
return &ServerQUIC{Server: s, tlsConfig: tlsConfig, quicConfig: quicConfig}, nil
}
// ServePacket implements caddy.UDPServer interface.
func (s *ServerQUIC) ServePacket(p net.PacketConn) error {
s.m.Lock()
s.listenAddr = s.quicListener.Addr()
s.m.Unlock()
return s.ServeQUIC()
}
// ServeQUIC listens for incoming QUIC packets.
func (s *ServerQUIC) ServeQUIC() error {
for {
conn, err := s.quicListener.Accept(context.Background())
if err != nil {
if s.isExpectedErr(err) {
s.closeQUICConn(conn, DoQCodeNoError)
return err
}
s.closeQUICConn(conn, DoQCodeInternalError)
return err
}
go s.serveQUICConnection(conn)
}
}
// serveQUICConnection handles a new QUIC connection. It waits for new streams
// and passes them to serveQUICStream.
func (s *ServerQUIC) serveQUICConnection(conn quic.Connection) {
for {
// In DoQ, one query consumes one stream.
// The client MUST select the next available client-initiated bidirectional
// stream for each subsequent query on a QUIC connection.
stream, err := conn.AcceptStream(context.Background())
if err != nil {
if s.isExpectedErr(err) {
s.closeQUICConn(conn, DoQCodeNoError)
return
}
s.closeQUICConn(conn, DoQCodeInternalError)
return
}
go s.serveQUICStream(stream, conn)
}
}
func (s *ServerQUIC) serveQUICStream(stream quic.Stream, conn quic.Connection) {
buf, err := readDOQMessage(stream)
// io.EOF does not really mean that there's any error, it is just
// the STREAM FIN indicating that there will be no data to read
// anymore from this stream.
if err != nil && err != io.EOF {
s.closeQUICConn(conn, DoQCodeProtocolError)
return
}
req := &dns.Msg{}
err = req.Unpack(buf)
if err != nil {
clog.Debugf("unpacking quic packet: %s", err)
s.closeQUICConn(conn, DoQCodeProtocolError)
return
}
if !validRequest(req) {
// If a peer encounters such an error condition, it is considered a
// fatal error. It SHOULD forcibly abort the connection using QUIC's
// CONNECTION_CLOSE mechanism and SHOULD use the DoQ error code
// DOQ_PROTOCOL_ERROR.
// See https://www.rfc-editor.org/rfc/rfc9250#section-4.3.3-3
s.closeQUICConn(conn, DoQCodeProtocolError)
return
}
w := &DoQWriter{
localAddr: conn.LocalAddr(),
remoteAddr: conn.RemoteAddr(),
stream: stream,
Msg: req,
}
dnsCtx := context.WithValue(stream.Context(), Key{}, s.Server)
dnsCtx = context.WithValue(dnsCtx, LoopKey{}, 0)
s.ServeDNS(dnsCtx, w, req)
s.countResponse(DoQCodeNoError)
}
// ListenPacket implements caddy.UDPServer interface.
func (s *ServerQUIC) ListenPacket() (net.PacketConn, error) {
p, err := reuseport.ListenPacket("udp", s.Addr[len(transport.QUIC+"://"):])
if err != nil {
return nil, err
}
s.m.Lock()
defer s.m.Unlock()
s.quicListener, err = quic.Listen(p, s.tlsConfig, s.quicConfig)
if err != nil {
return nil, err
}
return p, nil
}
// OnStartupComplete lists the sites served by this server
// and any relevant information, assuming Quiet is false.
func (s *ServerQUIC) OnStartupComplete() {
if Quiet {
return
}
out := startUpZones(transport.QUIC+"://", s.Addr, s.zones)
if out != "" {
fmt.Print(out)
}
}
// Stop stops the server non-gracefully. It blocks until the server is totally stopped.
func (s *ServerQUIC) Stop() error {
s.m.Lock()
defer s.m.Unlock()
if s.quicListener != nil {
return s.quicListener.Close()
}
return nil
}
// Serve implements caddy.TCPServer interface.
func (s *ServerQUIC) Serve(l net.Listener) error { return nil }
// Listen implements caddy.TCPServer interface.
func (s *ServerQUIC) Listen() (net.Listener, error) { return nil, nil }
// closeQUICConn quietly closes the QUIC connection.
func (s *ServerQUIC) closeQUICConn(conn quic.Connection, code quic.ApplicationErrorCode) {
if conn == nil {
return
}
clog.Debugf("closing quic conn %s with code %d", conn.LocalAddr(), code)
err := conn.CloseWithError(code, "")
if err != nil {
clog.Debugf("failed to close quic connection with code %d: %s", code, err)
}
// DoQCodeNoError metrics are already registered after s.ServeDNS()
if code != DoQCodeNoError {
s.countResponse(code)
}
}
// validRequest checks for protocol errors in the unpacked DNS message.
// See https://www.rfc-editor.org/rfc/rfc9250.html#name-protocol-errors
func validRequest(req *dns.Msg) (ok bool) {
// 1. a client or server receives a message with a non-zero Message ID.
if req.Id != 0 {
return false
}
// 2. an implementation receives a message containing the edns-tcp-keepalive
// EDNS(0) Option [RFC7828].
if opt := req.IsEdns0(); opt != nil {
for _, option := range opt.Option {
if option.Option() == dns.EDNS0TCPKEEPALIVE {
clog.Debug("client sent EDNS0 TCP keepalive option")
return false
}
}
}
// 3. the client or server does not indicate the expected STREAM FIN after
// sending requests or responses.
//
// This is quite problematic to validate this case since this would imply
// we have to wait until STREAM FIN is arrived before we start processing
// the message. So we're consciously ignoring this case in this
// implementation.
// 4. a server receives a "replayable" transaction in 0-RTT data
//
// The information necessary to validate this is not exposed by quic-go.
return true
}
// readDOQMessage reads a DNS over QUIC (DOQ) message from the given stream
// and returns the message bytes.
// Drafts of the RFC9250 did not require the 2-byte prefixed message length.
// Thus, we are only supporting the official version (DoQ v1).
func readDOQMessage(r io.Reader) ([]byte, error) {
// All DNS messages (queries and responses) sent over DoQ connections MUST
// be encoded as a 2-octet length field followed by the message content as
// specified in [RFC1035].
// See https://www.rfc-editor.org/rfc/rfc9250.html#section-4.2-4
sizeBuf := make([]byte, 2)
_, err := io.ReadFull(r, sizeBuf)
if err != nil {
return nil, err
}
size := binary.BigEndian.Uint16(sizeBuf)
if size == 0 {
return nil, fmt.Errorf("message size is 0: probably unsupported DoQ version")
}
buf := make([]byte, size)
_, err = io.ReadFull(r, buf)
// A client or server receives a STREAM FIN before receiving all the bytes
// for a message indicated in the 2-octet length field.
// See https://www.rfc-editor.org/rfc/rfc9250#section-4.3.3-2.2
if size != uint16(len(buf)) {
return nil, fmt.Errorf("message size does not match 2-byte prefix")
}
return buf, err
}
// isExpectedErr returns true if err is an expected error, likely related to
// the current implementation.
func (s *ServerQUIC) isExpectedErr(err error) bool {
if err == nil {
return false
}
// This error is returned when the QUIC listener was closed by us. As
// graceful shutdown is not implemented, the connection will be abruptly
// closed but there is no error to signal.
if errors.Is(err, quic.ErrServerClosed) {
return true
}
// This error happens when the connection was closed due to a DoQ
// protocol error but there's still something to read in the closed stream.
// For example, when the message was sent without the prefixed length.
var qAppErr *quic.ApplicationError
if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 2 {
return true
}
// When a connection hits the idle timeout, quic.AcceptStream() returns
// an IdleTimeoutError. In this, case, we should just drop the connection
// with DoQCodeNoError.
var qIdleErr *quic.IdleTimeoutError
return errors.As(err, &qIdleErr)
}
func (s *ServerQUIC) countResponse(code quic.ApplicationErrorCode) {
switch code {
case DoQCodeNoError:
vars.QUICResponsesCount.WithLabelValues(s.Addr, "0x0").Inc()
case DoQCodeInternalError:
vars.QUICResponsesCount.WithLabelValues(s.Addr, "0x1").Inc()
case DoQCodeProtocolError:
vars.QUICResponsesCount.WithLabelValues(s.Addr, "0x2").Inc()
}
}

View File

@@ -5,6 +5,7 @@ import (
"crypto/tls"
"fmt"
"net"
"time"
"github.com/coredns/caddy"
"github.com/coredns/coredns/plugin/pkg/reuseport"
@@ -50,11 +51,20 @@ func (s *ServerTLS) Serve(l net.Listener) error {
}
// Only fill out the TCP server for this one.
s.server[tcp] = &dns.Server{Listener: l, Net: "tcp-tls", Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.server[tcp] = &dns.Server{Listener: l,
Net: "tcp-tls",
MaxTCPQueries: tlsMaxQueries,
ReadTimeout: s.readTimeout,
WriteTimeout: s.writeTimeout,
IdleTimeout: func() time.Duration {
return s.idleTimeout
},
Handler: dns.HandlerFunc(func(w dns.ResponseWriter, r *dns.Msg) {
ctx := context.WithValue(context.Background(), Key{}, s.Server)
ctx = context.WithValue(ctx, LoopKey{}, 0)
s.ServeDNS(ctx, w, r)
})}
s.m.Unlock()
return s.server[tcp].ActivateAndServe()
@@ -87,3 +97,7 @@ func (s *ServerTLS) OnStartupComplete() {
fmt.Print(out)
}
}
const (
tlsMaxQueries = -1
)

View File

@@ -10,14 +10,15 @@ package dnsserver
// (after) them during a request, but they must not
// care what plugin above them are doing.
var Directives = []string{
"root",
"metadata",
"geoip",
"cancel",
"tls",
"timeouts",
"reload",
"nsid",
"bufsize",
"root",
"bind",
"debug",
"trace",

View File

@@ -28,6 +28,9 @@ func init() {
caddy.RegisterCaddyfileLoader("flag", caddy.LoaderFunc(confLoader))
caddy.SetDefaultCaddyfileLoader("default", caddy.LoaderFunc(defaultLoader))
flag.StringVar(&dnsserver.Port, serverType+".port", dnsserver.DefaultPort, "Default port")
flag.StringVar(&dnsserver.Port, "p", dnsserver.DefaultPort, "Default port")
caddy.AppName = coreName
caddy.AppVersion = CoreVersion
}
@@ -42,7 +45,7 @@ func Run() {
}
log.SetOutput(os.Stdout)
log.SetFlags(0) // Set to 0 because we're doing our own time, with timezone
log.SetFlags(LogFlags)
if version {
showVersion()
@@ -166,10 +169,14 @@ var (
conf string
version bool
plugins bool
// LogFlags are initially set to 0 for no extra output
LogFlags int
)
// Build information obtained with the help of -ldflags
var (
// nolint
appVersion = "(untracked dev build)" // inferred at startup
devBuild = true // inferred at startup

View File

@@ -2,7 +2,7 @@ package coremain
// Various CoreDNS constants.
const (
CoreVersion = "1.10.0"
CoreVersion = "1.11.3"
coreName = "CoreDNS"
serverType = "dns"
)

View File

@@ -10,8 +10,7 @@ With *cache* enabled, all records except zone transfers and metadata records wil
3600s. Caching is mostly useful in a scenario when fetching data from the backend (upstream,
database, etc.) is expensive.
*Cache* will change the query to enable DNSSEC (DNSSEC OK; DO) if it passes through the plugin. If
the client didn't request any DNSSEC (records), these are filtered out when replying.
*Cache* will pass DNSSEC (DNSSEC OK; DO) options through the plugin for upstream queries.
This plugin can only be used once per Server Block.
@@ -40,6 +39,7 @@ cache [TTL] [ZONES...] {
serve_stale [DURATION] [REFRESH_MODE]
servfail DURATION
disable success|denial [ZONES...]
keepttl
}
~~~
@@ -70,6 +70,11 @@ cache [TTL] [ZONES...] {
greater than 5 minutes.
* `disable` disable the success or denial cache for the listed **ZONES**. If no **ZONES** are given, the specified
cache will be disabled for all zones.
* `keepttl` do not age TTL when serving responses from cache. The entry will still be removed from cache
when the TTL expires as normal, but until it expires responses will include the original TTL instead
of the remaining TTL. This can be useful if CoreDNS is used as an authoritative server and you want
to serve a consistent TTL to downstream clients. This is **NOT** recommended when CoreDNS is caching
records it is not authoritative for because it could result in downstream clients using stale answers.
## Capacity and Eviction
@@ -136,4 +141,4 @@ example.org {
disable denial sub.example.org
}
}
~~~
~~~

View File

@@ -48,6 +48,9 @@ type Cache struct {
pexcept []string
nexcept []string
// Keep ttl option
keepttl bool
// Testing.
now func() time.Time
}
@@ -76,7 +79,7 @@ func New() *Cache {
// key returns key under which we store the item, -1 will be returned if we don't store the message.
// Currently we do not cache Truncated, errors zone transfers or dynamic update messages.
// qname holds the already lowercased qname.
func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
func key(qname string, m *dns.Msg, t response.Type, do, cd bool) (bool, uint64) {
// We don't store truncated responses.
if m.Truncated {
return false, 0
@@ -86,11 +89,27 @@ func key(qname string, m *dns.Msg, t response.Type) (bool, uint64) {
return false, 0
}
return true, hash(qname, m.Question[0].Qtype)
return true, hash(qname, m.Question[0].Qtype, do, cd)
}
func hash(qname string, qtype uint16) uint64 {
var one = []byte("1")
var zero = []byte("0")
func hash(qname string, qtype uint16, do, cd bool) uint64 {
h := fnv.New64()
if do {
h.Write(one)
} else {
h.Write(zero)
}
if cd {
h.Write(one)
} else {
h.Write(zero)
}
h.Write([]byte{byte(qtype >> 8)})
h.Write([]byte{byte(qtype)})
h.Write([]byte(qname))
@@ -116,6 +135,7 @@ type ResponseWriter struct {
server string // Server handling the request.
do bool // When true the original request had the DO bit set.
cd bool // When true the original request had the CD bit set.
ad bool // When true the original request had the AD bit set.
prefetch bool // When true write nothing back to the client.
remoteAddr net.Addr
@@ -145,6 +165,8 @@ func newPrefetchResponseWriter(server string, state request.Request, c *Cache) *
Cache: c,
state: state,
server: server,
do: state.Do(),
cd: state.Req.CheckingDisabled,
prefetch: true,
remoteAddr: addr,
}
@@ -163,7 +185,7 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
mt, _ := response.Typify(res, w.now().UTC())
// key returns empty string for anything we don't want to cache.
hasKey, key := key(w.state.Name(), res, mt)
hasKey, key := key(w.state.Name(), res, mt, w.do, w.cd)
msgTTL := dnsutil.MinimalTTL(res, mt)
var duration time.Duration
@@ -191,11 +213,10 @@ func (w *ResponseWriter) WriteMsg(res *dns.Msg) error {
}
// Apply capped TTL to this reply to avoid jarring TTL experience 1799 -> 8 (e.g.)
// We also may need to filter out DNSSEC records, see toMsg() for similar code.
ttl := uint32(duration.Seconds())
res.Answer = filterRRSlice(res.Answer, ttl, w.do, false)
res.Ns = filterRRSlice(res.Ns, ttl, w.do, false)
res.Extra = filterRRSlice(res.Extra, ttl, w.do, false)
res.Answer = filterRRSlice(res.Answer, ttl, false)
res.Ns = filterRRSlice(res.Ns, ttl, false)
res.Extra = filterRRSlice(res.Extra, ttl, false)
if !w.do && !w.ad {
// unset AD bit if requester is not OK with DNSSEC

View File

@@ -2,35 +2,13 @@ package cache
import "github.com/miekg/dns"
// isDNSSEC returns true if r is a DNSSEC record. NSEC,NSEC3,DS and RRSIG/SIG
// are DNSSEC records. DNSKEYs is not in this list on the assumption that the
// client explicitly asked for it.
func isDNSSEC(r dns.RR) bool {
switch r.Header().Rrtype {
case dns.TypeNSEC:
return true
case dns.TypeNSEC3:
return true
case dns.TypeDS:
return true
case dns.TypeRRSIG:
return true
case dns.TypeSIG:
return true
}
return false
}
// filterRRSlice filters rrs and removes DNSSEC RRs when do is false. In the returned slice
// the TTLs are set to ttl. If dup is true the RRs in rrs are _copied_ into the slice that is
// filterRRSlice filters out OPT RRs, and sets all RR TTLs to ttl.
// If dup is true the RRs in rrs are _copied_ into the slice that is
// returned.
func filterRRSlice(rrs []dns.RR, ttl uint32, do, dup bool) []dns.RR {
func filterRRSlice(rrs []dns.RR, ttl uint32, dup bool) []dns.RR {
j := 0
rs := make([]dns.RR, len(rrs))
for _, r := range rrs {
if !do && isDNSSEC(r) {
continue
}
if r.Header().Rrtype == dns.TypeOPT {
continue
}

View File

@@ -18,6 +18,7 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
rc := r.Copy() // We potentially modify r, to prevent other plugins from seeing this (r is a pointer), copy r into rc.
state := request.Request{W: w, Req: rc}
do := state.Do()
cd := r.CheckingDisabled
ad := r.AuthenticatedData
zone := plugin.Zones(c.Zones).Matches(state.Name())
@@ -28,17 +29,15 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
now := c.now().UTC()
server := metrics.WithServer(ctx)
// On cache miss, if the request has the OPT record and the DO bit set we leave the message as-is. If there isn't a DO bit
// set we will modify the request to _add_ one. This means we will always do DNSSEC lookups on cache misses.
// When writing to cache, any DNSSEC RRs in the response are written to cache with the response.
// When sending a response to a non-DNSSEC client, we remove DNSSEC RRs from the response. We use a 2048 buffer size, which is
// less than 4096 (and older default) and more than 1024 which may be too small. We might need to tweaks this
// value to be smaller still to prevent UDP fragmentation?
// On cache refresh, we will just use the DO bit from the incoming query for the refresh since we key our cache
// with the query DO bit. That means two separate cache items for the query DO bit true or false. In the situation
// in which upstream doesn't support DNSSEC, the two cache items will effectively be the same. Regardless, any
// DNSSEC RRs in the response are written to cache with the response.
ttl := 0
i := c.getIgnoreTTL(now, state, server)
if i == nil {
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do, ad: ad,
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do, ad: ad, cd: cd,
nexcept: c.nexcept, pexcept: c.pexcept, wildcardFunc: wildcardFunc(ctx)}
return c.doRefresh(ctx, state, crr)
}
@@ -46,7 +45,7 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
if ttl < 0 {
// serve stale behavior
if c.verifyStale {
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do}
crr := &ResponseWriter{ResponseWriter: w, Cache: c, state: state, server: server, do: do, cd: cd}
cw := newVerifyStaleResponseWriter(crr)
ret, err := c.doRefresh(ctx, state, cw)
if cw.refreshed {
@@ -73,6 +72,11 @@ func (c *Cache) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
})
}
if c.keepttl {
// If keepttl is enabled we fake the current time to the stored
// one so that we always get the original TTL
now = i.stored
}
resp := i.toMsg(r, now, do, ad)
w.WriteMsg(resp)
return dns.RcodeSuccess, nil
@@ -101,9 +105,6 @@ func (c *Cache) doPrefetch(ctx context.Context, state request.Request, cw *Respo
}
func (c *Cache) doRefresh(ctx context.Context, state request.Request, cw dns.ResponseWriter) (int, error) {
if !state.Do() {
setDo(state.Req)
}
return plugin.NextOrFailure(c.Name(), c.Next, ctx, cw, state.Req)
}
@@ -121,7 +122,7 @@ func (c *Cache) Name() string { return "cache" }
// getIgnoreTTL unconditionally returns an item if it exists in the cache.
func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string) *item {
k := hash(state.Name(), state.QType())
k := hash(state.Name(), state.QType(), state.Do(), state.Req.CheckingDisabled)
cacheRequests.WithLabelValues(server, c.zonesMetricLabel, c.viewMetricLabel).Inc()
if i, ok := c.ncache.Get(k); ok {
@@ -145,7 +146,7 @@ func (c *Cache) getIgnoreTTL(now time.Time, state request.Request, server string
}
func (c *Cache) exists(state request.Request) *item {
k := hash(state.Name(), state.QType())
k := hash(state.Name(), state.QType(), state.Do(), state.Req.CheckingDisabled)
if i, ok := c.ncache.Get(k); ok {
return i.(*item)
}
@@ -154,22 +155,3 @@ func (c *Cache) exists(state request.Request) *item {
}
return nil
}
// setDo sets the DO bit and UDP buffer size in the message m.
func setDo(m *dns.Msg) {
o := m.IsEdns0()
if o != nil {
o.SetDo()
o.SetUDPSize(defaultUDPBufSize)
return
}
o = &dns.OPT{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeOPT}}
o.SetDo()
o.SetUDPSize(defaultUDPBufSize)
m.Extra = append(m.Extra, o)
}
// defaultUDPBufsize is the bufsize the cache plugin uses on outgoing requests that don't
// have an OPT RR.
const defaultUDPBufSize = 2048

View File

@@ -87,9 +87,9 @@ func (i *item) toMsg(m *dns.Msg, now time.Time, do bool, ad bool) *dns.Msg {
m1.Extra = make([]dns.RR, len(i.Extra))
ttl := uint32(i.ttl(now))
m1.Answer = filterRRSlice(i.Answer, ttl, do, true)
m1.Ns = filterRRSlice(i.Ns, ttl, do, true)
m1.Extra = filterRRSlice(i.Extra, ttl, do, true)
m1.Answer = filterRRSlice(i.Answer, ttl, true)
m1.Ns = filterRRSlice(i.Ns, ttl, true)
m1.Extra = filterRRSlice(i.Extra, ttl, true)
return m1
}

View File

@@ -240,6 +240,12 @@ func cacheParse(c *caddy.Controller) (*Cache, error) {
default:
return nil, fmt.Errorf("cache type for disable must be %q or %q", Success, Denial)
}
case "keepttl":
args := c.RemainingArgs()
if len(args) != 0 {
return nil, c.ArgErr()
}
ca.keepttl = true
default:
return nil, c.ArgErr()
}

View File

@@ -8,33 +8,32 @@
//
// Implement the Provider interface for a plugin p:
//
// func (p P) Metadata(ctx context.Context, state request.Request) context.Context {
// metadata.SetValueFunc(ctx, "test/something", func() string { return "myvalue" })
// return ctx
// }
// func (p P) Metadata(ctx context.Context, state request.Request) context.Context {
// metadata.SetValueFunc(ctx, "test/something", func() string { return "myvalue" })
// return ctx
// }
//
// Basic example with caching:
//
// func (p P) Metadata(ctx context.Context, state request.Request) context.Context {
// cached := ""
// f := func() string {
// if cached != "" {
// return cached
// }
// cached = expensiveFunc()
// return cached
// }
// metadata.SetValueFunc(ctx, "test/something", f)
// return ctx
// }
// func (p P) Metadata(ctx context.Context, state request.Request) context.Context {
// cached := ""
// f := func() string {
// if cached != "" {
// return cached
// }
// cached = expensiveFunc()
// return cached
// }
// metadata.SetValueFunc(ctx, "test/something", f)
// return ctx
// }
//
// If you need access to this metadata from another plugin:
//
// // ...
// valueFunc := metadata.ValueFunc(ctx, "test/something")
// value := valueFunc()
// // use 'value'
//
// // ...
// valueFunc := metadata.ValueFunc(ctx, "test/something")
// value := valueFunc()
// // use 'value'
package metadata
import (

View File

@@ -21,6 +21,7 @@ the following metrics are exported:
* `coredns_dns_response_size_bytes{server, zone, view, proto}` - response size in bytes.
* `coredns_dns_responses_total{server, zone, view, rcode, plugin}` - response per zone, rcode and plugin.
* `coredns_dns_https_responses_total{server, status}` - responses per server and http status code.
* `coredns_dns_quic_responses_total{server, status}` - responses per server and QUIC application code.
* `coredns_plugin_enabled{server, zone, view, name}` - indicates whether a plugin is enabled on per server, zone and view basis.
Almost each counter has a label `zone` which is the zonename used for the request/response.

View File

@@ -17,19 +17,21 @@ var (
}, []string{"server", "zone", "view", "proto", "family", "type"})
RequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "request_duration_seconds",
Buckets: plugin.TimeBuckets,
Help: "Histogram of the time (in seconds) each request took per zone.",
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "request_duration_seconds",
Buckets: plugin.TimeBuckets,
NativeHistogramBucketFactor: plugin.NativeHistogramBucketFactor,
Help: "Histogram of the time (in seconds) each request took per zone.",
}, []string{"server", "zone", "view"})
RequestSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "request_size_bytes",
Help: "Size of the EDNS0 UDP buffer in bytes (64K for TCP) per zone and protocol.",
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "request_size_bytes",
Help: "Size of the EDNS0 UDP buffer in bytes (64K for TCP) per zone and protocol.",
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
NativeHistogramBucketFactor: plugin.NativeHistogramBucketFactor,
}, []string{"server", "zone", "view", "proto"})
RequestDo = promauto.NewCounterVec(prometheus.CounterOpts{
@@ -40,11 +42,12 @@ var (
}, []string{"server", "zone", "view"})
ResponseSize = promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "response_size_bytes",
Help: "Size of the returned response in bytes.",
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "response_size_bytes",
Help: "Size of the returned response in bytes.",
Buckets: []float64{0, 100, 200, 300, 400, 511, 1023, 2047, 4095, 8291, 16e3, 32e3, 48e3, 64e3},
NativeHistogramBucketFactor: plugin.NativeHistogramBucketFactor,
}, []string{"server", "zone", "view", "proto"})
ResponseRcode = promauto.NewCounterVec(prometheus.CounterOpts{
@@ -72,6 +75,13 @@ var (
Name: "https_responses_total",
Help: "Counter of DoH responses per server and http status code.",
}, []string{"server", "status"})
QUICResponsesCount = promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: plugin.Namespace,
Subsystem: subsystem,
Name: "quic_responses_total",
Help: "Counter of DoQ responses per server and QUIC application code.",
}, []string{"server", "status"})
)
const (

View File

@@ -48,5 +48,6 @@ const (
// MinimalDefaultTTL is the absolute lowest TTL we use in CoreDNS.
MinimalDefaultTTL = 5 * time.Second
// MaximumDefaulTTL is the maximum TTL was use on RRsets in CoreDNS.
// TODO: rename as MaximumDefaultTTL
MaximumDefaulTTL = 1 * time.Hour
)

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"github.com/miekg/dns"
)
@@ -16,18 +17,30 @@ 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.
// NewRequest returns a new DoH request given a HTTP method, URL and dns.Msg.
//
// The URL should not have a path, so please exclude /dns-query. The URL will
// be prefixed with https:// by default, unless it's already prefixed with
// either http:// or https://.
func NewRequest(method, url string, m *dns.Msg) (*http.Request, error) {
buf, err := m.Pack()
if err != nil {
return nil, err
}
if !strings.HasPrefix(url, "http://") && !strings.HasPrefix(url, "https://") {
url = fmt.Sprintf("https://%s", url)
}
switch method {
case http.MethodGet:
b64 := base64.RawURLEncoding.EncodeToString(buf)
req, err := http.NewRequest(http.MethodGet, "https://"+url+Path+"?dns="+b64, nil)
req, err := http.NewRequest(
http.MethodGet,
fmt.Sprintf("%s%s?dns=%s", url, Path, b64),
nil,
)
if err != nil {
return req, err
}
@@ -37,7 +50,11 @@ func NewRequest(method, url string, m *dns.Msg) (*http.Request, error) {
return req, nil
case http.MethodPost:
req, err := http.NewRequest(http.MethodPost, "https://"+url+Path+"?bla=foo:443", bytes.NewReader(buf))
req, err := http.NewRequest(
http.MethodPost,
fmt.Sprintf("%s%s?bla=foo:443", url, Path),
bytes.NewReader(buf),
)
if err != nil {
return req, err
}

View File

@@ -36,8 +36,7 @@ func SupportedOption(option uint16) bool {
// Version checks the EDNS version in the request. If error
// is nil everything is OK and we can invoke the plugin. If non-nil, the
// returned Msg is valid to be returned to the client (and should). For some
// reason this response should not contain a question RR in the question section.
// returned Msg is valid to be returned to the client (and should).
func Version(req *dns.Msg) (*dns.Msg, error) {
opt := req.IsEdns0()
if opt == nil {
@@ -48,8 +47,6 @@ func Version(req *dns.Msg) (*dns.Msg, error) {
}
m := new(dns.Msg)
m.SetReply(req)
// zero out question section, wtf.
m.Question = nil
o := new(dns.OPT)
o.Hdr.Name = "."

View File

@@ -13,7 +13,7 @@ import (
"io"
golog "log"
"os"
"sync"
"sync/atomic"
)
// D controls whether we should output debug logs. If true, we do, once set
@@ -21,30 +21,22 @@ import (
var D = &d{}
type d struct {
on bool
sync.RWMutex
on atomic.Bool
}
// Set enables debug logging.
func (d *d) Set() {
d.Lock()
d.on = true
d.Unlock()
d.on.Store(true)
}
// Clear disables debug logging.
func (d *d) Clear() {
d.Lock()
d.on = false
d.Unlock()
d.on.Store(false)
}
// Value returns if debug logging is enabled.
func (d *d) Value() bool {
d.RLock()
b := d.on
d.RUnlock()
return b
return d.on.Load()
}
// logf calls log.Printf prefixed with level.

View File

@@ -1,21 +0,0 @@
// Package nonwriter implements a dns.ResponseWriter that never writes, but captures the dns.Msg being written.
package nonwriter
import (
"github.com/miekg/dns"
)
// Writer is a type of ResponseWriter that captures the message, but never writes to the client.
type Writer struct {
dns.ResponseWriter
Msg *dns.Msg
}
// New makes and returns a new NonWriter.
func New(w dns.ResponseWriter) *Writer { return &Writer{ResponseWriter: w} }
// WriteMsg records the message, but doesn't write it itself.
func (w *Writer) WriteMsg(res *dns.Msg) error {
w.Msg = res
return nil
}

View File

@@ -33,6 +33,14 @@ func HostPortOrFile(s ...string) ([]string, error) {
var servers []string
for _, h := range s {
trans, host := Transport(h)
if len(host) == 0 {
return servers, fmt.Errorf("invalid address: %q", h)
}
if trans == transport.UNIX {
servers = append(servers, trans+"://"+host)
continue
}
addr, _, err := net.SplitHostPort(host)
@@ -53,6 +61,8 @@ func HostPortOrFile(s ...string) ([]string, error) {
ss = net.JoinHostPort(host, transport.Port)
case transport.TLS:
ss = transport.TLS + "://" + net.JoinHostPort(host, transport.TLSPort)
case transport.QUIC:
ss = transport.QUIC + "://" + net.JoinHostPort(host, transport.QUICPort)
case transport.GRPC:
ss = transport.GRPC + "://" + net.JoinHostPort(host, transport.GRPCPort)
case transport.HTTPS:
@@ -89,7 +99,7 @@ func tryFile(s string) ([]string, error) {
servers := []string{}
for _, s := range c.Servers {
servers = append(servers, net.JoinHostPort(s, c.Port))
servers = append(servers, net.JoinHostPort(stripZone(s), c.Port))
}
return servers, nil
}

View File

@@ -19,6 +19,10 @@ func Transport(s string) (trans string, addr string) {
s = s[len(transport.DNS+"://"):]
return transport.DNS, s
case strings.HasPrefix(s, transport.QUIC+"://"):
s = s[len(transport.QUIC+"://"):]
return transport.QUIC, s
case strings.HasPrefix(s, transport.GRPC+"://"):
s = s[len(transport.GRPC+"://"):]
return transport.GRPC, s
@@ -27,6 +31,9 @@ func Transport(s string) (trans string, addr string) {
s = s[len(transport.HTTPS+"://"):]
return transport.HTTPS, s
case strings.HasPrefix(s, transport.UNIX+"://"):
s = s[len(transport.UNIX+"://"):]
return transport.UNIX, s
}
return transport.DNS, s

View File

@@ -4,8 +4,10 @@ package transport
const (
DNS = "dns"
TLS = "tls"
QUIC = "quic"
GRPC = "grpc"
HTTPS = "https"
UNIX = "unix"
)
// Port numbers for the various transports.
@@ -14,6 +16,8 @@ const (
Port = "53"
// TLSPort is the default port for DNS-over-TLS.
TLSPort = "853"
// QUICPort is the default port for DNS-over-QUIC.
QUICPort = "853"
// GRPCPort is the default port for DNS-over-gRPC.
GRPCPort = "443"
// HTTPSPort is the default port for DNS-over-HTTPS.

View File

@@ -108,5 +108,9 @@ var TimeBuckets = prometheus.ExponentialBuckets(0.00025, 2, 16) // from 0.25ms t
// SlimTimeBuckets is low cardinality set of duration buckets.
var SlimTimeBuckets = prometheus.ExponentialBuckets(0.00025, 10, 5) // from 0.25ms to 2.5 seconds
// NativeHistogramBucketFactor controls the resolution of Prometheus native histogram buckets.
// See: https://pkg.go.dev/github.com/prometheus/client_golang@v1.19.0/prometheus#section-readme
var NativeHistogramBucketFactor = 1.05
// ErrOnce is returned when a plugin doesn't support multiple setups per server.
var ErrOnce = errors.New("this plugin can only be used once per Server Block")

View File

@@ -3,6 +3,7 @@ package test
import (
"os"
"path/filepath"
"testing"
)
// TempFile will create a temporary file on disk and returns the name and a cleanup function to remove it later.
@@ -18,12 +19,9 @@ func TempFile(dir, content string) (string, func(), error) {
return f.Name(), rmFunc, nil
}
// WritePEMFiles creates a tmp dir with ca.pem, cert.pem, and key.pem and the func to remove it
func WritePEMFiles(dir string) (string, func(), error) {
tempDir, err := os.MkdirTemp(dir, "go-test-pemfiles")
if err != nil {
return "", nil, err
}
// WritePEMFiles creates a tmp dir with ca.pem, cert.pem, and key.pem
func WritePEMFiles(t *testing.T) (string, error) {
tempDir := t.TempDir()
data := `-----BEGIN CERTIFICATE-----
MIIC9zCCAd+gAwIBAgIJALGtqdMzpDemMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV
@@ -45,7 +43,7 @@ I1rs/VUGKzcJGVIWbHrgjP68CTStGAvKgbsTqw7aLXTSqtPw88N9XVSyRg==
-----END CERTIFICATE-----`
path := filepath.Join(tempDir, "ca.pem")
if err := os.WriteFile(path, []byte(data), 0644); err != nil {
return "", nil, err
return "", err
}
data = `-----BEGIN CERTIFICATE-----
MIICozCCAYsCCQCRlf5BrvPuqjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdr
@@ -65,8 +63,8 @@ zhDEPP4FhY+Sz+y1yWirphl7A1aZwhXVPcfWIGqpQ3jzNwUeocbH27kuLh+U4hQo
qeg10RdFnw==
-----END CERTIFICATE-----`
path = filepath.Join(tempDir, "cert.pem")
if err = os.WriteFile(path, []byte(data), 0644); err != nil {
return "", nil, err
if err := os.WriteFile(path, []byte(data), 0644); err != nil {
return "", err
}
data = `-----BEGIN RSA PRIVATE KEY-----
@@ -97,10 +95,9 @@ E/WObVJXDnBdViu0L9abE9iaTToBVri4cmlDlZagLuKVR+TFTCN/DSlVZTDkqkLI
8chzqtkH6b2b2R73hyRysWjsomys34ma3mEEPTX/aXeAF2MSZ/EWT9yL
-----END RSA PRIVATE KEY-----`
path = filepath.Join(tempDir, "key.pem")
if err = os.WriteFile(path, []byte(data), 0644); err != nil {
return "", nil, err
if err := os.WriteFile(path, []byte(data), 0644); err != nil {
return "", err
}
rmFunc := func() { os.RemoveAll(tempDir) }
return tempDir, rmFunc, nil
return tempDir, nil
}

View File

@@ -29,15 +29,19 @@ func (p RRSet) Less(i, j int) bool { return p[i].String() < p[j].String() }
// Case represents a test case that encapsulates various data from a query and response.
// Note that is the TTL of a record is 303 we don't compare it with the TTL.
type Case struct {
Qname string
Qtype uint16
Rcode int
Do bool
AuthenticatedData bool
Answer []dns.RR
Ns []dns.RR
Extra []dns.RR
Error error
Qname string
Qtype uint16
Rcode int
Do bool
CheckingDisabled bool
RecursionAvailable bool
AuthenticatedData bool
Authoritative bool
Truncated bool
Answer []dns.RR
Ns []dns.RR
Extra []dns.RR
Error error
}
// Msg returns a *dns.Msg embedded in c.

View File

@@ -19,7 +19,6 @@
//
// result := Scrape("http://localhost:9153/metrics")
// v := MetricValue("coredns_cache_capacity", result)
//
package test
import (
@@ -217,7 +216,7 @@ func makeBuckets(m *dto.Metric) map[string]string {
func fetchMetricFamilies(url string, ch chan<- *dto.MetricFamily) {
defer close(ch)
req, err := http.NewRequest("GET", url, nil)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return
}