With this you can retreive the raw data that the etcd middleware used to create the reply. The debug data is put in TXT records that are stuffed in the CH classs. This is only enabled if you specify `debug` in the etcd stanza. You can retrieve it by prefixing your query with 'o-o.debug.' For instance: ; <<>> DiG 9.10.3-P4-Ubuntu <<>> @localhost -p 1053 SRV o-o.debug.production.*.skydns.local ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47798 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ;; QUESTION SECTION: ;o-o.debug.production.*.skydns.local. IN SRV ;; ANSWER SECTION: production.*.skydns.local. 154 IN SRV 10 50 8080 service1.example.com. production.*.skydns.local. 154 IN SRV 10 50 8080 service2.example.com. ;; ADDITIONAL SECTION: skydns.local.skydns.east.production.rails.1. 154 CH TXT "service1.example.com:8080(10,0,,false)[0,]" skydns.local.skydns.west.production.rails.2. 154 CH TXT "service2.example.com:8080(10,0,,false)[0,]"
198 lines
6 KiB
Go
198 lines
6 KiB
Go
package msg
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/miekg/dns"
|
|
)
|
|
|
|
// This *is* the rdata from a SRV record, but with a twist.
|
|
// Host (Target in SRV) must be a domain name, but if it looks like an IP
|
|
// address (4/6), we will treat it like an IP address.
|
|
type Service struct {
|
|
Host string `json:"host,omitempty"`
|
|
Port int `json:"port,omitempty"`
|
|
Priority int `json:"priority,omitempty"`
|
|
Weight int `json:"weight,omitempty"`
|
|
Text string `json:"text,omitempty"`
|
|
Mail bool `json:"mail,omitempty"` // Be an MX record. Priority becomes Preference.
|
|
Ttl uint32 `json:"ttl,omitempty"`
|
|
|
|
// When a SRV record with a "Host: IP-address" is added, we synthesize
|
|
// a srv.Target domain name. Normally we convert the full Key where
|
|
// the record lives to a DNS name and use this as the srv.Target. When
|
|
// TargetStrip > 0 we strip the left most TargetStrip labels from the
|
|
// DNS name.
|
|
TargetStrip int `json:"targetstrip,omitempty"`
|
|
|
|
// Group is used to group (or *not* to group) different services
|
|
// together. Services with an identical Group are returned in the same
|
|
// answer.
|
|
Group string `json:"group,omitempty"`
|
|
|
|
// Etcd key where we found this service and ignored from json un-/marshalling
|
|
Key string `json:"-"`
|
|
}
|
|
|
|
// RR returns an RR representation of s. It is in a condensed form to minimize space
|
|
// when this is returned in a DNS message.
|
|
// The RR will look like:
|
|
// skydns.local.skydns.east.production.rails.1. 300 CH TXT "service1.example.com:8080(10,0,,false)[0,]"
|
|
// etcd Key Ttl Host:Port < see below >
|
|
// between parens: (Priority, Weight, Text (only first 200 bytes!), Mail)
|
|
// between blockquotes: [TargetStrip,Group]
|
|
// If the record is synthesised by CoreDNS (i.e. no lookup in etcd happened):
|
|
//
|
|
// skydns.local.skydns.east.production.rails.1. 300 CH TXT "service1.example.com:8080(10,0,,false)[0,]"
|
|
//
|
|
func (s *Service) RR() *dns.TXT {
|
|
l := len(s.Text)
|
|
if l > 200 {
|
|
l = 200
|
|
}
|
|
t := new(dns.TXT)
|
|
t.Hdr.Class = dns.ClassCHAOS
|
|
t.Hdr.Ttl = s.Ttl
|
|
t.Hdr.Rrtype = dns.TypeTXT
|
|
// TODO(miek): key guaranteerd to be > 1?
|
|
t.Hdr.Name = strings.Replace(s.Key[1:], "/", ".", -1) + "." // TODO(miek): slightly more like etcd.Domain()
|
|
|
|
t.Txt = make([]string, 1)
|
|
t.Txt[0] = fmt.Sprintf("%s:%d(%d,%d,%s,%t)[%d,%s]",
|
|
s.Host, s.Port,
|
|
s.Priority, s.Weight, s.Text[:l], s.Mail,
|
|
s.TargetStrip, s.Group)
|
|
return t
|
|
}
|
|
|
|
// NewSRV returns a new SRV record based on the Service.
|
|
func (s *Service) NewSRV(name string, weight uint16) *dns.SRV {
|
|
host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip)
|
|
|
|
return &dns.SRV{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.Ttl},
|
|
Priority: uint16(s.Priority), Weight: weight, Port: uint16(s.Port), Target: dns.Fqdn(host)}
|
|
}
|
|
|
|
// NewMX returns a new MX record based on the Service.
|
|
func (s *Service) NewMX(name string) *dns.MX {
|
|
host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip)
|
|
|
|
return &dns.MX{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: s.Ttl},
|
|
Preference: uint16(s.Priority), Mx: host}
|
|
}
|
|
|
|
// NewA returns a new A record based on the Service.
|
|
func (s *Service) NewA(name string, ip net.IP) *dns.A {
|
|
return &dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.Ttl}, A: ip}
|
|
}
|
|
|
|
// NewAAAA returns a new AAAA record based on the Service.
|
|
func (s *Service) NewAAAA(name string, ip net.IP) *dns.AAAA {
|
|
return &dns.AAAA{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.Ttl}, AAAA: ip}
|
|
}
|
|
|
|
// NewCNAME returns a new CNAME record based on the Service.
|
|
func (s *Service) NewCNAME(name string, target string) *dns.CNAME {
|
|
return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: s.Ttl}, Target: dns.Fqdn(target)}
|
|
}
|
|
|
|
// NewTXT returns a new TXT record based on the Service.
|
|
func (s *Service) NewTXT(name string) *dns.TXT {
|
|
return &dns.TXT{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.Ttl}, Txt: split255(s.Text)}
|
|
}
|
|
|
|
// NewNS returns a new NS record based on the Service.
|
|
func (s *Service) NewNS(name string) *dns.NS {
|
|
host := targetStrip(dns.Fqdn(s.Host), s.TargetStrip)
|
|
return &dns.NS{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: s.Ttl}, Ns: host}
|
|
}
|
|
|
|
// Group checks the services in sx, it looks for a Group attribute on the shortest
|
|
// keys. If there are multiple shortest keys *and* the group attribute disagrees (and
|
|
// is not empty), we don't consider it a group.
|
|
// If a group is found, only services with *that* group (or no group) will be returned.
|
|
func Group(sx []Service) []Service {
|
|
if len(sx) == 0 {
|
|
return sx
|
|
}
|
|
|
|
// Shortest key with group attribute sets the group for this set.
|
|
group := sx[0].Group
|
|
slashes := strings.Count(sx[0].Key, "/")
|
|
length := make([]int, len(sx))
|
|
for i, s := range sx {
|
|
x := strings.Count(s.Key, "/")
|
|
length[i] = x
|
|
if x < slashes {
|
|
if s.Group == "" {
|
|
break
|
|
}
|
|
slashes = x
|
|
group = s.Group
|
|
}
|
|
}
|
|
|
|
if group == "" {
|
|
return sx
|
|
}
|
|
|
|
ret := []Service{} // with slice-tricks in sx we can prolly save this allocation (TODO)
|
|
|
|
for i, s := range sx {
|
|
if s.Group == "" {
|
|
ret = append(ret, s)
|
|
continue
|
|
}
|
|
|
|
// Disagreement on the same level
|
|
if length[i] == slashes && s.Group != group {
|
|
return sx
|
|
}
|
|
|
|
if s.Group == group {
|
|
ret = append(ret, s)
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// Split255 splits a string into 255 byte chunks.
|
|
func split255(s string) []string {
|
|
if len(s) < 255 {
|
|
return []string{s}
|
|
}
|
|
sx := []string{}
|
|
p, i := 0, 255
|
|
for {
|
|
if i <= len(s) {
|
|
sx = append(sx, s[p:i])
|
|
} else {
|
|
sx = append(sx, s[p:])
|
|
break
|
|
|
|
}
|
|
p, i = p+255, i+255
|
|
}
|
|
|
|
return sx
|
|
}
|
|
|
|
// targetStrip strips "targetstrip" labels from the left side of the fully qualified name.
|
|
func targetStrip(name string, targetStrip int) string {
|
|
if targetStrip == 0 {
|
|
return name
|
|
}
|
|
|
|
offset, end := 0, false
|
|
for i := 0; i < targetStrip; i++ {
|
|
offset, end = dns.NextLabel(name, offset)
|
|
}
|
|
if end {
|
|
// We overshot the name, use the orignal one.
|
|
offset = 0
|
|
}
|
|
name = name[offset:]
|
|
return name
|
|
}
|