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:
parent
4985d698e2
commit
bfaf9e0aec
24 changed files with 570 additions and 50 deletions
34
README.md
34
README.md
|
@ -5,9 +5,10 @@
|
||||||
[](https://codecov.io/github/coredns/coredns?branch=master)
|
[](https://codecov.io/github/coredns/coredns?branch=master)
|
||||||
[](https://goreportcard.com/report/coredns/coredns)
|
[](https://goreportcard.com/report/coredns/coredns)
|
||||||
|
|
||||||
CoreDNS is a DNS server that started as a fork of [Caddy](https://github.com/mholt/caddy/). It has the
|
CoreDNS is a DNS server that started as a fork of [Caddy](https://github.com/mholt/caddy/). It has
|
||||||
same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type plugin for
|
the same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type
|
||||||
Caddy. CoreDNS is also a [Cloud Native Computing Foundation](https://cncf.io) inception level project.
|
plugin for Caddy. CoreDNS is also a [Cloud Native Computing Foundation](https://cncf.io) inception
|
||||||
|
level project.
|
||||||
|
|
||||||
CoreDNS is the successor to [SkyDNS](https://github.com/skynetservices/skydns). SkyDNS is a thin
|
CoreDNS is the successor to [SkyDNS](https://github.com/skynetservices/skydns). SkyDNS is a thin
|
||||||
layer that exposes services in etcd in the DNS. CoreDNS builds on this idea and is a generic DNS
|
layer that exposes services in etcd in the DNS. CoreDNS builds on this idea and is a generic DNS
|
||||||
|
@ -16,6 +17,11 @@ server that can talk to multiple backends (etcd, kubernetes, etc.).
|
||||||
CoreDNS aims to be a fast and flexible DNS server. The keyword here is *flexible*: with CoreDNS you
|
CoreDNS aims to be a fast and flexible DNS server. The keyword here is *flexible*: with CoreDNS you
|
||||||
are able to do what you want with your DNS data. And if not: write some middleware!
|
are able to do what you want with your DNS data. And if not: write some middleware!
|
||||||
|
|
||||||
|
CoreDNS can listen for DNS request coming in over UDP/TCP (go'old DNS), TLS
|
||||||
|
([RFC 7858](https://tools.ietf.org/html/rfc7858)) and gRPC (not
|
||||||
|
a standard.
|
||||||
|
|
||||||
|
|
||||||
Currently CoreDNS is able to:
|
Currently CoreDNS is able to:
|
||||||
|
|
||||||
* Serve zone data from a file; both DNSSEC (NSEC only) and DNS are supported (*file*).
|
* Serve zone data from a file; both DNSSEC (NSEC only) and DNS are supported (*file*).
|
||||||
|
@ -44,8 +50,8 @@ Each of the middlewares has a README.md of its own.
|
||||||
CoreDNS can be used as an authoritative nameserver for your domains, and should be stable enough to
|
CoreDNS can be used as an authoritative nameserver for your domains, and should be stable enough to
|
||||||
provide you with good DNS(SEC) service.
|
provide you with good DNS(SEC) service.
|
||||||
|
|
||||||
There are still a few known [issues](https://github.com/coredns/coredns/issues), and work is ongoing on making
|
There are still a few known [issues](https://github.com/coredns/coredns/issues), and work is ongoing
|
||||||
things fast and to reduce the memory usage.
|
on making things fast and to reduce the memory usage.
|
||||||
|
|
||||||
All in all, CoreDNS should be able to provide you with enough functionality to replace parts of BIND
|
All in all, CoreDNS should be able to provide you with enough functionality to replace parts of BIND
|
||||||
9, Knot, NSD or PowerDNS and SkyDNS. Most documentation is in the source and some blog articles can
|
9, Knot, NSD or PowerDNS and SkyDNS. Most documentation is in the source and some blog articles can
|
||||||
|
@ -169,6 +175,24 @@ example.org {
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
Listening on TLS and for gRPC? Use:
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
tls://example.org grpc://example.org {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Specifying ports works in the same way:
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
grpc://example.org:1443 {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
~~~
|
||||||
|
|
||||||
|
When no transport protocol is specified the default `dns://` is assumed.
|
||||||
|
|
||||||
## Blog and Contact
|
## Blog and Contact
|
||||||
|
|
||||||
Website: <https://coredns.io>
|
Website: <https://coredns.io>
|
||||||
|
|
|
@ -9,12 +9,26 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type zoneAddr struct {
|
type zoneAddr struct {
|
||||||
Zone string
|
Zone string
|
||||||
Port string
|
Port string
|
||||||
|
Transport string // dns, tls or grpc
|
||||||
}
|
}
|
||||||
|
|
||||||
// String return z.Zone + ":" + z.Port as a string.
|
// String return the string represenation of z.
|
||||||
func (z zoneAddr) String() string { return z.Zone + ":" + z.Port }
|
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
|
// normalizeZone parses an zone string into a structured format with separate
|
||||||
// host, and port portions, as well as the original input string.
|
// 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) {
|
func normalizeZone(str string) (zoneAddr, error) {
|
||||||
var err 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)
|
host, port, err := net.SplitHostPort(str)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
host, port, err = net.SplitHostPort(str + ":")
|
host, port, err = net.SplitHostPort(str + ":")
|
||||||
// no error check here; return err at end of function
|
// 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))
|
return zoneAddr{}, fmt.Errorf("specified zone is too long: %d > 255", len(host))
|
||||||
}
|
}
|
||||||
_, d := dns.IsDomainName(host)
|
_, d := dns.IsDomainName(host)
|
||||||
|
@ -39,8 +67,23 @@ func normalizeZone(str string) (zoneAddr, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if port == "" {
|
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
|
expected string
|
||||||
shouldErr bool
|
shouldErr bool
|
||||||
}{
|
}{
|
||||||
{".", ".:53", false},
|
{".", "dns://.:53", false},
|
||||||
{".:54", ".:54", false},
|
{".:54", "dns://.:54", false},
|
||||||
{"..", ":", true},
|
{"..", "://:", true},
|
||||||
{"..", ":", true},
|
{"..", "://:", true},
|
||||||
} {
|
} {
|
||||||
addr, err := normalizeZone(test.input)
|
addr, err := normalizeZone(test.input)
|
||||||
actual := addr.String()
|
actual := addr.String()
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package dnsserver
|
package dnsserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"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.
|
// First consumer is the file middleware to looks for zone files in this place.
|
||||||
Root string
|
Root string
|
||||||
|
|
||||||
// Server is the server that handles this config
|
// The transport we implement, normally just "dns" over TCP/UDP, but could be
|
||||||
Server *Server
|
// 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 stack.
|
||||||
Middleware []middleware.Middleware
|
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
|
// 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).
|
// comes before the middleware you are checking; it will not be there (yet).
|
||||||
func GetMiddleware(c *caddy.Controller, name string) middleware.Handler {
|
func GetMiddleware(c *caddy.Controller, name string) middleware.Handler {
|
||||||
// TODO(miek): calling the handler h(nil) should be a noop...
|
|
||||||
conf := GetConfig(c)
|
conf := GetConfig(c)
|
||||||
for _, h := range conf.Middleware {
|
for _, h := range conf.Middleware {
|
||||||
x := h(nil)
|
x := h(nil)
|
||||||
|
|
|
@ -61,15 +61,16 @@ func (h *dnsContext) InspectServerBlocks(sourceFile string, serverBlocks []caddy
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s.Keys[i] = za.String()
|
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)
|
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
|
// Save the config to our master list, and key it for lookups
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
Zone: za.Zone,
|
Zone: za.Zone,
|
||||||
Port: za.Port,
|
Port: za.Port,
|
||||||
|
Transport: za.Transport,
|
||||||
}
|
}
|
||||||
h.saveConfig(za.String(), cfg)
|
h.saveConfig(za.String(), cfg)
|
||||||
}
|
}
|
||||||
|
@ -88,11 +89,31 @@ func (h *dnsContext) MakeServers() ([]caddy.Server, error) {
|
||||||
// then we create a server for each group
|
// then we create a server for each group
|
||||||
var servers []caddy.Server
|
var servers []caddy.Server
|
||||||
for addr, group := range groups {
|
for addr, group := range groups {
|
||||||
s, err := NewServer(addr, group)
|
// switch on addr
|
||||||
if err != nil {
|
switch Transport(addr) {
|
||||||
return nil, err
|
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
|
return servers, nil
|
||||||
|
@ -112,14 +133,11 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
||||||
groups := make(map[string][]*Config)
|
groups := make(map[string][]*Config)
|
||||||
|
|
||||||
for _, conf := range configs {
|
for _, conf := range configs {
|
||||||
if conf.Port == "" {
|
|
||||||
conf.Port = Port
|
|
||||||
}
|
|
||||||
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.ListenHost, conf.Port))
|
addr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(conf.ListenHost, conf.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
addrstr := addr.String()
|
addrstr := conf.Transport + "://" + addr.String()
|
||||||
groups[addrstr] = append(groups[addrstr], conf)
|
groups[addrstr] = append(groups[addrstr], conf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +147,10 @@ func groupConfigsByListenAddr(configs []*Config) (map[string][]*Config, error) {
|
||||||
const (
|
const (
|
||||||
// DefaultPort is the default port.
|
// DefaultPort is the default port.
|
||||||
DefaultPort = "53"
|
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
|
// 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)
|
stack = site.Middleware[i](stack)
|
||||||
}
|
}
|
||||||
site.middlewareChain = stack
|
site.middlewareChain = stack
|
||||||
site.Server = s
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return s, nil
|
return s, nil
|
||||||
|
@ -95,7 +94,7 @@ func (s *Server) ServePacket(p net.PacketConn) error {
|
||||||
|
|
||||||
// Listen implements caddy.TCPServer interface.
|
// Listen implements caddy.TCPServer interface.
|
||||||
func (s *Server) Listen() (net.Listener, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,7 +103,7 @@ func (s *Server) Listen() (net.Listener, error) {
|
||||||
|
|
||||||
// ListenPacket implements caddy.UDPServer interface.
|
// ListenPacket implements caddy.UDPServer interface.
|
||||||
func (s *Server) ListenPacket() (net.PacketConn, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ package dnsserver
|
||||||
// care what middleware above them are doing.
|
// care what middleware above them are doing.
|
||||||
|
|
||||||
var directives = []string{
|
var directives = []string{
|
||||||
|
"tls",
|
||||||
"root",
|
"root",
|
||||||
"bind",
|
"bind",
|
||||||
"trace",
|
"trace",
|
||||||
|
|
|
@ -24,6 +24,7 @@ import (
|
||||||
_ "github.com/coredns/coredns/middleware/rewrite"
|
_ "github.com/coredns/coredns/middleware/rewrite"
|
||||||
_ "github.com/coredns/coredns/middleware/root"
|
_ "github.com/coredns/coredns/middleware/root"
|
||||||
_ "github.com/coredns/coredns/middleware/secondary"
|
_ "github.com/coredns/coredns/middleware/secondary"
|
||||||
|
_ "github.com/coredns/coredns/middleware/tls"
|
||||||
_ "github.com/coredns/coredns/middleware/trace"
|
_ "github.com/coredns/coredns/middleware/trace"
|
||||||
_ "github.com/coredns/coredns/middleware/whoami"
|
_ "github.com/coredns/coredns/middleware/whoami"
|
||||||
)
|
)
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
# Local middleware example:
|
# Local middleware example:
|
||||||
# 80:log:log
|
# 80:log:log
|
||||||
|
|
||||||
|
1:tls:tls
|
||||||
10:root:root
|
10:root:root
|
||||||
20:bind:bind
|
20:bind:bind
|
||||||
30:trace:trace
|
30:trace:trace
|
||||||
|
|
|
@ -38,7 +38,7 @@ func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
|
||||||
m.Authoritative = true
|
m.Authoritative = true
|
||||||
|
|
||||||
// small dance to copy rrA or rrAAAA into a non-pointer var that allows us to overwrite the ownername
|
// small dance to copy rrA or rrAAAA into a non-pointer var that allows us to overwrite the ownername
|
||||||
// in a non-racy manor.
|
// in a non-racy way.
|
||||||
switch state.QType() {
|
switch state.QType() {
|
||||||
case dns.TypeA:
|
case dns.TypeA:
|
||||||
rr := *(rrA.(*dns.A))
|
rr := *(rrA.(*dns.A))
|
||||||
|
|
|
@ -7,6 +7,8 @@ import (
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// See core/dnsserver/address.go - we should unify these two impls.
|
||||||
|
|
||||||
// Zones respresents a lists of zone names.
|
// Zones respresents a lists of zone names.
|
||||||
type Zones []string
|
type Zones []string
|
||||||
|
|
||||||
|
@ -56,12 +58,24 @@ type (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Normalize will return the host portion of host, stripping
|
// Normalize will return the host portion of host, stripping
|
||||||
// of any port. The host will also be fully qualified and lowercased.
|
// of any port or transport. The host will also be fully qualified and lowercased.
|
||||||
func (h Host) Normalize() string {
|
func (h Host) Normalize() string {
|
||||||
|
|
||||||
|
s := string(h)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(s, TransportTLS+"://"):
|
||||||
|
s = s[len(TransportTLS+"://"):]
|
||||||
|
case strings.HasPrefix(s, TransportDNS+"://"):
|
||||||
|
s = s[len(TransportDNS+"://"):]
|
||||||
|
case strings.HasPrefix(s, TransportGRPC+"://"):
|
||||||
|
s = s[len(TransportGRPC+"://"):]
|
||||||
|
}
|
||||||
|
|
||||||
// separate host and port
|
// separate host and port
|
||||||
host, _, err := net.SplitHostPort(string(h))
|
host, _, err := net.SplitHostPort(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
host, _, _ = net.SplitHostPort(string(h) + ":")
|
host, _, _ = net.SplitHostPort(s + ":")
|
||||||
}
|
}
|
||||||
return Name(host).Normalize()
|
return Name(host).Normalize()
|
||||||
}
|
}
|
||||||
|
@ -77,3 +91,10 @@ func (a Addr) Normalize() string {
|
||||||
// TODO(miek): lowercase it?
|
// TODO(miek): lowercase it?
|
||||||
return net.JoinHostPort(addr, port)
|
return net.JoinHostPort(addr, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Duplicated from core/dnsserver/address.go !
|
||||||
|
const (
|
||||||
|
TransportDNS = "dns"
|
||||||
|
TransportTLS = "tls"
|
||||||
|
TransportGRPC = "grpc"
|
||||||
|
)
|
||||||
|
|
|
@ -5,16 +5,13 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/middleware/proxy/pb"
|
|
||||||
"github.com/coredns/coredns/middleware/trace"
|
"github.com/coredns/coredns/middleware/trace"
|
||||||
|
"github.com/coredns/coredns/pb"
|
||||||
"github.com/coredns/coredns/request"
|
"github.com/coredns/coredns/request"
|
||||||
|
|
||||||
|
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials"
|
"google.golang.org/grpc/credentials"
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,8 +40,7 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
|
||||||
zones := make([]string, len(c.ServerBlockKeys))
|
zones := make([]string, len(c.ServerBlockKeys))
|
||||||
|
|
||||||
for i, str := range c.ServerBlockKeys {
|
for i, str := range c.ServerBlockKeys {
|
||||||
host, _, _ := net.SplitHostPort(str)
|
zones[i] = middleware.Host(str).Normalize()
|
||||||
zones[i] = strings.ToLower(host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
|
"github.com/coredns/coredns/middleware"
|
||||||
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +22,7 @@ func setup(c *caddy.Controller) error {
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
return c.ArgErr()
|
return middleware.Error("root", c.ArgErr())
|
||||||
}
|
}
|
||||||
config.Root = c.Val()
|
config.Root = c.Val()
|
||||||
}
|
}
|
||||||
|
@ -34,7 +35,7 @@ func setup(c *caddy.Controller) error {
|
||||||
// But make sure the user knows!
|
// But make sure the user knows!
|
||||||
log.Printf("[WARNING] Root path does not exist: %s", config.Root)
|
log.Printf("[WARNING] Root path does not exist: %s", config.Root)
|
||||||
} else {
|
} else {
|
||||||
return c.Errf("Unable to access root path '%s': %v", config.Root, err)
|
return middleware.Error("root", c.Errf("unable to access root path '%s': %v", config.Root, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ func TestRoot(t *testing.T) {
|
||||||
|
|
||||||
// Predefined error substrings
|
// Predefined error substrings
|
||||||
parseErrContent := "Parse error:"
|
parseErrContent := "Parse error:"
|
||||||
unableToAccessErrContent := "Unable to access root path"
|
unableToAccessErrContent := "unable to access root path"
|
||||||
|
|
||||||
existingDirPath, err := getTempDirPath()
|
existingDirPath, err := getTempDirPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
13
middleware/tls/README.md
Normal file
13
middleware/tls/README.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# tls
|
||||||
|
|
||||||
|
*tls* extra TLS configuration.
|
||||||
|
|
||||||
|
## Syntax
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
tls [STUFF]
|
||||||
|
~~~
|
||||||
|
|
||||||
|
**STUFF** is things you'll need to configure TLS.
|
||||||
|
|
||||||
|
## Examples
|
37
middleware/tls/tls.go
Normal file
37
middleware/tls/tls.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
|
"github.com/coredns/coredns/middleware"
|
||||||
|
"github.com/coredns/coredns/middleware/pkg/tls"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
caddy.RegisterPlugin("tls", caddy.Plugin{
|
||||||
|
ServerType: "dns",
|
||||||
|
Action: setup,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(c *caddy.Controller) error {
|
||||||
|
config := dnsserver.GetConfig(c)
|
||||||
|
|
||||||
|
if config.TLSConfig != nil {
|
||||||
|
return middleware.Error("tls", c.Errf("TLS already configured for this server instance"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for c.Next() {
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
if len(args) != 3 {
|
||||||
|
return middleware.Error("tls", c.ArgErr())
|
||||||
|
}
|
||||||
|
tls, err := tls.NewTLSConfig(args[0], args[1], args[2])
|
||||||
|
if err != nil {
|
||||||
|
return middleware.Error("tls", c.ArgErr())
|
||||||
|
}
|
||||||
|
config.TLSConfig = tls
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
44
middleware/tls/tls_test.go
Normal file
44
middleware/tls/tls_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mholt/caddy"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTLS(t *testing.T) {
|
||||||
|
log.SetOutput(ioutil.Discard)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
shouldErr bool
|
||||||
|
expectedRoot string // expected root, set to the controller. Empty for negative cases.
|
||||||
|
expectedErrContent string // substring from the expected error. Empty for positive cases.
|
||||||
|
}{
|
||||||
|
// positive
|
||||||
|
// negative
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
c := caddy.NewTestController("dns", test.input)
|
||||||
|
err := setup(c)
|
||||||
|
//cfg := dnsserver.GetConfig(c)
|
||||||
|
|
||||||
|
if test.shouldErr && err == nil {
|
||||||
|
t.Errorf("Test %d: Expected error but found %s for input %s", i, err, test.input)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if !test.shouldErr {
|
||||||
|
t.Errorf("Test %d: Expected no error but found one for input %s. Error was: %v", i, test.input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), test.expectedErrContent) {
|
||||||
|
t.Errorf("Test %d: Expected error to contain: %v, found error: %v, input: %s", i, test.expectedErrContent, err, test.input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
test/grpc_test.go
Normal file
62
test/grpc_test.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/coredns/coredns/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGrpc(t *testing.T) {
|
||||||
|
log.SetOutput(ioutil.Discard)
|
||||||
|
|
||||||
|
corefile := `grpc://.:0 {
|
||||||
|
whoami
|
||||||
|
}
|
||||||
|
`
|
||||||
|
g, err := CoreDNSServer(corefile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not get CoreDNS serving instance: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, tcp := CoreDNSServerPorts(g, 0)
|
||||||
|
defer g.Stop()
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(tcp, grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(5*time.Second))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Expected no error but got: %s", err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
client := pb.NewDnsServiceClient(conn)
|
||||||
|
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetQuestion("whoami.example.org.", dns.TypeA)
|
||||||
|
msg, _ := m.Pack()
|
||||||
|
|
||||||
|
reply, err := client.Query(context.TODO(), &pb.DnsPacket{Msg: msg})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d := new(dns.Msg)
|
||||||
|
err = d.Unpack(reply.Msg)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected no error but got: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.Rcode != dns.RcodeSuccess {
|
||||||
|
t.Errorf("Expected success but got %s", d.Rcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(d.Extra) != 2 {
|
||||||
|
t.Errorf("Expected 2 RRs in additional section, but got %s", len(d.Extra))
|
||||||
|
}
|
||||||
|
t.Logf("Message %v\n", d)
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ func TestLookupProxy(t *testing.T) {
|
||||||
}
|
}
|
||||||
// expect answer section with A record in it
|
// expect answer section with A record in it
|
||||||
if len(resp.Answer) == 0 {
|
if len(resp.Answer) == 0 {
|
||||||
t.Error("Expected to at least one RR in the answer section, got none")
|
t.Fatalf("Expected to at least one RR in the answer section, got none: %s", resp)
|
||||||
}
|
}
|
||||||
if resp.Answer[0].Header().Rrtype != dns.TypeA {
|
if resp.Answer[0].Header().Rrtype != dns.TypeA {
|
||||||
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
|
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue