// forwardSearch resolves a query by suffixing with search paths func (s *server) forwardSearch(req *dns.Msg, tcp bool) (*dns.Msg, error) { var r *dns.Msg var nodata *dns.Msg // stores the copy of a NODATA reply var searchName string // stores the current name suffixed with search domain var err error var didSearch bool name := req.Question[0].Name // original qname reqCopy := req.Copy() for _, domain := range s.config.SearchDomains { if strings.HasSuffix(name, domain) { continue } searchName = strings.ToLower(appendDomain(name, domain)) reqCopy.Question[0] = dns.Question{searchName, reqCopy.Question[0].Qtype, reqCopy.Question[0].Qclass} didSearch = true r, err = s.forwardQuery(reqCopy, tcp) if err != nil { // No server currently available, give up break } switch r.Rcode { case dns.RcodeSuccess: // In case of NO_DATA keep searching, otherwise a wildcard entry // could keep us from finding the answer higher in the search list if len(r.Answer) == 0 && !r.MsgHdr.Truncated { nodata = r.Copy() continue } case dns.RcodeNameError: fallthrough case dns.RcodeServerFailure: // try next search element if any continue } // anything else implies that we are done searching break } if !didSearch { m := new(dns.Msg) m.SetRcode(req, dns.RcodeNameError) return m, nil } if err == nil { if r.Rcode == dns.RcodeSuccess { if len(r.Answer) > 0 { cname := new(dns.CNAME) cname.Hdr = dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 360} cname.Target = searchName answers := []dns.RR{cname} for _, rr := range r.Answer { answers = append(answers, rr) } r.Answer = answers } // If we ever got a NODATA, return this instead of a negative result } else if nodata != nil { r = nodata } // Restore original question r.Question[0] = req.Question[0] } if err != nil && nodata != nil { r = nodata r.Question[0] = req.Question[0] err = nil } return r, err }
// TODO(axw) define an error for "no answer" func (s *jujuNameServer) answer(q dns.Question) (dns.RR, error) { if q.Qtype != dns.TypeA { return nil, nil } var envName string entityName := strings.ToLower(strings.TrimSuffix(q.Name, "."+zone)) if i := strings.IndexRune(entityName, '.'); i >= 0 { envName = entityName[i+1:] entityName = entityName[:i] } else { var err error envName, err = envcmd.GetDefaultEnvironment() if err != nil { return nil, err } } // TODO(axw) cache API connection api, err := s.openAPI(envName) if err != nil { return nil, err } defer api.Close() client := api.Client() // If the entity name parses as a tag, extract the ID. This enables // us to address "unit-mysql-0", where we couldn't otherwise, since // slashes are not allowed in domain names. Similarly for container // machines (e.g. to address "0/lxc/0", pass "machine-0-lxc-0"). if tag, err := names.ParseTag(entityName); err == nil { entityName = tag.Id() } var addr string if names.IsValidService(entityName) { status, err := client.Status([]string{entityName}) if err != nil { return nil, err } service := status.Services[entityName] addresses := make([]string, 0, len(service.Units)) for _, unit := range service.Units { if unit.PublicAddress == "" { continue } if includeLost || unit.UnitAgent.Status != "lost" { addresses = append(addresses, unit.PublicAddress) } } // Might be nice to have additional info in TXT? if len(addresses) == 0 { return nil, nil } addr = addresses[rand.Intn(len(addresses))] } else { // Assume it's a machine or unit name. addr, err = client.PublicAddress(entityName) if err != nil { return nil, err } } ip := net.ParseIP(addr) if ip != nil { rr := new(dns.A) rr.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0, } rr.A = ip return rr, nil } else { rr := new(dns.CNAME) rr.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, } rr.Target = addr + "." return rr, nil } }