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:
Miek Gieben 2017-03-13 20:24:37 +00:00 committed by GitHub
parent 4985d698e2
commit bfaf9e0aec
24 changed files with 570 additions and 50 deletions

View file

@ -5,9 +5,10 @@
[![Code Coverage](https://img.shields.io/codecov/c/github/coredns/coredns/master.svg?style=flat-square)](https://codecov.io/github/coredns/coredns?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/coredns/coredns?style=flat-square)](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
same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type plugin for
Caddy. CoreDNS is also a [Cloud Native Computing Foundation](https://cncf.io) inception level project.
CoreDNS is a DNS server that started as a fork of [Caddy](https://github.com/mholt/caddy/). It has
the same model: it chains middleware. In fact it's so similar that CoreDNS is now a server type
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
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
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:
* 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
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
things fast and to reduce the memory usage.
There are still a few known [issues](https://github.com/coredns/coredns/issues), and work is ongoing
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
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
Website: <https://coredns.io>

View file

@ -11,10 +11,24 @@ import (
type zoneAddr struct {
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 == "" {
if trans == TransportDNS {
port = Port
}
return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port}, err
if trans == TransportTLS {
port = TLSPort
}
if trans == TransportGRPC {
port = GRPCPort
}
}
return zoneAddr{Zone: strings.ToLower(dns.Fqdn(host)), Port: port, Transport: trans}, err
}
// Supported transports.
const (
TransportDNS = "dns"
TransportTLS = "tls"
TransportGRPC = "grpc"
)

View file

@ -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()

View file

@ -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)

View file

@ -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,
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 {
// 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)
}
}
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

View 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 }

View 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)
}
}

View file

@ -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
}

View file

@ -11,6 +11,7 @@ package dnsserver
// care what middleware above them are doing.
var directives = []string{
"tls",
"root",
"bind",
"trace",

View file

@ -24,6 +24,7 @@ import (
_ "github.com/coredns/coredns/middleware/rewrite"
_ "github.com/coredns/coredns/middleware/root"
_ "github.com/coredns/coredns/middleware/secondary"
_ "github.com/coredns/coredns/middleware/tls"
_ "github.com/coredns/coredns/middleware/trace"
_ "github.com/coredns/coredns/middleware/whoami"
)

View file

@ -19,6 +19,7 @@
# Local middleware example:
# 80:log:log
1:tls:tls
10:root:root
20:bind:bind
30:trace:trace

View file

@ -38,7 +38,7 @@ func (e *Erratic) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg
m.Authoritative = true
// 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() {
case dns.TypeA:
rr := *(rrA.(*dns.A))

View file

@ -7,6 +7,8 @@ import (
"github.com/miekg/dns"
)
// See core/dnsserver/address.go - we should unify these two impls.
// Zones respresents a lists of zone names.
type Zones []string
@ -56,12 +58,24 @@ type (
)
// 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 {
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
host, _, err := net.SplitHostPort(string(h))
host, _, err := net.SplitHostPort(s)
if err != nil {
host, _, _ = net.SplitHostPort(string(h) + ":")
host, _, _ = net.SplitHostPort(s + ":")
}
return Name(host).Normalize()
}
@ -77,3 +91,10 @@ func (a Addr) Normalize() string {
// TODO(miek): lowercase it?
return net.JoinHostPort(addr, port)
}
// Duplicated from core/dnsserver/address.go !
const (
TransportDNS = "dns"
TransportTLS = "tls"
TransportGRPC = "grpc"
)

View file

@ -5,16 +5,13 @@ import (
"crypto/tls"
"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/pb"
"github.com/coredns/coredns/request"
"github.com/grpc-ecosystem/grpc-opentracing/go/otgrpc"
"github.com/miekg/dns"
opentracing "github.com/opentracing/opentracing-go"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)

View file

@ -40,8 +40,7 @@ func reverseParse(c *caddy.Controller) (nets networks, fall bool, err error) {
zones := make([]string, len(c.ServerBlockKeys))
for i, str := range c.ServerBlockKeys {
host, _, _ := net.SplitHostPort(str)
zones[i] = strings.ToLower(host)
zones[i] = middleware.Host(str).Normalize()
}
for c.Next() {

View file

@ -5,6 +5,7 @@ import (
"os"
"github.com/coredns/coredns/core/dnsserver"
"github.com/coredns/coredns/middleware"
"github.com/mholt/caddy"
)
@ -21,7 +22,7 @@ func setup(c *caddy.Controller) error {
for c.Next() {
if !c.NextArg() {
return c.ArgErr()
return middleware.Error("root", c.ArgErr())
}
config.Root = c.Val()
}
@ -34,7 +35,7 @@ func setup(c *caddy.Controller) error {
// But make sure the user knows!
log.Printf("[WARNING] Root path does not exist: %s", config.Root)
} 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))
}
}

View file

@ -19,7 +19,7 @@ func TestRoot(t *testing.T) {
// Predefined error substrings
parseErrContent := "Parse error:"
unableToAccessErrContent := "Unable to access root path"
unableToAccessErrContent := "unable to access root path"
existingDirPath, err := getTempDirPath()
if err != nil {

13
middleware/tls/README.md Normal file
View 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
View 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
}

View 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
View 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)
}

View file

@ -46,7 +46,7 @@ func TestLookupProxy(t *testing.T) {
}
// expect answer section with A record in it
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 {
t.Errorf("Expected RR to A, got: %d", resp.Answer[0].Header().Rrtype)