mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 00:04:15 -04:00
more etcd stuff
This commit is contained in:
@@ -8,24 +8,24 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
etcdc "github.com/coreos/etcd/client"
|
||||
"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.
|
||||
func Etcd(c *Controller) (middleware.Middleware, error) {
|
||||
keysapi, err := etcdParse(c)
|
||||
client, err := etcdParse(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(next middleware.Handler) middleware.Handler {
|
||||
return file.File{Next: next, Zones: zones}
|
||||
return etcd.NewEtcd(client, next, c.ServerBlockHosts)
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func etcdParse(c *Controller) (etcdc.KeysAPI, error) {
|
||||
@@ -33,43 +33,30 @@ func etcdParse(c *Controller) (etcdc.KeysAPI, error) {
|
||||
if c.Val() == "etcd" {
|
||||
// etcd [address...]
|
||||
if !c.NextArg() {
|
||||
|
||||
return file.Zones{}, c.ArgErr()
|
||||
// TODO(certs) and friends, this is client side
|
||||
client, err := newEtcdClient([]string{defaultEndpoint}, "", "", "")
|
||||
return client, err
|
||||
}
|
||||
args1 := c.RemainingArgs()
|
||||
fileName := c.Val()
|
||||
|
||||
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)
|
||||
client, err := newEtcdClient(c.RemainingArgs(), "", "", "")
|
||||
return client, err
|
||||
}
|
||||
}
|
||||
return file.Zones{Z: z, Names: names}, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func newEtcdClient(machines []string, tlsCert, tlsKey, tlsCACert string) (etcd.KeysAPI, error) {
|
||||
etcdCfg := etcd.Config{
|
||||
Endpoints: machines,
|
||||
func newEtcdClient(endpoints []string, tlsCert, tlsKey, tlsCACert string) (etcdc.KeysAPI, error) {
|
||||
etcdCfg := etcdc.Config{
|
||||
Endpoints: endpoints,
|
||||
Transport: newHTTPSTransport(tlsCert, tlsKey, tlsCACert),
|
||||
}
|
||||
cli, err := etcd.New(etcdCfg)
|
||||
cli, err := etcdc.New(etcdCfg)
|
||||
if err != nil {
|
||||
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
|
||||
|
||||
if tlsCertFile != "" && tlsKeyFile != "" {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/miekg/coredns/middleware"
|
||||
"github.com/miekg/coredns/middleware/file"
|
||||
|
||||
"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
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/coredns/middleware"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/skynetservices/skydns/singleflight"
|
||||
"github.com/miekg/coredns/middleware/etcd/msg"
|
||||
"github.com/miekg/coredns/middleware/etcd/singleflight"
|
||||
|
||||
etcdc "github.com/coreos/etcd/client"
|
||||
"golang.org/x/net/context"
|
||||
@@ -12,23 +15,136 @@ import (
|
||||
|
||||
type (
|
||||
Etcd struct {
|
||||
Next middleware.Handler
|
||||
|
||||
client etcd.KeysAPI
|
||||
Next middleware.Handler
|
||||
Zones []string
|
||||
client etcdc.KeysAPI
|
||||
ctx context.Context
|
||||
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{
|
||||
Next: next,
|
||||
Zones: zones,
|
||||
client: client,
|
||||
ctx: context.Background(),
|
||||
inflight: &singleflight.Group{},
|
||||
}
|
||||
}
|
||||
|
||||
func (e Etcd) ServerDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||
return 0, nil
|
||||
func (g Etcd) 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
|
||||
@@ -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).
|
||||
|
||||
If you want to `round robin` A and AAAA responses look at the `round_robin` middleware.
|
||||
|
||||
~~~
|
||||
etcd {
|
||||
round_robin
|
||||
path /skydns
|
||||
endpoint address...
|
||||
stubzones
|
||||
}
|
||||
~~~
|
||||
|
||||
* `round_robin`
|
||||
* `path` /skydns
|
||||
* `endpoint` address...
|
||||
* `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)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// Lookup functions, ala
|
||||
// LookupHost
|
||||
// LookupCNAME
|
||||
|
||||
@@ -83,6 +83,30 @@ func (s State) Family() int {
|
||||
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.
|
||||
func (s State) Type() string {
|
||||
return dns.Type(s.Req.Question[0].Qtype).String()
|
||||
|
||||
Reference in New Issue
Block a user