// Sign signs a message m, it takes care of negative or nodata responses as // well by synthesising NSEC3 records. It will also cache the signatures, using // a hash of the signed data as a key. // We also fake the origin TTL in the signature, because we don't want to // throw away signatures when services decide to have longer TTL. So we just // set the origTTL to 60. // TODO(miek): revisit origTTL func (s *server) Sign(m *dns.Msg, bufsize uint16) { now := time.Now().UTC() incep := uint32(now.Add(-3 * time.Hour).Unix()) // 2+1 hours, be sure to catch daylight saving time and such expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week defer func() { promCacheSize.WithLabelValues("signature").Set(float64(s.scache.Size())) }() for _, r := range rrSets(m.Answer) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Answer = append(m.Answer, sig) } } for _, r := range rrSets(m.Ns) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Ns = append(m.Ns, sig) } } for _, r := range rrSets(m.Extra) { if r[0].Header().Rrtype == dns.TypeRRSIG || r[0].Header().Rrtype == dns.TypeOPT { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Extra = append(m.Extra, sig) } } if bufsize >= 512 || bufsize <= 4096 { // TCP here? promErrorCount.WithLabelValues("truncated").Inc() m.Truncated = m.Len() > int(bufsize) } o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetDo() o.SetUDPSize(4096) // TODO(miek): echo client m.Extra = append(m.Extra, o) return }
// subzoneConflict returns true if name is a child or parent zone of // any element in zones. If conflicts exist, return the conflicting zones. func subzoneConflict(zones []string, name string) (bool, []string) { conflicts := []string{} for _, z := range zones { if dns.IsSubDomain(z, name) || dns.IsSubDomain(name, z) { conflicts = append(conflicts, z) } } return (len(conflicts) != 0), conflicts }
func ParseSOA(d string, r []dns.RR) (*dns.SOA, []*dns.NS, *MyError.MyError) { var soa *dns.SOA var ns_a []*dns.NS for _, v := range r { vh := v.Header() if vh.Name == dns.Fqdn(d) || dns.IsSubDomain(vh.Name, dns.Fqdn(d)) { switch vh.Rrtype { case dns.TypeSOA: if vv, ok := v.(*dns.SOA); ok { //fmt.Print(utils.GetDebugLine(), "ParseSOA: ", vv) soa = vv utils.ServerLogger.Debug("ParseSOA: %v", vv) } case dns.TypeNS: if vv, ok := v.(*dns.NS); ok { ns_a = append(ns_a, vv) } default: //fmt.Println(utils.GetDebugLine(), " PasreSOA: error unexpect: ", v) utils.ServerLogger.Error("ParseSOA: error unexpect %v", v) } } else { //fmt.Print(utils.GetDebugLine(), "ParseSOA 258 ") //fmt.Println(utils.GetDebugLine(), vh.Name+" not match "+d) utils.ServerLogger.Debug("%s not match %s", vh.Name, d) return nil, nil, MyError.NewError(MyError.ERROR_NOTVALID, d+" has no SOA record,try parent") } } if soa != nil { return soa, ns_a, nil } else { return nil, nil, MyError.NewError(MyError.ERROR_NORESULT, "No SOA record for domain "+d) } }
func (e Etcd) AAAA(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, debug, err } 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 middleware.Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those continue } newRecord := serv.NewCNAME(state.QName(), serv.Host) if len(previousRecords) > 7 { // don't add it, and just continue continue } if isDuplicateCNAME(newRecord, previousRecords) { continue } state1 := copyState(state, serv.Host, state.QType()) nextRecords, nextDebug, err := e.AAAA(zone, state1, append(previousRecords, newRecord)) if err == nil { // Not 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...) debug = append(debug, nextDebug...) } continue } // This means we can not complete the CNAME, try to look else where. target := newRecord.Target if dns.IsSubDomain(zone, target) { // We should already have found it continue } m1, e1 := e.Proxy.Lookup(state, target, state.QType()) if e1 != nil { continue } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) continue // both here again case ip.To4() != nil: // nada? case ip.To4() == nil: records = append(records, serv.NewAAAA(state.QName(), ip.To16())) } } return records, debug, nil }
// TrimDomainName trims origin from s if s is a subdomain. // This function will never return "", but returns "@" instead (@ represents the apex (bare) domain). func TrimDomainName(s, origin string) string { // An apex (bare) domain is always returned as "@". // If the return value ends in a ".", the domain was not the suffix. // origin can end in "." or not. Either way the results should be the same. if len(s) == 0 { return "@" // Return the apex (@) rather than "". } // Someone is using TrimDomainName(s, ".") to remove a dot if it exists. if origin == "." { return strings.TrimSuffix(s, origin) } // Dude, you aren't even if the right subdomain! if !dns.IsSubDomain(origin, s) { return s } slabels := dns.Split(s) olabels := dns.Split(origin) m := dns.CompareDomainName(s, origin) if len(olabels) == m { if len(olabels) == len(slabels) { return "@" // origin == s } if (s[0] == '.') && (len(slabels) == (len(olabels) + 1)) { return "@" // TrimDomainName(".foo.", "foo.") } } // Return the first (len-m) labels: return s[:slabels[len(slabels)-m]-1] }
// Matches checks to see if other is a subdomain (or the same domain) of n. // This method assures that names can be easily and consistently matched. func (n Name) Matches(child string) bool { if dns.Name(n) == dns.Name(child) { return true } return dns.IsSubDomain(string(n), child) }
// Sign signs a message m, it takes care of negative or nodata responses as // well by synthesising NSEC3 records. It will also cache the signatures, using // a hash of the signed data as a key. // We also fake the origin TTL in the signature, because we don't want to // throw away signatures when services decide to have longer TTL. So we just // set the origTTL to 60. // TODO(miek): revisit origTTL func (s *server) Sign(m *dns.Msg, bufsize uint16) { now := time.Now().UTC() incep := uint32(now.Add(-3 * time.Hour).Unix()) // 2+1 hours, be sure to catch daylight saving time and such expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week for _, r := range rrSets(m.Answer) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Answer = append(m.Answer, sig) } } for _, r := range rrSets(m.Ns) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Ns = append(m.Ns, sig) } } for _, r := range rrSets(m.Extra) { if r[0].Header().Rrtype == dns.TypeRRSIG || r[0].Header().Rrtype == dns.TypeOPT { continue } if !dns.IsSubDomain(s.config.Domain, r[0].Header().Name) { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Extra = append(m.Extra, sig) } } o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetDo() o.SetUDPSize(4096) // TODO(miek): echo client m.Extra = append(m.Extra, o) return }
// 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, bufsize uint16, dnssec bool) (records []dns.RR, extra []dns.RR, err error) { return nil, nil, errors.New("Not Implement !") 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 }
// MX returns MX records from etcd. // If the Target is not a name but an IP address, a name is created on the fly. func (e Etcd) MX(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, nil, debug, 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(state.QName()) records = append(records, mx) if _, ok := lookup[mx.Mx]; ok { break } lookup[mx.Mx] = true if !dns.IsSubDomain(zone, mx.Mx) { m1, e1 := e.Proxy.Lookup(state, mx.Mx, dns.TypeA) if e1 == nil { extra = append(extra, m1.Answer...) } m1, e1 = e.Proxy.Lookup(state, mx.Mx, dns.TypeAAAA) 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 state1 := copyState(state, mx.Mx, dns.TypeA) addr, debugAddr, e1 := e.A(zone, state1, nil) if e1 == nil { extra = append(extra, addr...) debug = append(debug, debugAddr...) } // e.AAAA as well case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewMX(state.QName())) extra = append(extra, serv.NewA(serv.Host, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewMX(state.QName())) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) } } return records, extra, debug, nil }
func (k Kubernetes) A(zone string, state middleware.State, previousRecords []dns.RR) (records []dns.RR, err error) { services, err := k.records(state, false) if err != nil { return nil, err } for _, serv := range services { ip := net.ParseIP(serv.Host) switch { case ip == nil: // TODO(miek): lowercasing? Should lowercase in everything see #85 if middleware.Name(state.Name()).Matches(dns.Fqdn(serv.Host)) { // x CNAME x is a direct loop, don't add those continue } newRecord := serv.NewCNAME(state.QName(), serv.Host) if len(previousRecords) > 7 { // don't add it, and just continue continue } if isDuplicateCNAME(newRecord, previousRecords) { continue } state1 := copyState(state, serv.Host, state.QType()) nextRecords, err := k.A(zone, state1, append(previousRecords, newRecord)) if err == nil { // Not 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(zone, target) { // We should already have found it continue } m1, e1 := k.Proxy.Lookup(state, target, state.QType()) if e1 != nil { continue } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) continue case ip.To4() != nil: records = append(records, serv.NewA(state.QName(), ip.To4())) case ip.To4() == nil: // nodata? } } return records, nil }
// Matches checks is qname is a subdomain of any of the zones in z. The match // will return the most specific zones that matches other. The empty string // signals a not found condition. func (z Zones) Matches(qname string) string { zone := "" for _, zname := range z { if dns.IsSubDomain(zname, qname) { if len(zname) > len(zone) { zone = zname } } } return zone }
func serve(w dns.ResponseWriter, req *dns.Msg, z *dns.Zone) { if z == nil { panic("fksd: no zone") } m := new(dns.Msg) // Just NACK ANYs if req.Question[0].Qtype == dns.TypeANY { m.SetRcode(req, dns.RcodeServerFailure) ednsFromRequest(req, m) w.WriteMsg(m) return } logPrintf("[zone %s] incoming %s %s %d from %s\n", z.Origin, req.Question[0].Name, dns.TypeToString[req.Question[0].Qtype], req.MsgHdr.Id, w.RemoteAddr()) node, exact, ref := z.FindFunc(req.Question[0].Name, func(n interface{}) bool { return n.(*dns.ZoneData).NonAuth }) if ref { logPrintf("[zone %s] referral due\n", z.Origin) m.SetReply(req) m.Ns = node.RR[dns.TypeNS] for _, n := range m.Ns { if dns.IsSubDomain(n.(*dns.NS).Ns, n.Header().Name) { findGlue(m, z, n.(*dns.NS).Ns) } } ednsFromRequest(req, m) w.WriteMsg(m) return } if exact { exactMatch(w, req, m, z, node) return } // Not an exact match nor an referral if z.Wildcard > 0 { lx := dns.SplitLabels(req.Question[0].Name) wc := "*." + strings.Join(lx[1:], ".") node, exact = z.Find(wc) if exact { logPrintf("[zone %s] wildcard answer\n", z.Origin) // as exact,but not complete -- only the last part } } nameerror(w, m, req) return }
// Lookup looks up qname and qtype in the zone. When do is true DNSSEC records are included. // Three sets of records are returned, one for the answer, one for authority and one for the additional section. func (z *Zone) Lookup(qname string, qtype uint16, do bool) ([]dns.RR, []dns.RR, []dns.RR, Result) { if qtype == dns.TypeSOA { return z.lookupSOA(do) } if qtype == dns.TypeNS && qname == z.origin { return z.lookupNS(do) } elem, res := z.Tree.Search(qname, qtype) if elem == nil { if res == tree.EmptyNonTerminal { return z.emptyNonTerminal(qname, do) } return z.nameError(qname, qtype, do) } if res == tree.Delegation { rrs := elem.Types(dns.TypeNS) glue := []dns.RR{} for _, ns := range rrs { if dns.IsSubDomain(ns.Header().Name, ns.(*dns.NS).Ns) { // even with Do, this should be unsigned. elem, res := z.Tree.SearchGlue(ns.(*dns.NS).Ns) if res == tree.Found { glue = append(glue, elem.Types(dns.TypeAAAA)...) glue = append(glue, elem.Types(dns.TypeA)...) } } } return nil, rrs, glue, Delegation } rrs := elem.Types(dns.TypeCNAME) if len(rrs) > 0 { // should only ever be 1 actually; TODO(miek) check for this? return z.lookupCNAME(rrs, qtype, do) } rrs = elem.Types(qtype) if len(rrs) == 0 { return z.noData(elem, do) } if do { sigs := elem.Types(dns.TypeRRSIG) sigs = signatureForSubType(sigs, qtype) rrs = append(rrs, sigs...) } return rrs, nil, nil, Success }
// 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 }
// Handle exact match func exactMatch(w dns.ResponseWriter, req, m *dns.Msg, z *dns.Zone, node *dns.ZoneData) { logPrintf("[zone %s] exact match for %s\n", z.Origin, req.Question[0].Name) // If we have NS records for this name we still need to give out a referral if nss, ok := node.RR[dns.TypeNS]; ok && node.NonAuth { m.SetReply(req) m.Ns = nss for _, n := range m.Ns { if dns.IsSubDomain(n.(*dns.NS).Ns, n.Header().Name) { findGlue(m, z, n.(*dns.NS).Ns) } } ednsFromRequest(req, m) w.WriteMsg(m) return } // If we have the actual type too if rrs, ok := node.RR[req.Question[0].Qtype]; ok { answer(w, m, req, rrs, z) return } else { // NoData reply or CNAME m.SetReply(req) if cname, ok := node.RR[dns.TypeCNAME]; ok { m.Answer = cname /* i := 0 for cname.Rrtype == dns.TypeCNAME { } */ // Lookup cname.Target // get cname RRssss } findApex(m, z) w.WriteMsg(m) return } nameerror(w, m, req) return }
// 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(q dns.Question, name string, bufsize uint16, dnssec bool) (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) { log.Printf("skydns: target %q is not sub of %q, looking up...", srv.Target, s.config.Domain) 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) records = append(records, serv.NewSRV(q.Name, weight)) extra = append(extra, serv.NewA(serv.Host, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewSRV(q.Name, weight)) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) } } return records, extra, nil }
func (s *server) AddressRecords(q dns.Question, name string, previousRecords []dns.RR, bufsize uint16, dnssec, both bool) (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 { log.Printf("skydns: CNAME lookup limit of 8 exceeded for %s", newRecord) // don't add it, and just continue continue } if s.isDuplicateCNAME(newRecord, previousRecords) { log.Printf("skydns: 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), bufsize, dnssec, both) 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 { log.Printf("skydns: incomplete CNAME chain: %s", e1) continue } // Len(m1.Answer) > 0 here is well? records = append(records, newRecord) records = append(records, m1.Answer...) continue log.Printf("skydns: incomplete CNAME chain for %s", name) 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())) } } if s.config.RoundRobin { s.RoundRobin(records) } return records, nil }
func ListenHTTP(version string, server *DNSServer, domain string, db Zone, port int) { muxRouter := mux.NewRouter() muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "weave DNS", version) fmt.Fprintln(w, server.Status()) }) muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqError := func(msg string, logmsg string, logargs ...interface{}) { httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...) } vars := mux.Vars(r) idStr := vars["id"] ipStr := vars["ip"] name := r.FormValue("fqdn") if name == "" { reqError("Invalid FQDN", "Invalid FQDN in request: %s, %s", r.URL, r.Form) return } ip := net.ParseIP(ipStr) if ip == nil { reqError("Invalid IP", "Invalid IP in request: %s", ipStr) return } if dns.IsSubDomain(domain, name) { Info.Printf("[http] Adding %s -> %s", name, ipStr) if err := db.AddRecord(idStr, name, ip); err != nil { if _, ok := err.(DuplicateError); !ok { httpErrorAndLog( Error, w, "Internal error", http.StatusInternalServerError, "Unexpected error from DB: %s", err) return } // oh, I already know this. whatever. } } else { Info.Printf("[http] Ignoring name %s, not in %s", name, domain) } }) muxRouter.Methods("DELETE").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqError := func(msg string, logmsg string, logargs ...interface{}) { httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...) } vars := mux.Vars(r) idStr := vars["id"] ipStr := vars["ip"] ip := net.ParseIP(ipStr) if ip == nil { reqError("Invalid IP in request", "Invalid IP in request: %s", ipStr) return } Info.Printf("[http] Deleting %s (%s)", idStr, ipStr) if err := db.DeleteRecord(idStr, ip); err != nil { if _, ok := err.(LookupError); !ok { httpErrorAndLog( Error, w, "Internal error", http.StatusInternalServerError, "Unexpected error from DB: %s", err) return } } }) http.Handle("/", muxRouter) address := fmt.Sprintf(":%d", port) if err := http.ListenAndServe(address, nil); err != nil { Error.Fatal("[http] Unable to create http listener: ", err) } }
// SRV returns SRV records from etcd. // If the Target is not a name but an IP address, a name is created on the fly. func (e Etcd) SRV(zone string, state middleware.State) (records, extra []dns.RR, debug []msg.Service, err error) { services, debug, err := e.records(state, false) if err != nil { return nil, nil, nil, err } // 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(state.QName(), weight) records = append(records, srv) if _, ok := lookup[srv.Target]; ok { break } lookup[srv.Target] = true if !dns.IsSubDomain(zone, srv.Target) { m1, e1 := e.Proxy.Lookup(state, srv.Target, dns.TypeA) if e1 == nil { extra = append(extra, m1.Answer...) } m1, e1 = e.Proxy.Lookup(state, srv.Target, dns.TypeAAAA) 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. state1 := copyState(state, srv.Target, dns.TypeA) addr, debugAddr, e1 := e.A(zone, state1, nil) if e1 == nil { extra = append(extra, addr...) debug = append(debug, debugAddr...) } // e.AAA(zone, state1, nil) as well...? case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) srv := serv.NewSRV(state.QName(), 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(state.QName(), weight) records = append(records, srv) extra = append(extra, serv.NewAAAA(srv.Target, ip.To16())) } } return records, extra, debug, nil }
// SRVRecords returns SRV records from etcd. // If the Target is not an name but an IP address, an name is created . func (s *server) SRVRecords(q dns.Question, name string, bufsize uint16, dnssec bool) (records []dns.RR, extra []dns.RR, err error) { path, star := msg.PathWithWildcard(name) r, err := get(s.client, path, true) if err != nil { return nil, nil, err } if !r.Node.Dir { // single element serv := new(msg.Service) if err := json.Unmarshal([]byte(r.Node.Value), serv); err != nil { s.config.log.Infof("failed to parse json: %s", err.Error()) return nil, nil, err } ip := net.ParseIP(serv.Host) ttl := s.calculateTtl(r.Node, serv) if serv.Priority == 0 { serv.Priority = int(s.config.Priority) } serv.Key = r.Node.Key serv.Ttl = ttl switch { case ip == nil: srv := serv.NewSRV(q.Name, uint16(100)) records = append(records, srv) 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 already added. for _, a := range m1.Answer { if _, ok := a.(*dns.CNAME); !ok { extra = append(extra, a) } } } } case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewSRV(q.Name, uint16(100))) extra = append(extra, serv.NewA(serv.Host, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewSRV(q.Name, uint16(100))) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) } return records, extra, nil } sx, err := s.loopNodes(&r.Node.Nodes, strings.Split(msg.Path(name), "/"), star, nil) if err != nil || len(sx) == 0 { return nil, nil, err } // Looping twice to get the right weight vs priority w := make(map[int]int) for _, serv := range sx { 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 sx { 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 { 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) } } } } } lookup[srv.Target] = true case ip.To4() != nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewSRV(q.Name, weight)) extra = append(extra, serv.NewA(serv.Host, ip.To4())) case ip.To4() == nil: serv.Host = msg.Domain(serv.Key) records = append(records, serv.NewSRV(q.Name, weight)) extra = append(extra, serv.NewAAAA(serv.Host, ip.To16())) } } return records, extra, nil }
// ServeDNS is the handler for DNS requests, responsible for parsing DNS request, possibly forwarding // it to a real dns server and returning a response. func (s *server) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) m.Authoritative = true m.RecursionAvailable = true m.Compress = true bufsize := uint16(512) dnssec := false tcp := false if req.Question[0].Qtype == dns.TypeANY { m.Authoritative = false m.Rcode = dns.RcodeRefused m.RecursionAvailable = false m.RecursionDesired = false m.Compress = false // if write fails don't care w.WriteMsg(m) return } if o := req.IsEdns0(); o != nil { bufsize = o.UDPSize() dnssec = o.Do() } if bufsize < 512 { bufsize = 512 } // with TCP we can send 64K if _, ok := w.RemoteAddr().(*net.TCPAddr); ok { bufsize = dns.MaxMsgSize - 1 tcp = true } // Check cache first. key := cache.QuestionKey(req.Question[0], dnssec) m1, exp, hit := s.rcache.Search(key) if hit { // Cache hit! \o/ if time.Since(exp) < 0 { m1.Id = m.Id if dnssec { StatsDnssecOkCount.Inc(1) // The key for DNS/DNSSEC in cache is different, no // need to do Denial/Sign here. //if s.config.PubKey != nil { //s.Denial(m1) // not needed for cache hits //s.Sign(m1, bufsize) //} } if m1.Len() > int(bufsize) && !tcp { m1.Truncated = true } // Still round-robin even with hits from the cache. if s.config.RoundRobin && (req.Question[0].Qtype == dns.TypeA || req.Question[0].Qtype == dns.TypeAAAA) { switch l := len(m1.Answer); l { case 2: if dns.Id()%2 == 0 { m1.Answer[0], m1.Answer[1] = m1.Answer[1], m1.Answer[0] } default: // Do a minimum of l swap, maximum of 4l swaps for j := 0; j < l*(int(dns.Id())%4+1); j++ { q := int(dns.Id()) % l p := int(dns.Id()) % l if q == p { p = (p + 1) % l } m1.Answer[q], m1.Answer[p] = m1.Answer[p], m1.Answer[q] } } } if err := w.WriteMsg(m1); err != nil { s.config.log.Errorf("failure to return reply %q", err) } return } // Expired! /o\ s.rcache.Remove(key) } q := req.Question[0] name := strings.ToLower(q.Name) StatsRequestCount.Inc(1) if verbose { s.config.log.Infof("received DNS Request for %q from %q with type %d", q.Name, w.RemoteAddr(), q.Qtype) } // If the qname is local.dns.skydns.local. and s.config.Local != "", substitute that name. if s.config.Local != "" && name == s.config.localDomain { name = s.config.Local } if q.Qtype == dns.TypePTR && strings.HasSuffix(name, ".in-addr.arpa.") || strings.HasSuffix(name, ".ip6.arpa.") { s.ServeDNSReverse(w, req) return } if q.Qclass != dns.ClassCHAOS && !strings.HasSuffix(name, s.config.Domain) { s.ServeDNSForward(w, req) return } defer func() { if m.Rcode == dns.RcodeServerFailure { if err := w.WriteMsg(m); err != nil { s.config.log.Errorf("failure to return reply %q", err) } return } // Set TTL to the minimum of the RRset. minttl := s.config.Ttl if len(m.Answer) > 1 { for _, r := range m.Answer { if r.Header().Ttl < minttl { minttl = r.Header().Ttl } } for _, r := range m.Answer { r.Header().Ttl = minttl } } s.rcache.InsertMessage(cache.QuestionKey(req.Question[0], dnssec), m) if dnssec { StatsDnssecOkCount.Inc(1) if s.config.PubKey != nil { m.AuthenticatedData = true s.Denial(m) s.Sign(m, bufsize) } } if m.Len() > int(bufsize) && !tcp { // TODO(miek): this is a little brain dead, better is to not add // RRs in the message in the first place. m.Truncated = true } if err := w.WriteMsg(m); err != nil { s.config.log.Errorf("failure to return reply %q", err) } }() if name == s.config.Domain { if q.Qtype == dns.TypeSOA { m.Answer = []dns.RR{s.NewSOA()} return } if q.Qtype == dns.TypeDNSKEY { if s.config.PubKey != nil { m.Answer = []dns.RR{s.config.PubKey} return } } } if q.Qclass == dns.ClassCHAOS { if q.Qtype == dns.TypeTXT { switch name { case "authors.bind.": fallthrough case s.config.Domain: hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} authors := []string{"Erik St. Martin", "Brian Ketelsen", "Miek Gieben", "Michael Crosby"} for _, a := range authors { m.Answer = append(m.Answer, &dns.TXT{Hdr: hdr, Txt: []string{a}}) } for j := 0; j < len(authors)*(int(dns.Id())%4+1); j++ { q := int(dns.Id()) % len(authors) p := int(dns.Id()) % len(authors) if q == p { p = (p + 1) % len(authors) } m.Answer[q], m.Answer[p] = m.Answer[p], m.Answer[q] } return case "version.bind.": fallthrough case "version.server.": hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{Version}}} return case "hostname.bind.": fallthrough case "id.server.": // TODO(miek): machine name to return hdr := dns.RR_Header{Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassCHAOS, Ttl: 0} m.Answer = []dns.RR{&dns.TXT{Hdr: hdr, Txt: []string{"localhost"}}} return } } // still here, fail m.SetReply(req) m.SetRcode(req, dns.RcodeServerFailure) return } switch q.Qtype { case dns.TypeNS: if name != s.config.Domain { break } // Lookup s.config.DnsDomain records, extra, err := s.NSRecords(q, s.config.dnsDomain) if err != nil { if e, ok := err.(*etcd.EtcdError); ok { if e.ErrorCode == 100 { s.NameError(m, 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) if err != nil { if e, ok := err.(*etcd.EtcdError); ok { if e.ErrorCode == 100 { s.NameError(m, req) return } } if err.Error() == "incomplete CNAME chain" { // We can not complete the CNAME internally, *iff* there is a // external name in the set, take it, and try to resolve it externally. if len(records) == 0 { s.NameError(m, req) return } target := "" for _, r := range records { if v, ok := r.(*dns.CNAME); ok { if !dns.IsSubDomain(s.config.Domain, v.Target) { target = v.Target break } } } if target == "" { s.config.log.Warningf("incomplete CNAME chain for %s", name) s.NoDataError(m, req) return } m1, e1 := s.Lookup(target, req.Question[0].Qtype, bufsize, dnssec) if e1 != nil { s.config.log.Errorf("%q", err) s.NoDataError(m, req) return } records = append(records, m1.Answer...) } } m.Answer = append(m.Answer, records...) case dns.TypeCNAME: records, err := s.CNAMERecords(q, name) if err != nil { if e, ok := err.(*etcd.EtcdError); ok { if e.ErrorCode == 100 { s.NameError(m, req) return } } } m.Answer = append(m.Answer, records...) default: fallthrough // also catch other types, so that they return NODATA case dns.TypeSRV, dns.TypeANY: records, extra, err := s.SRVRecords(q, name, bufsize, dnssec) if err != nil { if e, ok := err.(*etcd.EtcdError); ok { if e.ErrorCode == 100 { s.NameError(m, req) return } } } // if we are here again, check the types, because an answer may only // be given for SRV or ANY. 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 || q.Qtype == dns.TypeANY { m.Answer = append(m.Answer, records...) m.Extra = append(m.Extra, extra...) } } if len(m.Answer) == 0 { // NODATA response StatsNoDataCount.Inc(1) m.Ns = []dns.RR{s.NewSOA()} m.Ns[0].Header().Ttl = s.config.MinTtl } }
func ServeHTTP(listener net.Listener, version string, server *DNSServer, dockerCli *docker.Client) { muxRouter := mux.NewRouter() muxRouter.Methods("GET").Path("/status").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "weave DNS", version) fmt.Fprintln(w, server.Status()) fmt.Fprintln(w, server.Zone.Status()) }) muxRouter.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, server.Zone.Domain()) }) muxRouter.Methods("PUT").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqError := func(msg string, logmsg string, logargs ...interface{}) { httpErrorAndLog(Warning, w, msg, http.StatusBadRequest, logmsg, logargs...) } vars := mux.Vars(r) idStr := vars["id"] ipStr := vars["ip"] name := r.FormValue("fqdn") if name == "" { reqError("Invalid FQDN", "Invalid FQDN in request: %s, %s", r.URL, r.Form) return } ip := net.ParseIP(ipStr) if ip == nil { reqError("Invalid IP", "Invalid IP in request: %s", ipStr) return } domain := server.Zone.Domain() if !dns.IsSubDomain(domain, name) { Info.Printf("[http] Ignoring name %s, not in %s", name, domain) return } Info.Printf("[http] Adding %s -> %s", name, ipStr) if err := server.Zone.AddRecord(idStr, name, ip); err != nil { if _, ok := err.(DuplicateError); !ok { httpErrorAndLog( Error, w, "Internal error", http.StatusInternalServerError, "Unexpected error from DB: %s", err) return } // oh, I already know this. whatever. } if dockerCli != nil && dockerCli.IsContainerNotRunning(idStr) { Info.Printf("[http] '%s' is not running: removing", idStr) server.Zone.DeleteRecords(idStr, name, ip) } }) muxRouter.Methods("DELETE").Path("/name/{id:.+}/{ip:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) idStr := vars["id"] ipStr := vars["ip"] ip := net.ParseIP(ipStr) if ip == nil { httpErrorAndLog( Warning, w, "Invalid IP in request", http.StatusBadRequest, "Invalid IP in request: %s", ipStr) return } fqdnStr, fqdn := extractFQDN(r) Info.Printf("[http] Deleting ID %s, IP %s, FQDN %s", idStr, ipStr, fqdnStr) server.Zone.DeleteRecords(idStr, fqdn, ip) }) muxRouter.Methods("DELETE").Path("/name/{id:.+}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) idStr := vars["id"] fqdnStr, fqdn := extractFQDN(r) Info.Printf("[http] Deleting ID %s, IP *, FQDN %s", idStr, fqdnStr) server.Zone.DeleteRecords(idStr, fqdn, net.IP{}) }) muxRouter.Methods("DELETE").Path("/name").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fqdnStr, fqdn := extractFQDN(r) Info.Printf("[http] Deleting ID *, IP *, FQDN %s", fqdnStr) server.Zone.DeleteRecords("", fqdn, net.IP{}) }) http.Handle("/", muxRouter) if err := http.Serve(listener, nil); err != nil { Error.Fatal("[http] Unable to serve http: ", err) } }
func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) { router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, n.domain) }) router.Methods("PUT").Path("/name/{container}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( vars = mux.Vars(r) container = vars["container"] ipStr = vars["ip"] hostname = dns.Fqdn(r.FormValue("fqdn")) ip, err = address.ParseIP(ipStr) ) if err != nil { n.badRequest(w, err) return } if !dns.IsSubDomain(n.domain, hostname) { n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, ipStr, container, n.domain) return } n.AddEntry(hostname, container, n.ourName, ip) if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(container) { n.infof("container '%s' is not running: removing", container) n.Delete(hostname, container, ipStr, ip) } w.WriteHeader(204) }) deleteHandler := func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) hostname := r.FormValue("fqdn") if hostname == "" { hostname = "*" } else { hostname = dns.Fqdn(hostname) } container, ok := vars["container"] if !ok { container = "*" } ipStr, ok := vars["ip"] ip, err := address.ParseIP(ipStr) if ok && err != nil { n.badRequest(w, err) return } else if !ok { ipStr = "*" } n.Delete(hostname, container, ipStr, ip) w.WriteHeader(204) } router.Methods("DELETE").Path("/name/{container}/{ip}").HandlerFunc(deleteHandler) router.Methods("DELETE").Path("/name/{container}").HandlerFunc(deleteHandler) router.Methods("DELETE").Path("/name").HandlerFunc(deleteHandler) router.Methods("GET").Path("/name").Headers("Accept", "application/json").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { n.RLock() defer n.RUnlock() if err := json.NewEncoder(w).Encode(n.entries); err != nil { n.badRequest(w, fmt.Errorf("Error marshalling response: %v", err)) } }) }