mirror of
https://github.com/coredns/coredns.git
synced 2025-10-28 00:34:24 -04:00
more etcd stuff
This commit is contained in:
@@ -8,24 +8,24 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
etcdc "github.com/coreos/etcd/client"
|
|
||||||
"github.com/miekg/coredns/middleware"
|
"github.com/miekg/coredns/middleware"
|
||||||
"github.com/miekg/coredns/middleware/file"
|
"github.com/miekg/coredns/middleware/etcd"
|
||||||
|
|
||||||
|
etcdc "github.com/coreos/etcd/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultAddress = "http://127.0.0.1:2379"
|
const defaultEndpoint = "http://127.0.0.1:2379"
|
||||||
|
|
||||||
// Etcd sets up the etcd middleware.
|
// Etcd sets up the etcd middleware.
|
||||||
func Etcd(c *Controller) (middleware.Middleware, error) {
|
func Etcd(c *Controller) (middleware.Middleware, error) {
|
||||||
keysapi, err := etcdParse(c)
|
client, err := etcdParse(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return func(next middleware.Handler) middleware.Handler {
|
return func(next middleware.Handler) middleware.Handler {
|
||||||
return file.File{Next: next, Zones: zones}
|
return etcd.NewEtcd(client, next, c.ServerBlockHosts)
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func etcdParse(c *Controller) (etcdc.KeysAPI, error) {
|
func etcdParse(c *Controller) (etcdc.KeysAPI, error) {
|
||||||
@@ -33,43 +33,30 @@ func etcdParse(c *Controller) (etcdc.KeysAPI, error) {
|
|||||||
if c.Val() == "etcd" {
|
if c.Val() == "etcd" {
|
||||||
// etcd [address...]
|
// etcd [address...]
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
|
// TODO(certs) and friends, this is client side
|
||||||
return file.Zones{}, c.ArgErr()
|
client, err := newEtcdClient([]string{defaultEndpoint}, "", "", "")
|
||||||
|
return client, err
|
||||||
}
|
}
|
||||||
args1 := c.RemainingArgs()
|
client, err := newEtcdClient(c.RemainingArgs(), "", "", "")
|
||||||
fileName := c.Val()
|
return client, err
|
||||||
|
|
||||||
origin := c.ServerBlockHosts[c.ServerBlockHostIndex]
|
|
||||||
if c.NextArg() {
|
|
||||||
c.Next()
|
|
||||||
origin = c.Val()
|
|
||||||
}
|
|
||||||
// normalize this origin
|
|
||||||
origin = middleware.Host(origin).StandardHost()
|
|
||||||
|
|
||||||
zone, err := parseZone(origin, fileName)
|
|
||||||
if err == nil {
|
|
||||||
z[origin] = zone
|
|
||||||
}
|
|
||||||
names = append(names, origin)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return file.Zones{Z: z, Names: names}, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newEtcdClient(machines []string, tlsCert, tlsKey, tlsCACert string) (etcd.KeysAPI, error) {
|
func newEtcdClient(endpoints []string, tlsCert, tlsKey, tlsCACert string) (etcdc.KeysAPI, error) {
|
||||||
etcdCfg := etcd.Config{
|
etcdCfg := etcdc.Config{
|
||||||
Endpoints: machines,
|
Endpoints: endpoints,
|
||||||
Transport: newHTTPSTransport(tlsCert, tlsKey, tlsCACert),
|
Transport: newHTTPSTransport(tlsCert, tlsKey, tlsCACert),
|
||||||
}
|
}
|
||||||
cli, err := etcd.New(etcdCfg)
|
cli, err := etcdc.New(etcdCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return etcd.NewKeysAPI(cli), nil
|
return etcdc.NewKeysAPI(cli), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPSTransport(tlsCertFile, tlsKeyFile, tlsCACertFile string) etcd.CancelableTransport {
|
func newHTTPSTransport(tlsCertFile, tlsKeyFile, tlsCACertFile string) etcdc.CancelableTransport {
|
||||||
var cc *tls.Config = nil
|
var cc *tls.Config = nil
|
||||||
|
|
||||||
if tlsCertFile != "" && tlsKeyFile != "" {
|
if tlsCertFile != "" && tlsKeyFile != "" {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/miekg/coredns/middleware"
|
"github.com/miekg/coredns/middleware"
|
||||||
"github.com/miekg/coredns/middleware/file"
|
"github.com/miekg/coredns/middleware/file"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,164 +0,0 @@
|
|||||||
// Package etcd provides the etcd server Backend implementation,
|
|
||||||
package etcd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/miekg/coredns/middleware/etcd/msg"
|
|
||||||
|
|
||||||
etcdc "github.com/coreos/etcd/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
priority = 10 // default priority when nothing is set
|
|
||||||
ttl = 3600 // default ttl when nothing is set
|
|
||||||
)
|
|
||||||
|
|
||||||
func (g *Backend) Records(name string, exact bool) ([]msg.Service, error) {
|
|
||||||
path, star := msg.PathWithWildcard(name)
|
|
||||||
r, err := g.get(path, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
segments := strings.Split(msg.Path(name), "/")
|
|
||||||
switch {
|
|
||||||
case exact && r.Node.Dir:
|
|
||||||
return nil, nil
|
|
||||||
case r.Node.Dir:
|
|
||||||
return g.loopNodes(r.Node.Nodes, segments, star, nil)
|
|
||||||
default:
|
|
||||||
return g.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Backend) ReverseRecord(name string) (*msg.Service, error) {
|
|
||||||
path, star := msg.PathWithWildcard(name)
|
|
||||||
if star {
|
|
||||||
return nil, fmt.Errorf("reverse can not contain wildcards")
|
|
||||||
}
|
|
||||||
r, err := g.get(path, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if r.Node.Dir {
|
|
||||||
return nil, fmt.Errorf("reverse must not be a directory")
|
|
||||||
}
|
|
||||||
segments := strings.Split(msg.Path(name), "/")
|
|
||||||
records, err := g.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(records) != 1 {
|
|
||||||
return nil, fmt.Errorf("must be only one service record")
|
|
||||||
}
|
|
||||||
return &records[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// get is a wrapper for client.Get that uses SingleInflight to suppress multiple
|
|
||||||
// outstanding queries.
|
|
||||||
func (g *Backend) get(path string, recursive bool) (*etcdc.Response, error) {
|
|
||||||
resp, err := g.inflight.Do(path, func() (interface{}, error) {
|
|
||||||
r, e := g.client.Get(g.ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
|
|
||||||
if e != nil {
|
|
||||||
return nil, e
|
|
||||||
}
|
|
||||||
return r, e
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.(*etcdc.Response), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type bareService struct {
|
|
||||||
Host string
|
|
||||||
Port int
|
|
||||||
Priority int
|
|
||||||
Weight int
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
// skydns/local/skydns/east/staging/web
|
|
||||||
// skydns/local/skydns/west/production/web
|
|
||||||
//
|
|
||||||
// skydns/local/skydns/*/*/web
|
|
||||||
// skydns/local/skydns/*/web
|
|
||||||
|
|
||||||
// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname
|
|
||||||
// will be match against any wildcards when star is true.
|
|
||||||
func (g *Backend) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[bareService]bool) (sx []msg.Service, err error) {
|
|
||||||
if bx == nil {
|
|
||||||
bx = make(map[bareService]bool)
|
|
||||||
}
|
|
||||||
Nodes:
|
|
||||||
for _, n := range ns {
|
|
||||||
if n.Dir {
|
|
||||||
nodes, err := g.loopNodes(n.Nodes, nameParts, star, bx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sx = append(sx, nodes...)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if star {
|
|
||||||
keyParts := strings.Split(n.Key, "/")
|
|
||||||
for i, n := range nameParts {
|
|
||||||
if i > len(keyParts)-1 {
|
|
||||||
// name is longer than key
|
|
||||||
continue Nodes
|
|
||||||
}
|
|
||||||
if n == "*" || n == "any" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if keyParts[i] != n {
|
|
||||||
continue Nodes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
serv := new(msg.Service)
|
|
||||||
if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b := bareService{serv.Host, serv.Port, serv.Priority, serv.Weight, serv.Text}
|
|
||||||
if _, ok := bx[b]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
bx[b] = true
|
|
||||||
|
|
||||||
serv.Key = n.Key
|
|
||||||
serv.Ttl = g.calculateTtl(n, serv)
|
|
||||||
if serv.Priority == 0 {
|
|
||||||
serv.Priority = priority
|
|
||||||
}
|
|
||||||
sx = append(sx, *serv)
|
|
||||||
}
|
|
||||||
return sx, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// calculateTtl returns the smaller of the etcd TTL and the service's
|
|
||||||
// TTL. If neither of these are set (have a zero value), the server
|
|
||||||
// default is used.
|
|
||||||
func (g *Backend) calculateTtl(node *etcdc.Node, serv *msg.Service) uint32 {
|
|
||||||
etcdTtl := uint32(node.TTL)
|
|
||||||
|
|
||||||
if etcdTtl == 0 && serv.Ttl == 0 {
|
|
||||||
return ttl
|
|
||||||
}
|
|
||||||
if etcdTtl == 0 {
|
|
||||||
return serv.Ttl
|
|
||||||
}
|
|
||||||
if serv.Ttl == 0 {
|
|
||||||
return etcdTtl
|
|
||||||
}
|
|
||||||
if etcdTtl < serv.Ttl {
|
|
||||||
return etcdTtl
|
|
||||||
}
|
|
||||||
return serv.Ttl
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client exposes the underlying Etcd client (used in tests).
|
|
||||||
func (g *Backend) Client() etcdc.KeysAPI {
|
|
||||||
return g.client
|
|
||||||
}
|
|
||||||
@@ -2,9 +2,12 @@
|
|||||||
package etcd
|
package etcd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/miekg/coredns/middleware"
|
"github.com/miekg/coredns/middleware"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/coredns/middleware/etcd/msg"
|
||||||
"github.com/skynetservices/skydns/singleflight"
|
"github.com/miekg/coredns/middleware/etcd/singleflight"
|
||||||
|
|
||||||
etcdc "github.com/coreos/etcd/client"
|
etcdc "github.com/coreos/etcd/client"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
@@ -12,23 +15,136 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
Etcd struct {
|
Etcd struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
|
Zones []string
|
||||||
client etcd.KeysAPI
|
client etcdc.KeysAPI
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
inflight *singleflight.Group
|
inflight *singleflight.Group
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewEtcd(client etcdc.KeysAPI, next middleware.Handler) Etcd {
|
func NewEtcd(client etcdc.KeysAPI, next middleware.Handler, zones []string) Etcd {
|
||||||
return Etcd{
|
return Etcd{
|
||||||
Next: next,
|
Next: next,
|
||||||
|
Zones: zones,
|
||||||
client: client,
|
client: client,
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
inflight: &singleflight.Group{},
|
inflight: &singleflight.Group{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Etcd) ServerDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
func (g Etcd) Records(name string, exact bool) ([]msg.Service, error) {
|
||||||
return 0, nil
|
path, star := msg.PathWithWildcard(name)
|
||||||
|
r, err := g.Get(path, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
segments := strings.Split(msg.Path(name), "/")
|
||||||
|
switch {
|
||||||
|
case exact && r.Node.Dir:
|
||||||
|
return nil, nil
|
||||||
|
case r.Node.Dir:
|
||||||
|
return g.loopNodes(r.Node.Nodes, segments, star, nil)
|
||||||
|
default:
|
||||||
|
return g.loopNodes([]*etcdc.Node{r.Node}, segments, false, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get is a wrapper for client.Get that uses SingleInflight to suppress multiple outstanding queries.
|
||||||
|
func (g Etcd) Get(path string, recursive bool) (*etcdc.Response, error) {
|
||||||
|
resp, err := g.inflight.Do(path, func() (interface{}, error) {
|
||||||
|
r, e := g.client.Get(g.ctx, path, &etcdc.GetOptions{Sort: false, Recursive: recursive})
|
||||||
|
if e != nil {
|
||||||
|
return nil, e
|
||||||
|
}
|
||||||
|
return r, e
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp.(*etcdc.Response), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// skydns/local/skydns/east/staging/web
|
||||||
|
// skydns/local/skydns/west/production/web
|
||||||
|
//
|
||||||
|
// skydns/local/skydns/*/*/web
|
||||||
|
// skydns/local/skydns/*/web
|
||||||
|
|
||||||
|
// loopNodes recursively loops through the nodes and returns all the values. The nodes' keyname
|
||||||
|
// will be match against any wildcards when star is true.
|
||||||
|
func (g Etcd) loopNodes(ns []*etcdc.Node, nameParts []string, star bool, bx map[msg.Service]bool) (sx []msg.Service, err error) {
|
||||||
|
if bx == nil {
|
||||||
|
bx = make(map[msg.Service]bool)
|
||||||
|
}
|
||||||
|
Nodes:
|
||||||
|
for _, n := range ns {
|
||||||
|
if n.Dir {
|
||||||
|
nodes, err := g.loopNodes(n.Nodes, nameParts, star, bx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sx = append(sx, nodes...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if star {
|
||||||
|
keyParts := strings.Split(n.Key, "/")
|
||||||
|
for i, n := range nameParts {
|
||||||
|
if i > len(keyParts)-1 {
|
||||||
|
// name is longer than key
|
||||||
|
continue Nodes
|
||||||
|
}
|
||||||
|
if n == "*" || n == "any" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if keyParts[i] != n {
|
||||||
|
continue Nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serv := new(msg.Service)
|
||||||
|
if err := json.Unmarshal([]byte(n.Value), serv); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b := msg.Service{Host: serv.Host, Port: serv.Port, Priority: serv.Priority, Weight: serv.Weight, Text: serv.Text}
|
||||||
|
if _, ok := bx[b]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bx[b] = true
|
||||||
|
|
||||||
|
serv.Key = n.Key
|
||||||
|
serv.Ttl = g.Ttl(n, serv)
|
||||||
|
if serv.Priority == 0 {
|
||||||
|
serv.Priority = priority
|
||||||
|
}
|
||||||
|
sx = append(sx, *serv)
|
||||||
|
}
|
||||||
|
return sx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ttl returns the smaller of the etcd TTL and the service's
|
||||||
|
// TTL. If neither of these are set (have a zero value), a default is used.
|
||||||
|
func (g Etcd) Ttl(node *etcdc.Node, serv *msg.Service) uint32 {
|
||||||
|
etcdTtl := uint32(node.TTL)
|
||||||
|
|
||||||
|
if etcdTtl == 0 && serv.Ttl == 0 {
|
||||||
|
return ttl
|
||||||
|
}
|
||||||
|
if etcdTtl == 0 {
|
||||||
|
return serv.Ttl
|
||||||
|
}
|
||||||
|
if serv.Ttl == 0 {
|
||||||
|
return etcdTtl
|
||||||
|
}
|
||||||
|
if etcdTtl < serv.Ttl {
|
||||||
|
return etcdTtl
|
||||||
|
}
|
||||||
|
return serv.Ttl
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
priority = 10 // default priority when nothing is set
|
||||||
|
ttl = 300 // default ttl when nothing is set
|
||||||
|
minTtl = 60
|
||||||
|
hostmaster = "hostmaster"
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
`etcd` enabled reading zone data from an etcd instance. The data in etcd has to be encoded as
|
`etcd` enabled reading zone data from an etcd instance. The data in etcd has to be encoded as
|
||||||
a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26)
|
a [message](https://github.com/skynetservices/skydns/blob/2fcff74cdc9f9a7dd64189a447ef27ac354b725f/msg/service.go#L26)
|
||||||
like SkyDNS.
|
like [SkyDNS](https//github.com/skynetservices/skydns).
|
||||||
|
|
||||||
|
If you need replies to SOA and NS queries you should add a little zone after etcd directive that has
|
||||||
|
these resource records.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
@@ -14,16 +17,16 @@ etcd [endpoint...]
|
|||||||
|
|
||||||
The will default to `/skydns` as the path and the local etcd proxy (http://127.0.0.1:2379).
|
The will default to `/skydns` as the path and the local etcd proxy (http://127.0.0.1:2379).
|
||||||
|
|
||||||
|
If you want to `round robin` A and AAAA responses look at the `round_robin` middleware.
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
etcd {
|
etcd {
|
||||||
round_robin
|
|
||||||
path /skydns
|
path /skydns
|
||||||
endpoint address...
|
endpoint address...
|
||||||
stubzones
|
stubzones
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* `round_robin`
|
|
||||||
* `path` /skydns
|
* `path` /skydns
|
||||||
* `endpoint` address...
|
* `endpoint` address...
|
||||||
* `stubzones`
|
* `stubzones`
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package etcd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/miekg/coredns/middleware"
|
||||||
|
"github.com/miekg/dns"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (e Etcd) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
|
println("ETCD MIDDLEWARE HIT")
|
||||||
|
|
||||||
|
state := middleware.State{W: w, Req: r}
|
||||||
|
|
||||||
|
m := state.AnswerMessage()
|
||||||
|
m.Authoritative = true
|
||||||
|
m.RecursionAvailable = true
|
||||||
|
m.Compress = true
|
||||||
|
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// only needs state and current zone name we are auth for.
|
||||||
|
/*
|
||||||
|
func (s *server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
|
|
||||||
|
q := req.Question[0]
|
||||||
|
name := strings.ToLower(q.Name)
|
||||||
|
|
||||||
|
switch q.Qtype {
|
||||||
|
case dns.TypeNS:
|
||||||
|
records, extra, err := s.NSRecords(q, s.config.dnsDomain)
|
||||||
|
if isEtcdNameError(err, s) {
|
||||||
|
m = s.NameError(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, records...)
|
||||||
|
m.Extra = append(m.Extra, extra...)
|
||||||
|
case dns.TypeA, dns.TypeAAAA:
|
||||||
|
records, err := s.AddressRecords(q, name, nil, bufsize, dnssec, false)
|
||||||
|
if isEtcdNameError(err, s) {
|
||||||
|
m = s.NameError(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, records...)
|
||||||
|
case dns.TypeTXT:
|
||||||
|
records, err := s.TXTRecords(q, name)
|
||||||
|
if isEtcdNameError(err, s) {
|
||||||
|
m = s.NameError(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, records...)
|
||||||
|
case dns.TypeCNAME:
|
||||||
|
records, err := s.CNAMERecords(q, name)
|
||||||
|
if isEtcdNameError(err, s) {
|
||||||
|
m = s.NameError(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, records...)
|
||||||
|
case dns.TypeMX:
|
||||||
|
records, extra, err := s.MXRecords(q, name, bufsize, dnssec)
|
||||||
|
if isEtcdNameError(err, s) {
|
||||||
|
m = s.NameError(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.Answer = append(m.Answer, records...)
|
||||||
|
m.Extra = append(m.Extra, extra...)
|
||||||
|
default:
|
||||||
|
fallthrough // also catch other types, so that they return NODATA
|
||||||
|
case dns.TypeSRV:
|
||||||
|
records, extra, err := s.SRVRecords(q, name, bufsize, dnssec)
|
||||||
|
if err != nil {
|
||||||
|
if isEtcdNameError(err, s) {
|
||||||
|
m = s.NameError(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logf("got error from backend: %s", err)
|
||||||
|
if q.Qtype == dns.TypeSRV { // Otherwise NODATA
|
||||||
|
m = s.ServerFailure(req)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we are here again, check the types, because an answer may only
|
||||||
|
// be given for SRV. All other types should return NODATA, the
|
||||||
|
// NXDOMAIN part is handled in the above code. TODO(miek): yes this
|
||||||
|
// can be done in a more elegant manor.
|
||||||
|
if q.Qtype == dns.TypeSRV {
|
||||||
|
m.Answer = append(m.Answer, records...)
|
||||||
|
m.Extra = append(m.Extra, extra...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m.Answer) == 0 { // NODATA response
|
||||||
|
m.Ns = []dns.RR{s.NewSOA()}
|
||||||
|
m.Ns[0].Header().Ttl = s.config.MinTtl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// etcNameError checks if the error is ErrorCodeKeyNotFound from etcd.
|
||||||
|
func isEtcdNameError(err error, s *server) bool {
|
||||||
|
if e, ok := err.(etcd.Error); ok && e.Code == etcd.ErrorCodeKeyNotFound {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|||||||
327
middleware/etcd/lookup.go
Normal file
327
middleware/etcd/lookup.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
package etcd
|
||||||
|
|
||||||
|
/*
|
||||||
|
func (s *server) AddressRecords(q dns.Question, name string, previousRecords []dns.RR, state middleware.State) (records []dns.RR, err error) {
|
||||||
|
services, err := s.backend.Records(name, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
services = msg.Group(services)
|
||||||
|
|
||||||
|
for _, serv := range services {
|
||||||
|
ip := net.ParseIP(serv.Host)
|
||||||
|
switch {
|
||||||
|
case ip == nil:
|
||||||
|
// Try to resolve as CNAME if it's not an IP, but only if we don't create loops.
|
||||||
|
if q.Name == dns.Fqdn(serv.Host) {
|
||||||
|
// x CNAME x is a direct loop, don't add those
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newRecord := serv.NewCNAME(q.Name, dns.Fqdn(serv.Host))
|
||||||
|
if len(previousRecords) > 7 {
|
||||||
|
logf("CNAME lookup limit of 8 exceeded for %s", newRecord)
|
||||||
|
// don't add it, and just continue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if s.isDuplicateCNAME(newRecord, previousRecords) {
|
||||||
|
logf("CNAME loop detected for record %s", newRecord)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
nextRecords, err := s.AddressRecords(dns.Question{Name: dns.Fqdn(serv.Host), Qtype: q.Qtype, Qclass: q.Qclass},
|
||||||
|
strings.ToLower(dns.Fqdn(serv.Host)), append(previousRecords, newRecord), state)
|
||||||
|
if err == nil {
|
||||||
|
// Only have we found something we should add the CNAME and the IP addresses.
|
||||||
|
if len(nextRecords) > 0 {
|
||||||
|
records = append(records, newRecord)
|
||||||
|
records = append(records, nextRecords...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// This means we can not complete the CNAME, try to look else where.
|
||||||
|
target := newRecord.Target
|
||||||
|
if dns.IsSubDomain(s.config.Domain, target) {
|
||||||
|
// We should already have found it
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
m1, e1 := s.Lookup(target, q.Qtype, bufsize, dnssec)
|
||||||
|
if e1 != nil {
|
||||||
|
logf("incomplete CNAME chain: %s", e1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Len(m1.Answer) > 0 here is well?
|
||||||
|
records = append(records, newRecord)
|
||||||
|
records = append(records, m1.Answer...)
|
||||||
|
continue
|
||||||
|
case ip.To4() != nil && (q.Qtype == dns.TypeA || both):
|
||||||
|
records = append(records, serv.NewA(q.Name, ip.To4()))
|
||||||
|
case ip.To4() == nil && (q.Qtype == dns.TypeAAAA || both):
|
||||||
|
records = append(records, serv.NewAAAA(q.Name, ip.To16()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NSRecords returns NS records from etcd.
|
||||||
|
func (s *server) NSRecords(q dns.Question, state middleware.State) (records []dns.RR, extra []dns.RR, err error) {
|
||||||
|
services, err := s.backend.Records(name, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
services = msg.Group(services)
|
||||||
|
|
||||||
|
for _, serv := range services {
|
||||||
|
ip := net.ParseIP(serv.Host)
|
||||||
|
switch {
|
||||||
|
case ip == nil:
|
||||||
|
return nil, nil, fmt.Errorf("NS record must be an IP address")
|
||||||
|
case ip.To4() != nil:
|
||||||
|
serv.Host = msg.Domain(serv.Key)
|
||||||
|
records = append(records, serv.NewNS(q.Name, serv.Host))
|
||||||
|
extra = append(extra, serv.NewA(serv.Host, ip.To4()))
|
||||||
|
case ip.To4() == nil:
|
||||||
|
serv.Host = msg.Domain(serv.Key)
|
||||||
|
records = append(records, serv.NewNS(q.Name, serv.Host))
|
||||||
|
extra = append(extra, serv.NewAAAA(serv.Host, ip.To16()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records, extra, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SRVRecords returns SRV records from etcd.
|
||||||
|
// If the Target is not a name but an IP address, a name is created.
|
||||||
|
func (s *server) SRVRecords(s middleware.State) (records []dns.RR, extra []dns.RR, err error) {
|
||||||
|
services, err := s.backend.Records(name, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
services = msg.Group(services)
|
||||||
|
|
||||||
|
// Looping twice to get the right weight vs priority
|
||||||
|
w := make(map[int]int)
|
||||||
|
for _, serv := range services {
|
||||||
|
weight := 100
|
||||||
|
if serv.Weight != 0 {
|
||||||
|
weight = serv.Weight
|
||||||
|
}
|
||||||
|
if _, ok := w[serv.Priority]; !ok {
|
||||||
|
w[serv.Priority] = weight
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
w[serv.Priority] += weight
|
||||||
|
}
|
||||||
|
lookup := make(map[string]bool)
|
||||||
|
for _, serv := range services {
|
||||||
|
w1 := 100.0 / float64(w[serv.Priority])
|
||||||
|
if serv.Weight == 0 {
|
||||||
|
w1 *= 100
|
||||||
|
} else {
|
||||||
|
w1 *= float64(serv.Weight)
|
||||||
|
}
|
||||||
|
weight := uint16(math.Floor(w1))
|
||||||
|
ip := net.ParseIP(serv.Host)
|
||||||
|
switch {
|
||||||
|
case ip == nil:
|
||||||
|
srv := serv.NewSRV(q.Name, weight)
|
||||||
|
records = append(records, srv)
|
||||||
|
|
||||||
|
if _, ok := lookup[srv.Target]; ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup[srv.Target] = true
|
||||||
|
|
||||||
|
if !dns.IsSubDomain(s.config.Domain, srv.Target) {
|
||||||
|
m1, e1 := s.Lookup(srv.Target, dns.TypeA, bufsize, dnssec)
|
||||||
|
if e1 == nil {
|
||||||
|
extra = append(extra, m1.Answer...)
|
||||||
|
}
|
||||||
|
m1, e1 = s.Lookup(srv.Target, dns.TypeAAAA, bufsize, dnssec)
|
||||||
|
if e1 == nil {
|
||||||
|
// If we have seen CNAME's we *assume* that they are already added.
|
||||||
|
for _, a := range m1.Answer {
|
||||||
|
if _, ok := a.(*dns.CNAME); !ok {
|
||||||
|
extra = append(extra, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Internal name, we should have some info on them, either v4 or v6
|
||||||
|
// Clients expect a complete answer, because we are a recursor in their
|
||||||
|
// view.
|
||||||
|
addr, e1 := s.AddressRecords(dns.Question{srv.Target, dns.ClassINET, dns.TypeA},
|
||||||
|
srv.Target, nil, bufsize, dnssec, true)
|
||||||
|
if e1 == nil {
|
||||||
|
extra = append(extra, addr...)
|
||||||
|
}
|
||||||
|
case ip.To4() != nil:
|
||||||
|
serv.Host = msg.Domain(serv.Key)
|
||||||
|
srv := serv.NewSRV(q.Name, weight)
|
||||||
|
|
||||||
|
records = append(records, srv)
|
||||||
|
extra = append(extra, serv.NewA(srv.Target, ip.To4()))
|
||||||
|
case ip.To4() == nil:
|
||||||
|
serv.Host = msg.Domain(serv.Key)
|
||||||
|
srv := serv.NewSRV(q.Name, weight)
|
||||||
|
|
||||||
|
records = append(records, srv)
|
||||||
|
extra = append(extra, serv.NewAAAA(srv.Target, ip.To16()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records, extra, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MXRecords returns MX records from etcd.
|
||||||
|
// If the Target is not a name but an IP address, a name is created.
|
||||||
|
func (s *server) MXRecords(q dns.Question, name string, s middleware.State) (records []dns.RR, extra []dns.RR, err error) {
|
||||||
|
services, err := s.backend.Records(name, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup := make(map[string]bool)
|
||||||
|
for _, serv := range services {
|
||||||
|
if !serv.Mail {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip := net.ParseIP(serv.Host)
|
||||||
|
switch {
|
||||||
|
case ip == nil:
|
||||||
|
mx := serv.NewMX(q.Name)
|
||||||
|
records = append(records, mx)
|
||||||
|
if _, ok := lookup[mx.Mx]; ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lookup[mx.Mx] = true
|
||||||
|
|
||||||
|
if !dns.IsSubDomain(s.config.Domain, mx.Mx) {
|
||||||
|
m1, e1 := s.Lookup(mx.Mx, dns.TypeA, bufsize, dnssec)
|
||||||
|
if e1 == nil {
|
||||||
|
extra = append(extra, m1.Answer...)
|
||||||
|
}
|
||||||
|
m1, e1 = s.Lookup(mx.Mx, dns.TypeAAAA, bufsize, dnssec)
|
||||||
|
if e1 == nil {
|
||||||
|
// If we have seen CNAME's we *assume* that they are already added.
|
||||||
|
for _, a := range m1.Answer {
|
||||||
|
if _, ok := a.(*dns.CNAME); !ok {
|
||||||
|
extra = append(extra, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Internal name
|
||||||
|
addr, e1 := s.AddressRecords(dns.Question{mx.Mx, dns.ClassINET, dns.TypeA},
|
||||||
|
mx.Mx, nil, bufsize, dnssec, true)
|
||||||
|
if e1 == nil {
|
||||||
|
extra = append(extra, addr...)
|
||||||
|
}
|
||||||
|
case ip.To4() != nil:
|
||||||
|
serv.Host = msg.Domain(serv.Key)
|
||||||
|
records = append(records, serv.NewMX(q.Name))
|
||||||
|
extra = append(extra, serv.NewA(serv.Host, ip.To4()))
|
||||||
|
case ip.To4() == nil:
|
||||||
|
serv.Host = msg.Domain(serv.Key)
|
||||||
|
records = append(records, serv.NewMX(q.Name))
|
||||||
|
extra = append(extra, serv.NewAAAA(serv.Host, ip.To16()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records, extra, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) CNAMERecords(q dns.Question, state middleware.State) (records []dns.RR, err error) {
|
||||||
|
services, err := s.backend.Records(name, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
services = msg.Group(services)
|
||||||
|
|
||||||
|
if len(services) > 0 {
|
||||||
|
serv := services[0]
|
||||||
|
if ip := net.ParseIP(serv.Host); ip == nil {
|
||||||
|
records = append(records, serv.NewCNAME(q.Name, dns.Fqdn(serv.Host)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) TXTRecords(q dns.Question, state middleware.State) (records []dns.RR, err error) {
|
||||||
|
services, err := s.backend.Records(name, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
services = msg.Group(services)
|
||||||
|
|
||||||
|
for _, serv := range services {
|
||||||
|
if serv.Text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
records = append(records, serv.NewTXT(q.Name))
|
||||||
|
}
|
||||||
|
return records, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDuplicateCNAME(r *dns.CNAME, records []dns.RR) bool {
|
||||||
|
for _, rec := range records {
|
||||||
|
if v, ok := rec.(*dns.CNAME); ok {
|
||||||
|
if v.Target == r.Target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to state.go somehow?
|
||||||
|
func (s *server) NameError(req *dns.Msg) *dns.Msg {
|
||||||
|
m := new(dns.Msg)
|
||||||
|
m.SetRcode(req, dns.RcodeNameError)
|
||||||
|
m.Ns = []dns.RR{s.NewSOA()}
|
||||||
|
m.Ns[0].Header().Ttl = s.config.MinTtl
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// overflowOrTruncated writes back an error to the client if the message does not fit.
|
||||||
|
// It updates prometheus metrics. If something has been written to the client, true
|
||||||
|
// will be returned.
|
||||||
|
func (s *server) overflowOrTruncated(w dns.ResponseWriter, m *dns.Msg, bufsize int, sy metrics.System) bool {
|
||||||
|
switch isTCP(w) {
|
||||||
|
case true:
|
||||||
|
if _, overflow := Fit(m, dns.MaxMsgSize, true); overflow {
|
||||||
|
metrics.ReportErrorCount(m, sy)
|
||||||
|
msgFail := s.ServerFailure(m)
|
||||||
|
w.WriteMsg(msgFail)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case false:
|
||||||
|
// Overflow with udp always results in TC.
|
||||||
|
Fit(m, bufsize, false)
|
||||||
|
metrics.ReportErrorCount(m, sy)
|
||||||
|
if m.Truncated {
|
||||||
|
w.WriteMsg(m)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// etcNameError return a NameError to the client if the error
|
||||||
|
// returned from etcd has ErrorCode == 100.
|
||||||
|
func isEtcdNameError(err error, s *server) bool {
|
||||||
|
if e, ok := err.(etcd.Error); ok && e.Code == etcd.ErrorCodeKeyNotFound {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
logf("error from backend: %s", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
*/
|
||||||
@@ -8,3 +8,7 @@ func Exchange(c *dns.Client, m *dns.Msg, server string) (*dns.Msg, error) {
|
|||||||
r, _, err := c.Exchange(m, server)
|
r, _, err := c.Exchange(m, server)
|
||||||
return r, err
|
return r, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lookup functions, ala
|
||||||
|
// LookupHost
|
||||||
|
// LookupCNAME
|
||||||
|
|||||||
@@ -83,6 +83,30 @@ func (s State) Family() int {
|
|||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do returns if the request has the DO (DNSSEC OK) bit set.
|
||||||
|
func (s State) Do() bool {
|
||||||
|
if o := s.Req.IsEdns0(); o != nil {
|
||||||
|
return o.Do()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// UDPSize returns if UDP buffer size advertised in the requests OPT record.
|
||||||
|
// Or when the request was over TCP, we return the maximum allowed size of 64K.
|
||||||
|
func (s State) Size() int {
|
||||||
|
if s.Proto() == "tcp" {
|
||||||
|
return dns.MaxMsgSize
|
||||||
|
}
|
||||||
|
if o := s.Req.IsEdns0(); o != nil {
|
||||||
|
s := o.UDPSize()
|
||||||
|
if s < dns.MinMsgSize {
|
||||||
|
s = dns.MinMsgSize
|
||||||
|
}
|
||||||
|
return int(s)
|
||||||
|
}
|
||||||
|
return dns.MinMsgSize
|
||||||
|
}
|
||||||
|
|
||||||
// Type returns the type of the question as a string.
|
// Type returns the type of the question as a string.
|
||||||
func (s State) Type() string {
|
func (s State) Type() string {
|
||||||
return dns.Type(s.Req.Question[0].Qtype).String()
|
return dns.Type(s.Req.Question[0].Qtype).String()
|
||||||
|
|||||||
Reference in New Issue
Block a user