// Get retrieves a list of services from the registry that matches the given domain pattern: // // uuid.host.region.version.service.environment // any of these positions may supply the wildcard "*", to have all values match in this position. // additionally, you only need to specify as much of the domain as needed the domain version.service.environment is perfectly acceptable, // and will assume "*" for all the ommited subdomain positions func (r *DefaultRegistry) Get(domain string) ([]msg.Service, error) { // TODO: account for version wildcards r.mutex.Lock() defer r.mutex.Unlock() // Ensure we are using lowercase keys, as this is the way they are stored domain = strings.ToLower(domain) // DNS queries have a trailing . if strings.HasSuffix(domain, ".") { domain = domain[:len(domain)-1] } tree := dns.SplitDomainName(domain) // Domains can be partial, and we should assume wildcards for the unsupplied portions if len(tree) < 6 { pad := 6 - len(tree) t := make([]string, pad) for i := 0; i < pad; i++ { t[i] = "*" } tree = append(t, tree...) } return r.tree.get(tree) }
// ToPunycode converts unicode domain names to DNS-appropriate punycode names. // This function will return an empty string result for domain names with // invalid unicode strings. This function expects domain names in lowercase. func ToPunycode(s string) string { // Early check to see if encoding is needed. // This will prevent making heap allocations when not needed. if !needToPunycode(s) { return s } tokens := dns.SplitDomainName(s) switch { case s == "": return "" case tokens == nil: // s == . return "." case s[len(s)-1] == '.': tokens = append(tokens, "") } for i := range tokens { t := encode([]byte(tokens[i])) if t == nil { return "" } tokens[i] = string(t) } return strings.Join(tokens, ".") }
func parent(name string) (string, bool) { labels := dns.SplitDomainName(name) if labels == nil { return "", false } return toLowerFQDN(strings.Join(labels[1:], ".")), true }
// NewNSEC3 returns the NSEC3 record need to denial qname, or gives back a NODATA NSEC3. func (s *server) NewNSEC3(qname string) *dns.NSEC { qlabels := dns.SplitDomainName(qname) if len(qlabels) < s.domainLabels { // TODO(miek): can not happen...? } // Strip the last s.domainLabels, return up to 4 before // that. Four labels is the maximum qname we can handle. ls := len(qlabels) - s.domainLabels ls4 := ls - 4 if ls4 < 0 { ls4 = 0 } key := qlabels[ls4:ls] key = key // TODO(miek) // TODO etcd here // prev, next := s.registry.GetNSEC(strings.Join(key, ".")) prev, next := "", "" nsec := &dns.NSEC{Hdr: dns.RR_Header{Name: prev + s.config.Domain + ".", Rrtype: dns.TypeNSEC, Class: dns.ClassINET, Ttl: 60}, NextDomain: next + s.config.Domain + "."} if prev == "" { nsec.TypeBitMap = []uint16{dns.TypeA, dns.TypeSOA, dns.TypeNS, dns.TypeAAAA, dns.TypeRRSIG, dns.TypeNSEC, dns.TypeDNSKEY} } else { nsec.TypeBitMap = []uint16{dns.TypeA, dns.TypeAAAA, dns.TypeSRV, dns.TypeRRSIG, dns.TypeNSEC} } return nsec }
func (d *Device) Hostname() string { l := dns.SplitDomainName(d.Name) if len(l) > 0 { return l[0] } return d.Name }
// Look in .../dns/stub/<domain>/xx for msg.Services. Loop through them // extract <domain> and add them as forwarders (ip:port-combos) for // the stub zones. Only numeric (i.e. IP address) hosts are used. func (s *server) UpdateStubZones() { stubmap := make(map[string][]string) services, err := s.backend.Records("stub.dns."+s.config.Domain, false, net.IP{}) if err != nil { logf("stub zone update failed: %s", err) return } for _, serv := range services { if serv.Port == 0 { serv.Port = 53 } ip := net.ParseIP(serv.Host) if ip == nil { logf("stub zone non-address %s seen for: %s", serv.Key, serv.Host) continue } domain := msg.Domain(serv.Key) // Chop of left most label, because that is used as the nameserver place holder // and drop the right most labels that belong to localDomain. labels := dns.SplitDomainName(domain) domain = dns.Fqdn(strings.Join(labels[1:len(labels)-dns.CountLabel(s.config.localDomain)], ".")) // If the remaining name equals s.config.LocalDomain we ignore it. if domain == s.config.localDomain { logf("not adding stub zone for my own domain") continue } stubmap[domain] = append(stubmap[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port))) } s.config.stub = &stubmap }
func getKey(domain string, rtype uint16) (r string, e error) { if *debug { Log.Printf("getKey: domain: %s, resource type: %d\n", domain, rtype) } if n, ok := dns.IsDomainName(domain); ok { labels := dns.SplitDomainName(domain) // Reverse domain, starting from top-level domain // eg. ".com.mkaczanowski.test " var tmp string for i := 0; i < int(math.Floor(float64(n/2))); i++ { tmp = labels[i] labels[i] = labels[n-1] labels[n-1] = tmp } reverseDomain := strings.Join(labels, ".") r = strings.Join([]string{reverseDomain, strconv.Itoa(int(rtype))}, "_") } else { e = errors.New("Invailid domain: " + domain) Log.Println(e.Error()) } return r, e }
// Path converts a domainname to an etcd path. If s looks like service.staging.skydns.local., // the resulting key will be /skydns/local/skydns/staging/service . func Path(s string) string { l := dns.SplitDomainName(s) for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { l[i], l[j] = l[j], l[i] } return path.Join(append([]string{"/skydns/"}, l...)...) }
func GetRootDomain(s string) string { labels := dns.SplitDomainName(s) if len(labels) < 2 { return s } root := dns.Fqdn(strings.Join([]string{labels[len(labels)-2], labels[len(labels)-1]}, ".")) return root }
// path converts a domainname to an etcd path. If s looks like service.staging.skydns.local., // the resulting key will be /skydns/local/skydns/staging/service . func path(s string) string { l := dns.SplitDomainName(s) for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { l[i], l[j] = l[j], l[i] } // TODO(miek): escape slashes in s. return "/skydns/" + strings.Join(l, "/") }
func (h *handler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) defer w.WriteMsg(m) if len(r.Question) != 1 { return } q := r.Question[0] if q.Qclass != dns.ClassINET { return } if q.Qtype != dns.TypeA { return } withID := false labels := dns.SplitDomainName(q.Name) for i, j := 0, len(labels)-1; i < j; i, j = i+1, j-1 { labels[i], labels[j] = labels[j], labels[i] } if len(labels) > 0 && labels[0] == "" { labels = labels[1:] } if len(labels) > 0 && labels[0] == "switch" { labels = labels[1:] } if len(labels) > 0 && labels[0] == "id" { labels = labels[1:] withID = true } name := strings.Join(labels, "/") var host *hosts.Host if withID { host = h.vnet.Hosts().GetTable().LookupByID(name) } else { host = h.vnet.Hosts().GetTable().LookupByName(name) } if host == nil { return } for _, ip := range host.IPv4Addrs { m.Answer = append(m.Answer, &dns.A{ Hdr: dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60, }, A: ip, }) } }
func GenerateParentDomain(d string) (string, *MyError.MyError) { x := dns.SplitDomainName(d) if cap(x) > 1 { // fmt.Println(x) return strings.Join(x[1:], "."), nil } else { return d, MyError.NewError(MyError.ERROR_NORESULT, d+" has no subdomain") } return d, MyError.NewError(MyError.ERROR_UNKNOWN, d+" unknown error") }
func (s *Server) getSRVRecords(q dns.Question) (records []dns.RR, err error) { var weight uint16 services := make([]msg.Service, 0) key := strings.TrimSuffix(q.Name, s.domain+".") services, err = s.registry.Get(key) if err != nil { return } weight = 0 if len(services) > 0 { weight = uint16(math.Floor(float64(100 / len(services)))) } for _, serv := range services { // TODO: Dynamically set weight records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 10, Weight: weight, Port: serv.Port, Target: serv.Host + "."}) } // Append matching entries in different region than requested with a higher priority labels := dns.SplitDomainName(key) pos := len(labels) - 4 if len(labels) >= 4 && labels[pos] != "any" && labels[pos] != "all" { region := labels[pos] labels[pos] = "any" // TODO: This is pretty much a copy of the above, and should be abstracted additionalServices := make([]msg.Service, len(services)) additionalServices, err = s.registry.Get(strings.Join(labels, ".")) if err != nil { return } weight = 0 if len(additionalServices) <= len(services) { return } weight = uint16(math.Floor(float64(100 / (len(additionalServices) - len(services))))) for _, serv := range additionalServices { // Exclude entries we already have if strings.ToLower(serv.Region) == region { continue } // TODO: Dynamically set priority and weight records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 20, Weight: weight, Port: serv.Port, Target: serv.Host + "."}) } } return }
// dispatch is used to parse a request and invoke the correct handler func (d *DNSServer) dispatch(network string, req, resp *dns.Msg) { // By default the query is in the default datacenter datacenter := d.agent.config.Datacenter // Get the QName without the domain suffix qName := dns.Fqdn(req.Question[0].Name) qName = strings.TrimSuffix(qName, d.domain) // Split into the label parts labels := dns.SplitDomainName(qName) // The last label is either "node", "service" or a datacenter name PARSE: n := len(labels) if n == 0 { goto INVALID } switch labels[n-1] { case "service": if n == 1 { goto INVALID } // Extract the service service := labels[n-2] // Support "." in the label, re-join all the parts tag := "" if n >= 3 { tag = strings.Join(labels[:n-2], ".") } // Handle lookup with and without tag d.serviceLookup(network, datacenter, service, tag, req, resp) case "node": if len(labels) == 1 { goto INVALID } // Allow a "." in the node name, just join all the parts node := strings.Join(labels[:n-1], ".") d.nodeLookup(network, datacenter, node, req, resp) default: // Store the DC, and re-parse datacenter = labels[n-1] labels = labels[:n-1] goto PARSE } return INVALID: d.logger.Printf("[WARN] dns: QName invalid: %s", qName) resp.SetRcode(req, dns.RcodeNameError) }
func (s HelixServer) getResponse(q dns.Question) (Response, error) { addr := dns.SplitDomainName(q.Name) path := []string{"helix"} for i := len(addr) - 1; i >= 0; i-- { path = append(path, addr[i]) } path = append(path, dns.TypeToString[q.Qtype]) return s.Client.Get(strings.Join(path, "/")) }
// As Path, but // if a name contains wildcards (*), the name will be chopped of before the (first) wildcard, and // we do a highler evel search and later find the matching names. // So service.*.skydns.local, will look for all services under skydns.local and will later check // for names that match service.*.skydns.local. If a wildcard is found the returned bool is true. func PathWithWildcard(s string) (string, bool) { l := dns.SplitDomainName(s) for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 { l[i], l[j] = l[j], l[i] } for i, k := range l { if k == "*" { return path.Join(append([]string{"/skydns/"}, l[:i]...)...), true } } return path.Join(append([]string{"/skydns/"}, l...)...), false }
// FromPunycode returns unicode domain name from provided punycode string. func FromPunycode(s string) string { tokens := dns.SplitDomainName(s) switch { case s == "": return "" case tokens == nil: // s == . return "." case s[len(s)-1] == '.': tokens = append(tokens, "") } for i := range tokens { tokens[i] = string(decode([]byte(tokens[i]))) } return strings.Join(tokens, ".") }
// getZoneForName returns the zone string that matches the name and a // list of the DNS labels from name that are within the zone. // For example, if "coredns.local" is a zone configured for the // Kubernetes middleware, then getZoneForName("a.b.coredns.local") // will return ("coredns.local", ["a", "b"]). func (g Kubernetes) getZoneForName(name string) (string, []string) { var zone string var serviceSegments []string for _, z := range g.Zones { if dns.IsSubDomain(z, name) { zone = z serviceSegments = dns.SplitDomainName(name) serviceSegments = serviceSegments[:len(serviceSegments)-dns.CountLabel(zone)] break } } return zone, serviceSegments }
// Look in .../dns/stub/<zone>/xx for msg.Services. Loop through them // extract <zone> and add them as forwarders (ip:port-combos) for // the stub zones. Only numeric (i.e. IP address) hosts are used. // Only the first zone configured on e is used for the lookup. func (e *Etcd) updateStubZones() { zone := e.Zones[0] services, err := e.Records(stubDomain+"."+zone, false) if err != nil { return } stubmap := make(map[string]proxy.Proxy) // track the nameservers on a per domain basis, but allow a list on the domain. nameservers := map[string][]string{} for _, serv := range services { if serv.Port == 0 { serv.Port = 53 } ip := net.ParseIP(serv.Host) if ip == nil { log.Printf("[WARNING] Non IP address stub nameserver: %s", serv.Host) continue } domain := msg.Domain(serv.Key) labels := dns.SplitDomainName(domain) // If the remaining name equals any of the zones we have, we ignore it. for _, z := range e.Zones { // Chop of left most label, because that is used as the nameserver place holder // and drop the right most labels that belong to zone. // We must *also* chop of dns.stub. which means cutting two more labels. domain = dns.Fqdn(strings.Join(labels[1:len(labels)-dns.CountLabel(z)-2], ".")) if domain == z { log.Printf("[WARNING] Skipping nameserver for domain we are authoritative for: %s", domain) continue } nameservers[domain] = append(nameservers[domain], net.JoinHostPort(serv.Host, strconv.Itoa(serv.Port))) } } for domain, nss := range nameservers { stubmap[domain] = proxy.New(nss) } // atomic swap (at least that's what we hope it is) if len(stubmap) > 0 { e.Stubmap = &stubmap } return }
// dispatch is used to parse a request and invoke the correct handler func (d *DNSServer) dispatch(network string, req, resp *dns.Msg) { // By default the query is in the default datacenter datacenter := d.agent.config.Datacenter // Get the QName without the domain suffix qName := dns.Fqdn(req.Question[0].Name) qName = strings.TrimSuffix(qName, d.domain) // Split into the label parts labels := dns.SplitDomainName(qName) // The last label is either "node", "service" or a datacenter name PARSE: if len(labels) == 0 { goto INVALID } switch labels[len(labels)-1] { case "service": // Handle lookup with and without tag switch len(labels) { case 2: d.serviceLookup(network, datacenter, labels[0], "", req, resp) case 3: d.serviceLookup(network, datacenter, labels[1], labels[0], req, resp) default: goto INVALID } case "node": if len(labels) != 2 { goto INVALID } d.nodeLookup(network, datacenter, labels[0], req, resp) default: // Store the DC, and re-parse datacenter = labels[len(labels)-1] labels = labels[:len(labels)-1] goto PARSE } return INVALID: d.logger.Printf("[WARN] dns: QName invalid: %s", qName) resp.SetRcode(req, dns.RcodeNameError) }
func (x *DNSTransportUpstreamCodec) Decode(msg string) ([]byte, DNSCodecHeader) { var ret []byte var header DNSCodecHeader labels := dns.SplitDomainName(msg) if len(labels) < 1+x.domain_label_count { return nil, header } header_bytes, err := base32.StdEncoding.DecodeString(labels[0]) if err != nil { return nil, header } x.header_codec.DecodeFromBytes(header_bytes, &header) labels = labels[1 : len(labels)-x.domain_label_count] for _, label := range labels { var data []byte data, err = base32.StdEncoding.DecodeString(label) if err == nil { ret = append(ret, data...) } } return ret, header }
func getQuestionName(z *Zone, req *dns.Msg) string { lx := dns.SplitDomainName(req.Question[0].Name) ql := lx[0 : len(lx)-z.LabelCount] return strings.ToLower(strings.Join(ql, ".")) }
// dispatch is used to parse a request and invoke the correct handler func (d *DNSServer) dispatch(network string, req, resp *dns.Msg) { // By default the query is in the default datacenter datacenter := d.agent.config.Datacenter // Get the QName without the domain suffix qName := strings.ToLower(dns.Fqdn(req.Question[0].Name)) qName = strings.TrimSuffix(qName, d.domain) // Split into the label parts labels := dns.SplitDomainName(qName) // The last label is either "node", "service" or a datacenter name PARSE: n := len(labels) if n == 0 { goto INVALID } switch labels[n-1] { case "service": if n == 1 { goto INVALID } // Support RFC 2782 style syntax if n == 3 && strings.HasPrefix(labels[n-2], "_") && strings.HasPrefix(labels[n-3], "_") { // Grab the tag since we make nuke it if it's tcp tag := labels[n-2][1:] // Treat _name._tcp.service.consul as a default, no need to filter on that tag if tag == "tcp" { tag = "" } // _name._tag.service.consul d.serviceLookup(network, datacenter, labels[n-3][1:], tag, req, resp) // Consul 0.3 and prior format for SRV queries } else { // Support "." in the label, re-join all the parts tag := "" if n >= 3 { tag = strings.Join(labels[:n-2], ".") } // tag[.tag].name.service.consul d.serviceLookup(network, datacenter, labels[n-2], tag, req, resp) } case "node": if len(labels) == 1 { goto INVALID } // Allow a "." in the node name, just join all the parts node := strings.Join(labels[:n-1], ".") d.nodeLookup(network, datacenter, node, req, resp) default: // Store the DC, and re-parse datacenter = labels[n-1] labels = labels[:n-1] goto PARSE } return INVALID: d.logger.Printf("[WARN] dns: QName invalid: %s", qName) d.addSOA(d.domain, resp) resp.SetRcode(req, dns.RcodeNameError) }
// dispatch is used to parse a request and invoke the correct handler func (d *DNSServer) dispatch(network string, req, resp *dns.Msg) { // By default the query is in the default datacenter datacenter := d.agent.config.Datacenter // Get the QName without the domain suffix qName := strings.ToLower(dns.Fqdn(req.Question[0].Name)) qName = strings.TrimSuffix(qName, d.domain) // Split into the label parts labels := dns.SplitDomainName(qName) // The last label is either "node", "service", "query", or a datacenter name PARSE: n := len(labels) if n == 0 { goto INVALID } switch labels[n-1] { case "service": if n == 1 { goto INVALID } // Support RFC 2782 style syntax if n == 3 && strings.HasPrefix(labels[n-2], "_") && strings.HasPrefix(labels[n-3], "_") { // Grab the tag since we make nuke it if it's tcp tag := labels[n-2][1:] // Treat _name._tcp.service.consul as a default, no need to filter on that tag if tag == "tcp" { tag = "" } // _name._tag.service.consul d.serviceLookup(network, datacenter, labels[n-3][1:], tag, req, resp) // Consul 0.3 and prior format for SRV queries } else { // Support "." in the label, re-join all the parts tag := "" if n >= 3 { tag = strings.Join(labels[:n-2], ".") } // tag[.tag].name.service.consul d.serviceLookup(network, datacenter, labels[n-2], tag, req, resp) } case "node": if n == 1 { goto INVALID } // Allow a "." in the node name, just join all the parts node := strings.Join(labels[:n-1], ".") d.nodeLookup(network, datacenter, node, req, resp) case "query": if n == 1 { goto INVALID } // Allow a "." in the query name, just join all the parts. query := strings.Join(labels[:n-1], ".") d.preparedQueryLookup(network, datacenter, query, req, resp) case "addr": if n != 2 { goto INVALID } switch len(labels[0]) / 2 { // IPv4 case 4: ip, err := hex.DecodeString(labels[0]) if err != nil { goto INVALID } resp.Answer = append(resp.Answer, &dns.A{ Hdr: dns.RR_Header{ Name: qName + d.domain, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: uint32(d.config.NodeTTL / time.Second), }, A: ip, }) // IPv6 case 16: ip, err := hex.DecodeString(labels[0]) if err != nil { goto INVALID } resp.Answer = append(resp.Answer, &dns.AAAA{ Hdr: dns.RR_Header{ Name: qName + d.domain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: uint32(d.config.NodeTTL / time.Second), }, AAAA: ip, }) } default: // Store the DC, and re-parse datacenter = labels[n-1] labels = labels[:n-1] goto PARSE } return INVALID: d.logger.Printf("[WARN] dns: QName invalid: %s", qName) d.addSOA(d.domain, resp) resp.SetRcode(req, dns.RcodeNameError) }
func (s *Server) getSRVRecords(q dns.Question) (records []dns.RR, extra []dns.RR, err error) { var weight uint16 services := make([]msg.Service, 0) key := strings.TrimSuffix(q.Name, s.domain+".") services, err = s.registry.Get(key) if err != nil { return } weight = 0 if len(services) > 0 { weight = uint16(math.Floor(float64(100 / len(services)))) } for _, serv := range services { // TODO: Dynamically set weight // a Service may have an IP as its Host"name", in this case // substitute UUID + "." + s.domain+"." an add an A record // with the name and IP in the additional section. // TODO(miek): check if resolvers actually grok this ip := net.ParseIP(serv.Host) switch { case ip == nil: records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 10, Weight: weight, Port: serv.Port, Target: serv.Host + "."}) continue case ip.To4() != nil: extra = append(extra, &dns.A{Hdr: dns.RR_Header{Name: serv.UUID + "." + s.domain + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: serv.TTL}, A: ip.To4()}) records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 10, Weight: weight, Port: serv.Port, Target: serv.UUID + "." + s.domain + "."}) case ip.To16() != nil: extra = append(extra, &dns.AAAA{Hdr: dns.RR_Header{Name: serv.UUID + "." + s.domain + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: serv.TTL}, AAAA: ip.To16()}) records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 10, Weight: weight, Port: serv.Port, Target: serv.UUID + "." + s.domain + "."}) default: panic("skydns: internal error") } } // Append matching entries in different region than requested with a higher priority labels := dns.SplitDomainName(key) pos := len(labels) - 4 if len(labels) >= 4 && labels[pos] != "*" { region := labels[pos] labels[pos] = "*" // TODO: This is pretty much a copy of the above, and should be abstracted additionalServices := make([]msg.Service, len(services)) additionalServices, err = s.registry.Get(strings.Join(labels, ".")) if err != nil { return } weight = 0 if len(additionalServices) <= len(services) { return } weight = uint16(math.Floor(float64(100 / (len(additionalServices) - len(services))))) for _, serv := range additionalServices { // Exclude entries we already have if strings.ToLower(serv.Region) == region { continue } // TODO: Dynamically set priority and weight // TODO(miek): same as above: abstract away ip := net.ParseIP(serv.Host) switch { case ip == nil: records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 20, Weight: weight, Port: serv.Port, Target: serv.Host + "."}) continue case ip.To4() != nil: extra = append(extra, &dns.A{Hdr: dns.RR_Header{Name: serv.UUID + "." + s.domain + ".", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: serv.TTL}, A: ip.To4()}) records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 20, Weight: weight, Port: serv.Port, Target: serv.UUID + "." + s.domain + "."}) case ip.To16() != nil: extra = append(extra, &dns.AAAA{Hdr: dns.RR_Header{Name: serv.UUID + "." + s.domain + ".", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: serv.TTL}, AAAA: ip.To16()}) records = append(records, &dns.SRV{Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: serv.TTL}, Priority: 20, Weight: weight, Port: serv.Port, Target: serv.UUID + "." + s.domain + "."}) default: panic("skydns: internal error") } } } return }
func (d dnsAPI) ServiceLookup(w dns.ResponseWriter, req *dns.Msg) { qName := req.Question[0].Name qType := req.Question[0].Qtype name := strings.TrimSuffix(strings.ToLower(dns.Fqdn(qName)), d.Domain) labels := dns.SplitDomainName(name) tcp := isTCP(w.RemoteAddr()) res := &dns.Msg{} res.Authoritative = true res.Compress = true res.RecursionAvailable = len(d.Recursors) > 0 res.SetReply(req) defer func() { if res.Rcode == dns.RcodeSuccess && qType == dns.TypeSOA { // SOA answer if requested. at the end of the request to ensure we didn't hit NXDOMAIN res.Answer = []dns.RR{d.soaRecord()} } if len(res.Answer) == 0 { // Add authority section with SOA if the answer has no items res.Ns = []dns.RR{d.soaRecord()} } w.WriteMsg(res) }() nxdomain := func() { res.SetRcode(req, dns.RcodeNameError) } var service string var proto string var instanceID string var leader bool switch { case len(labels) == 1: // normal lookup service = labels[0] case len(labels) == 2 && strings.HasPrefix(labels[0], "_") && strings.HasPrefix(labels[1], "_"): // RFC 2782 request looks like _postgres._tcp service = labels[0][1:] proto = labels[1][1:] case len(labels) == 3 && labels[2] == "_i": // address lookup for instance in RFC 2782 SRV record service = labels[1] instanceID = labels[0] case len(labels) == 2 && labels[0] == "leader": // leader lookup leader = true service = labels[1] default: nxdomain() return } var instances []*discoverd.Instance if !leader { a, err := d.GetStore().Instances(service) if err != nil { log.Printf("discoverd: dns: cannot retrieve instances: %s", err) nxdomain() return } else if a == nil { nxdomain() return } instances = a } if leader || instanceID != "" { // we're doing a lookup for a single instance var resInst *discoverd.Instance if leader { sl, err := d.GetStore().ServiceLeader(service) if err != nil { log.Printf("discoverd: dns: cannot retrieve service leader: %s", err) nxdomain() return } resInst = sl } else { for _, inst := range instances { if inst.ID == instanceID { resInst = inst break } } } if resInst == nil { nxdomain() return } addr := parseAddr(resInst) if qType != dns.TypeA && qType != dns.TypeAAAA && qType != dns.TypeANY && qType != dns.TypeSRV || addr.IPv4 == nil && qType == dns.TypeA || addr.IPv6 == nil && qType == dns.TypeAAAA { // no results if we're looking up an record that doesn't match the // request type or the type is incorrect return } res.Answer = make([]dns.RR, 0, 2) if qType != dns.TypeSRV { res.Answer = append(res.Answer, addrRecord(qName, addr)) } if qType == dns.TypeSRV || qType == dns.TypeANY { res.Answer = append(res.Answer, d.srvRecord(qName, service, addr, false)) } if tcp && qType == dns.TypeSRV { res.Extra = []dns.RR{addrRecord(qName, addr)} } return } if qType == dns.TypeSOA { // We don't need to do any more processing, as NXDOMAIN can't be reached // beyond this point, the SOA answer is added in the deferred function // above return } addrs := make([]*addrData, 0, len(instances)) added := make(map[string]struct{}, len(instances)) for _, inst := range instances { if proto != "" && inst.Proto != proto { continue } addr := parseAddr(inst) if _, ok := added[addr.String]; ok { continue } if addr.IPv4 == nil && qType == dns.TypeA || addr.IPv6 == nil && qType == dns.TypeAAAA { // Skip instance if we have an IPv6 address but want IPv4 or vice versa continue } if qType != dns.TypeSRV { // skip duplicate IPs if we're not doing an SRV lookup added[addr.String] = struct{}{} } addrs = append(addrs, addr) } if len(addrs) == 0 { // return empty response return } shuffle(addrs) // Truncate the response if we're using UDP if !tcp && len(addrs) > maxUDPRecords { addrs = addrs[:maxUDPRecords] } res.Answer = make([]dns.RR, 0, len(addrs)*2) for _, addr := range addrs { if qType == dns.TypeANY || qType == dns.TypeA || qType == dns.TypeAAAA { res.Answer = append(res.Answer, addrRecord(qName, addr)) } } for _, addr := range addrs { if qType == dns.TypeANY || qType == dns.TypeSRV { res.Answer = append(res.Answer, d.srvRecord(qName, service, addr, true)) } } if qType == dns.TypeSRV && tcp { // Add extra records mapping instance IDs to addresses res.Extra = make([]dns.RR, len(addrs)) for i, addr := range addrs { res.Extra[i] = addrRecord(d.instanceDomain(service, addr.ID), addr) } } }