mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 08:14:18 -04:00
core: add more transports (#574)
* core: add listening for other protocols
Allow CoreDNS to listen for TLS request coming over port 853. This can
be enabled with `tls://` in the config file.
Implement listening for grps:// as well.
a Corefile like:
~~~
. tls://.:1853 {
whoami
tls
}
~~~
Means we listen on 1853 for tls requests, the `tls` config item allows
configuration for TLS parameters. We *might* be tempted to use Caddy's
Let's Encrypt implementation here.
* Refactor coredns/grpc into CoreDNS
This makes gRPC a first class citizen in CoreDNS. Add defines as being
just another server.
* some cleanups
* unexport the servers
* Move protobuf dir
* Hook up TLS properly
* Fix test
* listen for TLS as well. README updates
* disable test, fix package
* fix test
* Fix tests
* Fix remaining test
* Some tests
* Make the test work
* Add grpc test from #580
* fix crash
* Fix tests
* Close conn
* README cleanups
* README
* link RFC
This commit is contained in:
@@ -9,12 +9,26 @@ import (
|
||||
)
|
||||
|
||||
type zoneAddr struct {
|
||||
Zone string
|
||||
Port string
|
||||
Zone string
|
||||
Port string
|
||||
Transport string // dns, tls or grpc
|
||||
}
|
||||
|
||||
// String return z.Zone + ":" + z.Port as a string.
|
||||
func (z zoneAddr) String() string { return z.Zone + ":" + z.Port }
|
||||
// String return the string represenation of z.
|
||||
func (z zoneAddr) String() string { return z.Transport + "://" + z.Zone + ":" + z.Port }
|
||||
|
||||
// Transport returns the protocol of the string s
|
||||
func Transport(s string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(s, TransportTLS+"://"):
|
||||
return TransportTLS
|
||||
case strings.HasPrefix(s, TransportDNS+"://"):
|
||||
return TransportDNS
|
||||
case strings.HasPrefix(s, TransportGRPC+"://"):
|
||||
return TransportGRPC
|
||||
}
|
||||
return TransportDNS
|
||||
}
|
||||
|
||||
// normalizeZone parses an zone string into a structured format with separate
|
||||
// host, and port portions, as well as the original input string.
|
||||
@@ -23,14 +37,28 @@ func (z zoneAddr) String() string { return z.Zone + ":" + z.Port }
|
||||
func normalizeZone(str string) (zoneAddr, error) {
|
||||
var err error
|
||||
|
||||
// separate host and port
|
||||
// Default to DNS if there isn't a transport protocol prefix.
|
||||
trans := TransportDNS
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(str, TransportTLS+"://"):
|
||||
trans = TransportTLS
|
||||
str = str[len(TransportTLS+"://"):]
|
||||
case strings.HasPrefix(str, TransportDNS+"://"):
|
||||
trans = TransportDNS
|
||||
str = str[len(TransportDNS+"://"):]
|
||||
case strings.HasPrefix(str, TransportGRPC+"://"):
|
||||
trans = TransportGRPC
|
||||
str = str[len(TransportGRPC+"://"):]
|
||||
}
|
||||
|
||||
host, port, err := net.SplitHostPort(str)
|
||||
if err != nil {
|
||||
host, port, err = net.SplitHostPort(str + ":")
|
||||
// no error check here; return err at end of function
|
||||
}
|
||||
|
||||
if len(host) > 255 {
|
||||
if len(host) > 255 { // TODO(miek): this should take escaping into account.
|
||||
return zoneAddr{}, fmt.Errorf("specified zone is too long: %d > 255", len(host))
|
||||
}
|
||||
_, d := dns.IsDomainName(host)
|
||||
@@ -39,8 +67,23 @@ func normalizeZone(str string) (zoneAddr, error) {
|
||||
}
|
||||
|
||||
if port == "" {
|
||||
port = Port
|
||||
if trans == TransportDNS {
|
||||
port = Port
|
||||
}
|
||||
if trans == TransportTLS {
|
||||
port = TLSPort
|
||||
}
|
||||
if trans == TransportGRPC {
|
||||
port = GRPCPort
|
||||
}
|
||||
}
|
||||
|
||||
return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port}, err
|
||||
return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port, Transport: trans}, err
|
||||
}
|
||||
|
||||
// Supported transports.
|
||||
const (
|
||||
TransportDNS = "dns"
|
||||
TransportTLS = "tls"
|
||||
TransportGRPC = "grpc"
|
||||
)
|
||||
|
||||
@@ -8,10 +8,10 @@ func TestNormalizeZone(t *testing.T) {
|
||||
expected string
|
||||
shouldErr bool
|
||||
}{
|
||||
{".", ".:53", false},
|
||||
{".:54", ".:54", false},
|
||||
{"..", ":", true},
|
||||
{"..", ":", true},
|
||||
{".", "dns://.:53", false},
|
||||
{".:54", "dns://.:54", false},
|
||||
{"..", "://:", true},
|
||||
{"..", "://:", true},
|
||||
} {
|
||||
addr, err := normalizeZone(test.input)
|
||||
actual := addr.String()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package dnsserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/coredns/coredns/middleware"
|
||||
|
||||
"github.com/mholt/caddy"
|
||||
@@ -21,8 +23,12 @@ type Config struct {
|
||||
// First consumer is the file middleware to looks for zone files in this place.
|
||||
Root string
|
||||
|
||||
// Server is the server that handles this config
|
||||
Server *Server
|
||||
// The transport we implement, normally just "dns" over TCP/UDP, but could be
|
||||
// DNS-over-TLS or DNS-over-gRPC.
|
||||
Transport string
|
||||
|
||||
// TLSConfig when listening for encrypted connections (gRPC, DNS-over-TLS).
|
||||
TLSConfig *tls.Config
|
||||
|
||||
// Middleware stack.
|
||||
Middleware []middleware.Middleware
|
||||
@@ -50,7 +56,6 @@ func GetConfig(c *caddy.Controller) *Config {
|
||||
// Note that this is order dependent and the order is defined in directives.go, i.e. if your middleware
|
||||
// comes before the middleware you are checking; it will not be there (yet).
|
||||
func GetMiddleware(c *caddy.Controller, name string) middleware.Handler {
|
||||
// TODO(miek): calling the handler h(nil) should be a noop...
|
||||
conf := GetConfig(c)
|
||||
for _, h := range conf.Middleware {
|
||||
x := h(nil)
|
||||
|
||||
@@ -61,15 +61,16 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
|
||||
return nil, err
|
||||
}
|
||||
s.Keys[i] = za.String()
|
||||
if v, ok := dups[za.Zone]; ok {
|
||||
if v, ok := dups[za.String()]; ok {
|
||||
return nil, fmt.Errorf("cannot serve %s - zone already defined for %v", za, v)
|
||||
}
|
||||
dups[za.Zone] = za.String()
|
||||
dups[za.String()] = za.String()
|
||||
|
||||
// Save the config to our master list, and key it for lookups
|
||||
cfg := &Config{
|
||||
Zone: za.Zone,
|
||||
Port: za.Port,
|
||||
Zone: za.Zone,
|
||||
Port: za.Port,
|
||||
Transport: za.Transport,
|
||||
}
|
||||
h.saveConfig(za.String(), cfg)
|
||||
}
|
||||
@@ -88,11 +89,31 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
|
||||
// then we create a server for each group
|
||||
var servers []caddy.Server
|
||||
for addr, group := range groups {
|
||||
s, err := NewServer(addr, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// switch on addr
|
||||
switch Transport(addr) {
|
||||
case TransportDNS:
|
||||
s, err := NewServer(addr, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
servers = append(servers, s)
|
||||
|
||||
case TransportTLS:
|
||||
s, err := NewServerTLS(addr, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
servers = append(servers, s)
|
||||
|
||||
case TransportGRPC:
|
||||
s, err := NewServergRPC(addr, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
servers = append(servers, s)
|
||||
|
||||
}
|
||||
servers = append(servers, s)
|
||||
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
@@ -112,14 +133,11 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
||||
groups := make(map[string][]*Config)
|
||||
|
||||
for _, conf := range configs {
|
||||
if conf.Port == "" {
|
||||
conf.Port = Port
|
||||
}
|
||||
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.ListenHost, conf.Port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addrstr := addr.String()
|
||||
addrstr := conf.Transport + "://" + addr.String()
|
||||
groups[addrstr] = append(groups[addrstr], conf)
|
||||
}
|
||||
|
||||
@@ -129,6 +147,10 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
||||
const (
|
||||
// DefaultPort is the default port.
|
||||
DefaultPort = "53"
|
||||
// TLSPort is the default port for DNS-over-TLS.
|
||||
TLSPort = "853"
|
||||
// GRPCPort is the default port for DNS-over-gRPC.
|
||||
GRPCPort = "443"
|
||||
)
|
||||
|
||||
// These "soft defaults" are configurable by
|
||||
|
||||
165
core/dnsserver/server-grpc.go
Normal file
165
core/dnsserver/server-grpc.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package dnsserver
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/peer"
|
||||
|
||||
"github.com/coredns/coredns/pb"
|
||||
)
|
||||
|
||||
// servergRPC represents an instance of a DNS-over-gRPC server.
|
||||
type servergRPC struct {
|
||||
*Server
|
||||
grpcServer *grpc.Server
|
||||
|
||||
listenAddr net.Addr
|
||||
}
|
||||
|
||||
// NewGRPCServer returns a new CoreDNS GRPC server and compiles all middleware in to it.
|
||||
func NewServergRPC(addr string, group []*Config) (*servergRPC, error) {
|
||||
|
||||
s, err := NewServer(addr, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gs := &servergRPC{Server: s}
|
||||
gs.grpcServer = grpc.NewServer()
|
||||
// trace foo... TODO(miek)
|
||||
pb.RegisterDnsServiceServer(gs.grpcServer, gs)
|
||||
|
||||
return gs, nil
|
||||
}
|
||||
|
||||
// Serve implements caddy.TCPServer interface.
|
||||
func (s *servergRPC) Serve(l net.Listener) error {
|
||||
s.m.Lock()
|
||||
s.listenAddr = l.Addr()
|
||||
s.m.Unlock()
|
||||
|
||||
return s.grpcServer.Serve(l)
|
||||
}
|
||||
|
||||
// ServePacket implements caddy.UDPServer interface.
|
||||
func (s *servergRPC) ServePacket(p net.PacketConn) error { return nil }
|
||||
|
||||
// Listen implements caddy.TCPServer interface.
|
||||
func (s *servergRPC) Listen() (net.Listener, error) {
|
||||
|
||||
// The *tls* middleware must make sure that multiple conflicting
|
||||
// TLS configuration return an error: it can only be specified once.
|
||||
tlsConfig := new(tls.Config)
|
||||
for _, conf := range s.zones {
|
||||
// Should we error if some configs *don't* have TLS?
|
||||
tlsConfig = conf.TLSConfig
|
||||
}
|
||||
|
||||
var (
|
||||
l net.Listener
|
||||
err error
|
||||
)
|
||||
|
||||
if tlsConfig == nil {
|
||||
l, err = net.Listen("tcp", s.Addr[len(TransportGRPC+"://"):])
|
||||
} else {
|
||||
l, err = tls.Listen("tcp", s.Addr[len(TransportGRPC+"://"):], tlsConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// ListenPacket implements caddy.UDPServer interface.
|
||||
func (s *servergRPC) 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 *servergRPC) OnStartupComplete() {
|
||||
if Quiet {
|
||||
return
|
||||
}
|
||||
|
||||
for zone, config := range s.zones {
|
||||
fmt.Println(TransportGRPC + "://" + zone + ":" + config.Port)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *servergRPC) Stop() (err error) {
|
||||
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.
|
||||
func (s *servergRPC) Query(ctx context.Context, in *pb.DnsPacket) (*pb.DnsPacket, error) {
|
||||
msg := new(dns.Msg)
|
||||
err := msg.Unpack(in.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, ok := peer.FromContext(ctx)
|
||||
if !ok {
|
||||
return nil, errors.New("no peer in gRPC context")
|
||||
}
|
||||
|
||||
a, ok := p.Addr.(*net.TCPAddr)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no TCP peer in gRPC context: %v", p.Addr)
|
||||
}
|
||||
|
||||
r := &net.IPAddr{IP: a.IP}
|
||||
w := &gRPCresponse{localAddr: s.listenAddr, remoteAddr: r, Msg: msg}
|
||||
|
||||
s.ServeDNS(ctx, w, msg)
|
||||
|
||||
packed, err := w.Msg.Pack()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.DnsPacket{Msg: packed}, nil
|
||||
}
|
||||
|
||||
func (g *servergRPC) Shutdown() error {
|
||||
if g.grpcServer != nil {
|
||||
g.grpcServer.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type gRPCresponse struct {
|
||||
localAddr net.Addr
|
||||
remoteAddr net.Addr
|
||||
Msg *dns.Msg
|
||||
}
|
||||
|
||||
// Write is the hack that makes this work. It does not actually write the message
|
||||
// but returns the bytes we need to to write in r. We can then pick this up in Query
|
||||
// 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.
|
||||
func (r *gRPCresponse) Close() error { return nil }
|
||||
func (r *gRPCresponse) TsigStatus() error { return nil }
|
||||
func (r *gRPCresponse) TsigTimersOnly(b bool) { return }
|
||||
func (r *gRPCresponse) Hijack() { return }
|
||||
func (r *gRPCresponse) LocalAddr() net.Addr { return r.localAddr }
|
||||
func (r *gRPCresponse) RemoteAddr() net.Addr { return r.remoteAddr }
|
||||
func (r *gRPCresponse) WriteMsg(m *dns.Msg) error { r.Msg = m; return nil }
|
||||
85
core/dnsserver/server-tls.go
Normal file
85
core/dnsserver/server-tls.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package dnsserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
// serverTLS represents an instance of a TLS-over-DNS-server.
|
||||
type serverTLS struct {
|
||||
*Server
|
||||
}
|
||||
|
||||
// NewTLSServer returns a new CoreDNS TLS server and compiles all middleware in to it.
|
||||
func NewServerTLS(addr string, group []*Config) (*serverTLS, error) {
|
||||
|
||||
s, err := NewServer(addr, group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &serverTLS{Server: s}, nil
|
||||
}
|
||||
|
||||
// Serve implements caddy.TCPServer interface.
|
||||
func (s *serverTLS) Serve(l net.Listener) error {
|
||||
s.m.Lock()
|
||||
|
||||
// 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.Background()
|
||||
s.ServeDNS(ctx, w, r)
|
||||
})}
|
||||
s.m.Unlock()
|
||||
|
||||
return s.server[tcp].ActivateAndServe()
|
||||
}
|
||||
|
||||
// ServePacket implements caddy.UDPServer interface.
|
||||
func (s *serverTLS) ServePacket(p net.PacketConn) error { return nil }
|
||||
|
||||
// Listen implements caddy.TCPServer interface.
|
||||
func (s *serverTLS) Listen() (net.Listener, error) {
|
||||
// The *tls* middleware must make sure that multiple conflicting
|
||||
// TLS configuration return an error: it can only be specified once.
|
||||
tlsConfig := new(tls.Config)
|
||||
for _, conf := range s.zones {
|
||||
// Should we error if some configs *don't* have TLS?
|
||||
tlsConfig = conf.TLSConfig
|
||||
}
|
||||
|
||||
var (
|
||||
l net.Listener
|
||||
err error
|
||||
)
|
||||
|
||||
if tlsConfig == nil {
|
||||
l, err = net.Listen("tcp", s.Addr[len(TransportTLS+"://"):])
|
||||
} else {
|
||||
l, err = tls.Listen("tcp", s.Addr[len(TransportTLS+"://"):], tlsConfig)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// ListenPacket implements caddy.UDPServer interface.
|
||||
func (s *serverTLS) 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 *serverTLS) OnStartupComplete() {
|
||||
if Quiet {
|
||||
return
|
||||
}
|
||||
|
||||
for zone, config := range s.zones {
|
||||
fmt.Println(TransportTLS + "://" + zone + ":" + config.Port)
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,6 @@ func NewServer(addr string, group []*Config) (*Server, error) {
|
||||
stack = site.Middleware[i](stack)
|
||||
}
|
||||
site.middlewareChain = stack
|
||||
site.Server = s
|
||||
}
|
||||
|
||||
return s, nil
|
||||
@@ -95,7 +94,7 @@ func (s *Server) ServePacket(p net.PacketConn) error {
|
||||
|
||||
// Listen implements caddy.TCPServer interface.
|
||||
func (s *Server) Listen() (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", s.Addr)
|
||||
l, err := net.Listen("tcp", s.Addr[len(TransportDNS+"://"):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -104,7 +103,7 @@ func (s *Server) Listen() (net.Listener, error) {
|
||||
|
||||
// ListenPacket implements caddy.UDPServer interface.
|
||||
func (s *Server) ListenPacket() (net.PacketConn, error) {
|
||||
p, err := net.ListenPacket("udp", s.Addr)
|
||||
p, err := net.ListenPacket("udp", s.Addr[len(TransportDNS+"://"):])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package dnsserver
|
||||
// care what middleware above them are doing.
|
||||
|
||||
var directives = []string{
|
||||
"tls",
|
||||
"root",
|
||||
"bind",
|
||||
"trace",
|
||||
|
||||
Reference in New Issue
Block a user