From b6341e8b639ca9385d4b91606e2a756a36fb1eff Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Sun, 20 Mar 2016 21:36:55 +0000 Subject: [PATCH] more etcd stuff --- core/setup/etcd.go | 49 ++---- core/setup/file.go | 1 + middleware/etcd/backend.go | 164 ------------------- middleware/etcd/etcd.go | 132 ++++++++++++++- middleware/etcd/etcd.md | 9 +- middleware/etcd/handler.go | 105 ++++++++++++ middleware/etcd/lookup.go | 327 +++++++++++++++++++++++++++++++++++++ middleware/exchange.go | 4 + middleware/state.go | 24 +++ 9 files changed, 609 insertions(+), 206 deletions(-) delete mode 100644 middleware/etcd/backend.go create mode 100644 middleware/etcd/lookup.go diff --git a/core/setup/etcd.go b/core/setup/etcd.go index 63e5bf8b2..68e150392 100644 --- a/core/setup/etcd.go +++ b/core/setup/etcd.go @@ -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 != "" { diff --git a/core/setup/file.go b/core/setup/file.go index 76aed5249..0b85d84f3 100644 --- a/core/setup/file.go +++ b/core/setup/file.go @@ -6,6 +6,7 @@ import ( "github.com/miekg/coredns/middleware" "github.com/miekg/coredns/middleware/file" + "github.com/miekg/dns" ) diff --git a/middleware/etcd/backend.go b/middleware/etcd/backend.go deleted file mode 100644 index 95990f719..000000000 --- a/middleware/etcd/backend.go +++ /dev/null @@ -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 -} diff --git a/middleware/etcd/etcd.go b/middleware/etcd/etcd.go index f7aa60a71..23bc931fe 100644 --- a/middleware/etcd/etcd.go +++ b/middleware/etcd/etcd.go @@ -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" +) diff --git a/middleware/etcd/etcd.md b/middleware/etcd/etcd.md index e5489298c..e9147c1ff 100644 --- a/middleware/etcd/etcd.md +++ b/middleware/etcd/etcd.md @@ -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` diff --git a/middleware/etcd/handler.go b/middleware/etcd/handler.go index e69de29bb..35c6a98c5 100644 --- a/middleware/etcd/handler.go +++ b/middleware/etcd/handler.go @@ -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 +} +*/ diff --git a/middleware/etcd/lookup.go b/middleware/etcd/lookup.go new file mode 100644 index 000000000..800518d82 --- /dev/null +++ b/middleware/etcd/lookup.go @@ -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 +} +*/ diff --git a/middleware/exchange.go b/middleware/exchange.go index 837fa3cdc..f2e74ab77 100644 --- a/middleware/exchange.go +++ b/middleware/exchange.go @@ -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 diff --git a/middleware/state.go b/middleware/state.go index 163a0dae2..fa8ef74a9 100644 --- a/middleware/state.go +++ b/middleware/state.go @@ -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()