2017-03-13 20:24:37 +00:00
|
|
|
package dnsserver
|
|
|
|
|
|
|
|
|
|
import (
|
2018-04-20 11:01:06 +01:00
|
|
|
"context"
|
2017-03-13 20:24:37 +00:00
|
|
|
"crypto/tls"
|
|
|
|
|
"errors"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net"
|
|
|
|
|
|
2020-09-24 18:14:41 +02:00
|
|
|
"github.com/coredns/caddy"
|
2018-06-21 12:34:02 +01:00
|
|
|
"github.com/coredns/coredns/pb"
|
2026-07-16 03:54:46 +03:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/dnsutil"
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
cproxyproto "github.com/coredns/coredns/plugin/pkg/proxyproto"
|
2019-11-17 02:02:46 +00:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/reuseport"
|
2018-09-19 07:29:37 +01:00
|
|
|
"github.com/coredns/coredns/plugin/pkg/transport"
|
2018-06-21 12:34:02 +01:00
|
|
|
|
2017-04-18 11:10:49 -04:00
|
|
|
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
2017-03-13 20:24:37 +00:00
|
|
|
"github.com/miekg/dns"
|
2018-01-14 03:09:14 -05:00
|
|
|
"github.com/opentracing/opentracing-go"
|
2026-02-11 02:14:05 +01:00
|
|
|
"github.com/pires/go-proxyproto"
|
2025-12-18 05:08:59 +02:00
|
|
|
"golang.org/x/net/netutil"
|
2017-03-13 20:24:37 +00:00
|
|
|
"google.golang.org/grpc"
|
|
|
|
|
"google.golang.org/grpc/peer"
|
|
|
|
|
)
|
|
|
|
|
|
2025-12-18 05:08:59 +02:00
|
|
|
const (
|
|
|
|
|
// maxDNSMessageBytes is the maximum size of a DNS message on the wire.
|
|
|
|
|
maxDNSMessageBytes = dns.MaxMsgSize
|
|
|
|
|
|
|
|
|
|
// maxProtobufPayloadBytes accounts for protobuf overhead.
|
|
|
|
|
// Field tag=1 (1 byte) + length varint for 65535 (3 bytes) = 4 bytes total
|
|
|
|
|
maxProtobufPayloadBytes = maxDNSMessageBytes + 4
|
|
|
|
|
|
|
|
|
|
// DefaultGRPCMaxStreams is the default maximum number of concurrent streams per connection.
|
|
|
|
|
DefaultGRPCMaxStreams = 256
|
|
|
|
|
|
|
|
|
|
// DefaultGRPCMaxConnections is the default maximum number of concurrent connections.
|
|
|
|
|
DefaultGRPCMaxConnections = 200
|
|
|
|
|
)
|
|
|
|
|
|
2017-06-14 09:37:10 -07:00
|
|
|
// ServergRPC represents an instance of a DNS-over-gRPC server.
|
|
|
|
|
type ServergRPC struct {
|
2017-03-13 20:24:37 +00:00
|
|
|
*Server
|
2022-02-17 23:11:45 -08:00
|
|
|
*pb.UnimplementedDnsServiceServer
|
2025-12-18 05:08:59 +02:00
|
|
|
grpcServer *grpc.Server
|
|
|
|
|
listenAddr net.Addr
|
|
|
|
|
tlsConfig *tls.Config
|
|
|
|
|
maxStreams int
|
|
|
|
|
maxConnections int
|
2017-03-13 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
|
2017-09-14 09:36:06 +01:00
|
|
|
// NewServergRPC returns a new CoreDNS GRPC server and compiles all plugin in to it.
|
2017-06-14 09:37:10 -07:00
|
|
|
func NewServergRPC(addr string, group []*Config) (*ServergRPC, error) {
|
2026-09-21 23:56:27 -07:00
|
|
|
tlsConfig, err := sharedTLSConfig(addr, group)
|
2017-03-13 20:24:37 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-09-21 23:56:27 -07:00
|
|
|
s, err := NewServer(addr, group)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
2018-01-14 03:09:14 -05:00
|
|
|
}
|
2021-10-08 15:56:33 +02:00
|
|
|
// http/2 is required when using gRPC. We need to specify it in next protos
|
|
|
|
|
// or the upgrade won't happen.
|
|
|
|
|
if tlsConfig != nil {
|
|
|
|
|
tlsConfig.NextProtos = []string{"h2"}
|
|
|
|
|
}
|
2018-01-14 03:09:14 -05:00
|
|
|
|
2025-12-18 05:08:59 +02:00
|
|
|
maxStreams := DefaultGRPCMaxStreams
|
|
|
|
|
if len(group) > 0 && group[0] != nil && group[0].MaxGRPCStreams != nil {
|
|
|
|
|
maxStreams = *group[0].MaxGRPCStreams
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
maxConnections := DefaultGRPCMaxConnections
|
|
|
|
|
if len(group) > 0 && group[0] != nil && group[0].MaxGRPCConnections != nil {
|
|
|
|
|
maxConnections = *group[0].MaxGRPCConnections
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &ServergRPC{
|
|
|
|
|
Server: s,
|
|
|
|
|
tlsConfig: tlsConfig,
|
|
|
|
|
maxStreams: maxStreams,
|
|
|
|
|
maxConnections: maxConnections,
|
|
|
|
|
}, nil
|
2017-03-13 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-05 11:34:20 -07:00
|
|
|
// Compile-time check to ensure ServergRPC implements the caddy.GracefulServer interface
|
|
|
|
|
var _ caddy.GracefulServer = &ServergRPC{}
|
2020-03-06 16:25:07 +08:00
|
|
|
|
2017-03-13 20:24:37 +00:00
|
|
|
// Serve implements caddy.TCPServer interface.
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) Serve(l net.Listener) error {
|
2017-03-13 20:24:37 +00:00
|
|
|
s.m.Lock()
|
|
|
|
|
s.listenAddr = l.Addr()
|
|
|
|
|
s.m.Unlock()
|
|
|
|
|
|
2025-12-18 05:08:59 +02:00
|
|
|
serverOpts := []grpc.ServerOption{
|
|
|
|
|
grpc.MaxRecvMsgSize(maxProtobufPayloadBytes),
|
|
|
|
|
grpc.MaxSendMsgSize(maxProtobufPayloadBytes),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only set MaxConcurrentStreams if not unbounded (0)
|
|
|
|
|
if s.maxStreams > 0 {
|
2026-01-01 13:50:29 +05:30
|
|
|
serverOpts = append(serverOpts, grpc.MaxConcurrentStreams(uint32(s.maxStreams))) // #nosec G115 -- maxStreams is bounded
|
2025-12-18 05:08:59 +02:00
|
|
|
}
|
|
|
|
|
|
2017-04-18 11:10:49 -04:00
|
|
|
if s.Tracer() != nil {
|
2026-03-30 03:02:20 +03:00
|
|
|
onlyIfParent := func(parentSpanCtx opentracing.SpanContext, _method string, _req, _resp any) bool {
|
2017-04-18 11:10:49 -04:00
|
|
|
return parentSpanCtx != nil
|
|
|
|
|
}
|
2025-12-18 05:08:59 +02:00
|
|
|
serverOpts = append(serverOpts, grpc.UnaryInterceptor(otgrpc.OpenTracingServerInterceptor(s.Tracer(), otgrpc.IncludingSpans(onlyIfParent))))
|
2017-04-18 11:10:49 -04:00
|
|
|
}
|
|
|
|
|
|
2025-12-18 05:08:59 +02:00
|
|
|
s.grpcServer = grpc.NewServer(serverOpts...)
|
|
|
|
|
|
2017-04-18 11:10:49 -04:00
|
|
|
pb.RegisterDnsServiceServer(s.grpcServer, s)
|
|
|
|
|
|
2018-01-14 03:09:14 -05:00
|
|
|
if s.tlsConfig != nil {
|
|
|
|
|
l = tls.NewListener(l, s.tlsConfig)
|
|
|
|
|
}
|
2025-12-18 05:08:59 +02:00
|
|
|
|
|
|
|
|
// Wrap listener to limit concurrent connections
|
|
|
|
|
if s.maxConnections > 0 {
|
|
|
|
|
l = netutil.LimitListener(l, s.maxConnections)
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-13 20:24:37 +00:00
|
|
|
return s.grpcServer.Serve(l)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ServePacket implements caddy.UDPServer interface.
|
2026-03-30 03:02:20 +03:00
|
|
|
func (s *ServergRPC) ServePacket(_p net.PacketConn) error { return nil }
|
2017-03-13 20:24:37 +00:00
|
|
|
|
|
|
|
|
// Listen implements caddy.TCPServer interface.
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) Listen() (net.Listener, error) {
|
2019-11-17 02:02:46 +00:00
|
|
|
l, err := reuseport.Listen("tcp", s.Addr[len(transport.GRPC+"://"):])
|
2017-03-13 20:24:37 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-02-11 02:14:05 +01:00
|
|
|
if s.connPolicy != nil {
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
if s.proxyProtoTLVMetadata {
|
|
|
|
|
l = cproxyproto.NewTLVListener(l, s.connPolicy)
|
|
|
|
|
} else {
|
|
|
|
|
l = &proxyproto.Listener{Listener: l, ConnPolicy: s.connPolicy}
|
|
|
|
|
}
|
2026-02-11 02:14:05 +01:00
|
|
|
}
|
2017-03-13 20:24:37 +00:00
|
|
|
return l, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ListenPacket implements caddy.UDPServer interface.
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) ListenPacket() (net.PacketConn, error) { return nil, nil }
|
2017-03-13 20:24:37 +00:00
|
|
|
|
|
|
|
|
// OnStartupComplete lists the sites served by this server
|
|
|
|
|
// and any relevant information, assuming Quiet is false.
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) OnStartupComplete() {
|
2017-03-13 20:24:37 +00:00
|
|
|
if Quiet {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-19 07:29:37 +01:00
|
|
|
out := startUpZones(transport.GRPC+"://", s.Addr, s.zones)
|
2018-03-17 19:04:01 +00:00
|
|
|
if out != "" {
|
2026-09-17 08:50:39 +08:00
|
|
|
printStartup(out)
|
2017-03-13 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-04 09:46:40 -07:00
|
|
|
// Stop stops the server. It blocks until the server is
|
|
|
|
|
// totally stopped.
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) Stop() (err error) {
|
2017-03-13 20:24:37 +00:00
|
|
|
s.m.Lock()
|
|
|
|
|
defer s.m.Unlock()
|
|
|
|
|
if s.grpcServer != nil {
|
|
|
|
|
s.grpcServer.GracefulStop()
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Query is the main entry-point into the gRPC server. From here we call ServeDNS like
|
|
|
|
|
// any normal server. We use a custom responseWriter to pick up the bytes we need to write
|
|
|
|
|
// back to the client as a protobuf.
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket, error) {
|
2025-12-18 05:08:59 +02:00
|
|
|
if len(in.GetMsg()) > dns.MaxMsgSize {
|
|
|
|
|
return nil, fmt.Errorf("dns message exceeds size limit: %d", len(in.GetMsg()))
|
|
|
|
|
}
|
2026-07-16 03:54:46 +03:00
|
|
|
msg, err := dnsutil.UnpackRequest(in.GetMsg())
|
2017-03-13 20:24:37 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
p, ok := peer.FromContext(ctx)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, errors.New("no peer in gRPC context")
|
|
|
|
|
}
|
|
|
|
|
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
// The peer address is normally a *net.TCPAddr. When proxyproto tlv_metadata
|
|
|
|
|
// is enabled, the TLV-capturing listener surfaces a *cproxyproto.Addr whose
|
|
|
|
|
// RemoteAddr carries the PPv2 TLVs; accept it so the metadata provider can
|
|
|
|
|
// read them. Validate the real client address is TCP in both cases.
|
|
|
|
|
var remoteAddr net.Addr
|
|
|
|
|
switch pa := p.Addr.(type) {
|
|
|
|
|
case *cproxyproto.Addr:
|
|
|
|
|
remoteAddr = pa
|
|
|
|
|
case *net.TCPAddr:
|
|
|
|
|
remoteAddr = pa
|
|
|
|
|
default:
|
2017-03-13 20:24:37 +00:00
|
|
|
return nil, fmt.Errorf("no TCP peer in gRPC context: %v", p.Addr)
|
|
|
|
|
}
|
|
|
|
|
|
plugin/proxyproto: expose PPv2 TLVs to downstream plugins as metadata (#8641)
The proxyproto plugin parsed PROXY protocol v2 headers only to recover the real
client address; the TLV vector was discarded, so downstream plugins had no way to
read values such as the AWS VPC endpoint ID, SSL client CN, or vendor-specific
custom TLVs.
Add an opt-in `tlv_metadata` option. When set, the connection's raw PPv2 TLV
vector is captured and the plugin, acting as a metadata.Provider, publishes it
under the `proxyproto/tlv` label as a hex-encoded string. Consumers decode and
split it with proxyproto.SplitTLVs. The option is off by default, so existing
deployments skip the extra per-connection processing and behave exactly as before.
PROXY protocol v1 connections, which carry no TLVs, are unaffected.
Capture spans all transports: dns:// (UDP and TCP), tls:// (DoT), https:// (DoH),
grpc://, quic:// (DoQ), and https3:// (DoH3). QUIC-based transports require
udp_session_tracking, since the PPv2 header arrives as a standalone first datagram
ahead of the QUIC handshake.
Includes unit tests for capture, the metadata provider (direct and through the
real metadata chain), v1 compatibility, and option parsing, plus README and man
page updates.
Fixes #8633
Signed-off-by: Mekias Yohannes <mekias@amazon.com>
Co-authored-by: Mekias Yohannes <mekias@amazon.com>
2026-10-15 22:28:43 -04:00
|
|
|
w := &gRPCresponse{localAddr: s.listenAddr, remoteAddr: remoteAddr, Msg: msg}
|
2017-03-13 20:24:37 +00:00
|
|
|
|
2026-03-24 13:46:15 -07:00
|
|
|
if tsig := msg.IsTsig(); tsig != nil {
|
2026-09-15 20:02:49 -07:00
|
|
|
if s.TsigSecret == nil {
|
2026-03-24 13:46:15 -07:00
|
|
|
w.tsigStatus = dns.ErrSecret
|
2026-09-15 20:02:49 -07:00
|
|
|
} else if secret, ok := s.TsigSecret[tsig.Hdr.Name]; !ok {
|
2026-03-24 13:46:15 -07:00
|
|
|
w.tsigStatus = dns.ErrSecret
|
2026-04-04 01:58:36 -07:00
|
|
|
} else {
|
|
|
|
|
w.tsigStatus = dns.TsigVerify(in.GetMsg(), secret, "", false)
|
2026-03-24 13:46:15 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-05-27 17:52:48 +03:00
|
|
|
dnsCtx := context.WithValue(ctx, Key{}, s.Server)
|
2021-01-15 19:26:04 +01:00
|
|
|
dnsCtx = context.WithValue(dnsCtx, LoopKey{}, 0)
|
2019-05-27 17:52:48 +03:00
|
|
|
s.ServeDNS(dnsCtx, w, msg)
|
2017-03-13 20:24:37 +00:00
|
|
|
|
|
|
|
|
packed, err := w.Msg.Pack()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &pb.DnsPacket{Msg: packed}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-04 09:46:40 -07:00
|
|
|
// Shutdown stops the server (non gracefully).
|
2017-06-14 09:37:10 -07:00
|
|
|
func (s *ServergRPC) Shutdown() error {
|
2017-05-25 12:08:34 -07:00
|
|
|
if s.grpcServer != nil {
|
|
|
|
|
s.grpcServer.Stop()
|
2017-03-13 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type gRPCresponse struct {
|
|
|
|
|
localAddr net.Addr
|
|
|
|
|
remoteAddr net.Addr
|
|
|
|
|
Msg *dns.Msg
|
2026-03-24 13:46:15 -07:00
|
|
|
tsigStatus error
|
2017-03-13 20:24:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write is the hack that makes this work. It does not actually write the message
|
2019-02-20 19:12:21 +07:00
|
|
|
// but returns the bytes we need to write in r. We can then pick this up in Query
|
2017-03-13 20:24:37 +00:00
|
|
|
// and write a proper protobuf back to the client.
|
|
|
|
|
func (r *gRPCresponse) Write(b []byte) (int, error) {
|
|
|
|
|
r.Msg = new(dns.Msg)
|
|
|
|
|
return len(b), r.Msg.Unpack(b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// These methods implement the dns.ResponseWriter interface from Go DNS.
|
2025-10-23 20:01:57 +03:00
|
|
|
|
2017-03-13 20:24:37 +00:00
|
|
|
func (r *gRPCresponse) Close() error { return nil }
|
2026-03-24 13:46:15 -07:00
|
|
|
func (r *gRPCresponse) TsigStatus() error { return r.tsigStatus }
|
2026-03-30 03:02:20 +03:00
|
|
|
func (r *gRPCresponse) TsigTimersOnly(_b bool) {}
|
2019-09-23 21:40:14 +08:00
|
|
|
func (r *gRPCresponse) Hijack() {}
|
2017-03-13 20:24:37 +00:00
|
|
|
func (r *gRPCresponse) LocalAddr() net.Addr { return r.localAddr }
|
|
|
|
|
func (r *gRPCresponse) RemoteAddr() net.Addr { return r.remoteAddr }
|
2025-07-14 22:48:29 -07:00
|
|
|
func (r *gRPCresponse) Network() string { return "" }
|
2017-03-13 20:24:37 +00:00
|
|
|
func (r *gRPCresponse) WriteMsg(m *dns.Msg) error { r.Msg = m; return nil }
|