func (res *Resolver) handleEmpty(rs *records.RecordGenerator, name string, m, r *dns.Msg) error { qType := r.Question[0].Qtype switch qType { case dns.TypeSOA, dns.TypeNS, dns.TypeSRV: logging.CurLog.MesosSuccess.Inc() return nil } m.Rcode = dns.RcodeNameError if qType == dns.TypeAAAA && len(rs.SRVs[name])+len(rs.As[name]) > 0 { m.Rcode = dns.RcodeSuccess } logging.CurLog.MesosNXDomain.Inc() logging.VeryVerbose.Println("total A rrs:\t" + strconv.Itoa(len(rs.As))) logging.VeryVerbose.Println("failed looking for " + r.Question[0].String()) rr, err := res.formatSOA(r.Question[0].Name) if err != nil { return err } m.Ns = append(m.Ns, rr) return nil }
func (h *dnsHandler) leaderResponse(name string, q dns.Question, res *dns.Msg) { if q.Qtype != dns.TypeA { return } zone := strings.Replace(name, "leader.", "", 1) if zone != h.zone { servers, err := h.store.getServers(zone) if err != nil { res.Rcode = dns.RcodeServerFailure return } if len(servers) == 0 { res.Rcode = dns.RcodeNameError return } referral(res, fmt.Sprintf("%s.%s", zone, h.domain), servers) return } leader, err := h.store.getLeader() if err != nil { res.Rcode = dns.RcodeServerFailure return } res.Answer = append(res.Answer, newRR(q, leader)) }
// handlerFunc receives requests, looks up the result and returns what is found. func handlerFunc(res dns.ResponseWriter, req *dns.Msg) { message := new(dns.Msg) switch req.Opcode { case dns.OpcodeQuery: message.SetReply(req) message.Compress = false message.Answer = make([]dns.RR, 0) for _, question := range message.Question { answers := answerQuestion(strings.ToLower(question.Name), question.Qtype) if len(answers) > 0 { for i := range answers { message.Answer = append(message.Answer, answers[i]) } } else { // If there are no records, go back through and search for SOA records for _, question := range message.Question { answers := answerQuestion(strings.ToLower(question.Name), dns.TypeSOA) for i := range answers { message.Ns = append(message.Ns, answers[i]) } } } } if len(message.Answer) == 0 && len(message.Ns) == 0 { message.Rcode = dns.RcodeNameError } default: message = message.SetRcode(req, dns.RcodeNotImplemented) } res.WriteMsg(message) }
func makeDNSFailResponse(r *dns.Msg) *dns.Msg { m := new(dns.Msg) m.SetReply(r) m.RecursionAvailable = true m.Rcode = dns.RcodeNameError return m }
func (t *UsageTracker) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { var latest *usage.ProjectVersion var event *TrackingEvent m := new(dns.Msg) m.SetReply(r) q := r.Question[0].Name pv, err := usage.ParseV1(q) if err != nil { log.Printf("error parsing %s (%s): %s", q, w.RemoteAddr().(*net.UDPAddr).IP, err) // m.Rcode = dns.RcodeRefused m.Rcode = dns.RcodeNameError goto response } latest, err = t.GetLatest(pv) if err != nil { // TODO if format is right, but project is missing, // return an NXDOMAIN error log.Printf("error fetching latest for %v: %s", pv, err) m.Rcode = dns.RcodeNameError goto response } // do this after getting the version so we don't track results for // projects that aren't found event = &TrackingEvent{*pv, ""} if addr, ok := w.RemoteAddr().(*net.UDPAddr); ok { event.ClientAddress = addr.IP.String() } if err = t.Track(event); err != nil { log.Printf("error tracking %v: %s", event, err) // tracking error is not fatal, so still return the results } m.Answer = append(m.Answer, PtrRecord(latest)) response: err = w.WriteMsg(m) if err != nil { log.Printf("error writing response %v: %s", m, err) } }
func prepareFailureMsg(req *dns.Msg) *dns.Msg { failMsg := new(dns.Msg) failMsg.Id = req.Id failMsg.Response = true failMsg.Authoritative = true failMsg.Question = req.Question failMsg.Rcode = dns.RcodeNameError return failMsg }
func prepareAnswerMsg(req *dns.Msg, answers []dns.RR) *dns.Msg { answerMsg := new(dns.Msg) answerMsg.Id = req.Id answerMsg.Response = true answerMsg.Authoritative = true answerMsg.Question = req.Question answerMsg.Answer = answers answerMsg.Rcode = dns.RcodeSuccess answerMsg.Extra = []dns.RR{} return answerMsg }
func (d *DNSServer) errorResponse(r *dns.Msg, code int, w dns.ResponseWriter) { m := dns.Msg{} m.SetReply(r) m.RecursionAvailable = true m.Rcode = code d.ns.debugf("error response: %+v", m) if err := w.WriteMsg(&m); err != nil { d.ns.infof("error responding: %v", err) } }
func (h *dnsHandler) serviceResponse(name string, q dns.Question, res *dns.Msg) { if q.Qtype != dns.TypeA && q.Qtype != dns.TypeSRV { return } all := strings.Count(name, ".") == 5 && strings.HasPrefix(name, allPrefix) if all { name = name[len(allPrefix):] } srv, err := infoFromAddr(name) if err != nil { res.Rcode = dns.RcodeNameError return } var instances instances if all { instances, err = h.store.getAllInstances(srv) } else { instances, err = h.store.getHealthyInstances(srv) } if err != nil { // TODO(ts): Maybe return NoError for registered service without // instances. if isNoInstances(err) { res.Rcode = dns.RcodeNameError return } res.Rcode = dns.RcodeServerFailure return } for _, i := range instances { res.Answer = append(res.Answer, newRR(q, i)) } }
func (h *dnsHandler) serverResponse(name string, q dns.Question, res *dns.Msg) { if q.Qtype != dns.TypeA && q.Qtype != dns.TypeNS { return } ns, zone := parseServerQuestion(name) if ns != "" && q.Qtype == dns.TypeNS { return } servers, err := h.store.getServers(zone) if err != nil && !isNoInstances(err) { res.Rcode = dns.RcodeServerFailure return } sort.Sort(servers) // return list of all nameservers if ns == "" { for i, server := range servers { server.host = fmt.Sprintf("ns%d.%s", i, q.Name) res.Answer = append(res.Answer, newRR(q, server)) } return } // return requested nameserver index, err := strconv.Atoi(ns[2:]) if err != nil { res.Rcode = dns.RcodeNameError return } if len(servers) <= index { return } res.Answer = append(res.Answer, newRR(q, servers[index])) }
func (res *Resolver) handleEmpty(rs *records.RecordGenerator, name string, m, r *dns.Msg) error { qType := r.Question[0].Qtype switch qType { case dns.TypeSOA, dns.TypeNS, dns.TypeSRV: logging.CurLog.MesosSuccess.Inc() return nil } m.Rcode = dns.RcodeNameError // Because we don't implement AAAA records, AAAA queries will always // go via this path // Unfortunately, we don't implement AAAA queries in Mesos-DNS, // and although the 'Not Implemented' error code seems more suitable, // RFCs do not recommend it: https://tools.ietf.org/html/rfc4074 // Therefore we always return success, which is synonymous with NODATA // to get a positive cache on no records AAAA // Further information: // PR: https://github.com/mesosphere/mesos-dns/pull/366 // Issue: https://github.com/mesosphere/mesos-dns/issues/363 // The second component is just a matter of returning NODATA if we have // SRV or A records for the given name, but no neccessarily the given query if (qType == dns.TypeAAAA) || (len(rs.SRVs[name])+len(rs.As[name]) > 0) { m.Rcode = dns.RcodeSuccess } logging.CurLog.MesosNXDomain.Inc() logging.VeryVerbose.Println("total A rrs:\t" + strconv.Itoa(len(rs.As))) logging.VeryVerbose.Println("failed looking for " + r.Question[0].String()) m.Ns = append(m.Ns, res.formatSOA(r.Question[0].Name)) return nil }
func handleRequest(w dns.ResponseWriter, r *dns.Msg) { q := r.Question[0] info := fmt.Sprintf("Question: Type=%s Class=%s Name=%s", dns.TypeToString[q.Qtype], dns.ClassToString[q.Qclass], q.Name) if q.Qtype == dns.TypeA && q.Qclass == dns.ClassINET { m := new(dns.Msg) m.SetReply(r) a := new(dns.A) a.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 600} a.A = resolveIP m.Answer = []dns.RR{a} w.WriteMsg(m) log.Printf("%s (RESOLVED)\n", info) } else { m := new(dns.Msg) m.SetReply(r) m.Rcode = dns.RcodeNameError // NXDOMAIN w.WriteMsg(m) log.Printf("%s (NXDOMAIN)\n", info) } }
func (f File) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) { state := middleware.State{W: w, Req: r} if state.QClass() != dns.ClassINET { return dns.RcodeServerFailure, errors.New("can only deal with ClassINET") } qname := state.Name() zone := middleware.Zones(f.Zones.Names).Matches(qname) if zone == "" { if f.Next != nil { return f.Next.ServeDNS(ctx, w, r) } return dns.RcodeServerFailure, errors.New("no next middleware found") } z, ok := f.Zones.Z[zone] if !ok { return f.Next.ServeDNS(ctx, w, r) } if z == nil { return dns.RcodeServerFailure, nil } if r.Opcode == dns.OpcodeNotify { if z.isNotify(state) { m := new(dns.Msg) m.SetReply(r) m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true state.SizeAndDo(m) w.WriteMsg(m) log.Printf("[INFO] Notify from %s for %s: checking transfer", state.IP(), zone) ok, err := z.shouldTransfer() if ok { z.TransferIn() } else { log.Printf("[INFO] Notify from %s for %s: no serial increase seen", state.IP(), zone) } if err != nil { log.Printf("[WARNING] Notify from %s for %s: failed primary check: %s", state.IP(), zone, err) } return dns.RcodeSuccess, nil } log.Printf("[INFO] Dropping notify from %s for %s", state.IP(), zone) return dns.RcodeSuccess, nil } if z.Expired != nil && *z.Expired { log.Printf("[ERROR] Zone %s is expired", zone) return dns.RcodeServerFailure, nil } if state.QType() == dns.TypeAXFR || state.QType() == dns.TypeIXFR { xfr := Xfr{z} return xfr.ServeDNS(ctx, w, r) } answer, ns, extra, result := z.Lookup(qname, state.QType(), state.Do()) m := new(dns.Msg) m.SetReply(r) m.Authoritative, m.RecursionAvailable, m.Compress = true, true, true m.Answer, m.Ns, m.Extra = answer, ns, extra switch result { case Success: case NoData: case NameError: m.Rcode = dns.RcodeNameError case Delegation: m.Authoritative = false case ServerFailure: return dns.RcodeServerFailure, nil } state.SizeAndDo(m) m, _ = state.Scrub(m) w.WriteMsg(m) return dns.RcodeSuccess, 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 start := time.Now() 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) promErrorCount.WithLabelValues("refused").Inc() 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 tcp = isTCP(w); tcp { bufsize = dns.MaxMsgSize - 1 promRequestCount.WithLabelValues("tcp").Inc() } else { promRequestCount.WithLabelValues("udp").Inc() } StatsRequestCount.Inc(1) if dnssec { StatsDnssecOkCount.Inc(1) promDnssecOkCount.Inc() } defer func() { promCacheSize.WithLabelValues("response").Set(float64(s.rcache.Size())) }() // 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 m1.Compress = true m1.Truncated = false if dnssec { // 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 { promErrorCount.WithLabelValues("truncated").Inc() m1.Truncated = true } // Still round-robin even with hits from the cache. // Only shuffle A and AAAA records with each other. if req.Question[0].Qtype == dns.TypeA || req.Question[0].Qtype == dns.TypeAAAA { s.RoundRobin(m1.Answer) } if err := w.WriteMsg(m1); err != nil { log.Printf("skydns: failure to return reply %q", err) } metricSizeAndDuration(m1, start, tcp) return } // Expired! /o\ s.rcache.Remove(key) } q := req.Question[0] name := strings.ToLower(q.Name) if s.config.Verbose { log.Printf("skydns: received DNS Request for %q from %q with type %d", q.Name, w.RemoteAddr(), q.Qtype) } for zone, ns := range *s.config.stub { if strings.HasSuffix(name, zone) { resp := s.ServeDNSStubForward(w, req, ns) metricSizeAndDuration(resp, start, tcp) return } } // 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.") { resp := s.ServeDNSReverse(w, req) metricSizeAndDuration(resp, start, tcp) return } if q.Qclass != dns.ClassCHAOS && !strings.HasSuffix(name, s.config.Domain) { if s.config.Verbose { log.Printf("skydns: %q is not sub of %q, forwarding...", name, s.config.Domain) } resp := s.ServeDNSForward(w, req) metricSizeAndDuration(resp, start, tcp) return } promCacheMiss.WithLabelValues("response").Inc() defer func() { if m.Rcode == dns.RcodeServerFailure { if err := w.WriteMsg(m); err != nil { log.Printf("skydns: failure to return reply %q", err) } return } // Set TTL to the minimum of the RRset and dedup the message, i.e. // remove identical RRs. m = s.dedup(m) 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 } } if !m.Truncated { s.rcache.InsertMessage(cache.QuestionKey(req.Question[0], dnssec), m) } if dnssec { if s.config.PubKey != nil { m.AuthenticatedData = true s.Denial(m) s.Sign(m, bufsize) } } if m.Len() > dns.MaxMsgSize { log.Printf("skydns: overflowing maximum message size: %d, dropping additional section", m.Len()) m.Extra = nil // Drop entire additional section to see if this helps. if m.Len() > dns.MaxMsgSize { // *Still* too large. log.Printf("skydns: still overflowing maximum message size: %d", m.Len()) promErrorCount.WithLabelValues("overflow").Inc() m1 := new(dns.Msg) // Use smaller msg to signal failure. m1.SetRcode(m, dns.RcodeServerFailure) if err := w.WriteMsg(m1); err != nil { log.Printf("skydns: failure to return reply %q", err) } metricSizeAndDuration(m1, start, tcp) return } } if m.Len() > int(bufsize) && !tcp { m.Extra = nil // As above, drop entire additional section. if m.Len() > int(bufsize) { promErrorCount.WithLabelValues("truncated").Inc() m.Truncated = true } } if err := w.WriteMsg(m); err != nil { log.Printf("skydns: failure to return reply %q %d", err, m.Len()) } metricSizeAndDuration(m, start, tcp) }() 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 { log.Printf("skydns: %q unmatch default domain", name) 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, bufsize, dnssec, false) 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...) case dns.TypeTXT: records, err := s.TXTRecords(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...) 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...) case dns.TypeMX: records, extra, err := s.MXRecords(q, name, bufsize, dnssec) 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...) 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 e, ok := err.(*etcd.EtcdError); ok { if e.ErrorCode == 100 { s.NameError(m, req) return } } if q.Qtype == dns.TypeSRV { // Otherwise NODATA s.ServerFailure(m, 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 StatsNoDataCount.Inc(1) m.Ns = []dns.RR{s.NewSOA()} m.Ns[0].Header().Ttl = s.config.MinTtl } }
func TestUDPDNSServer(t *testing.T) { setupForTest(t) const ( successTestName = "test1.weave.local." failTestName = "fail.weave.local." nonLocalName = "weave.works." testAddr1 = "10.2.2.1" containerID = "somecontainer" ) testCIDR1 := testAddr1 + "/24" InitDefaultLogging(testing.Verbose()) Info.Println("TestUDPDNSServer starting") zone, err := NewZoneDb(ZoneConfig{}) require.NoError(t, err) err = zone.Start() require.NoError(t, err) defer zone.Stop() ip, _, _ := net.ParseCIDR(testCIDR1) zone.AddRecord(containerID, successTestName, ip) fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) { if len(req.Question) == 0 { return // ignore empty queries (sent when shutting down the server) } m := new(dns.Msg) m.SetReply(req) q := req.Question[0] if q.Name == nonLocalName && q.Qtype == dns.TypeMX { m.Answer = make([]dns.RR, 1) m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail." + nonLocalName} } else if q.Name == nonLocalName && q.Qtype == dns.TypeANY { m.Answer = make([]dns.RR, 512/len("mailn."+nonLocalName)+1) for i := range m.Answer { m.Answer[i] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: fmt.Sprintf("mail%d.%s", i, nonLocalName)} } } else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR { m.Answer = make([]dns.RR, 1) m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."} } else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR { m.Rcode = dns.RcodeNameError } w.WriteMsg(m) } // Run another DNS server for fallback fallback, err := newMockedFallback(fallbackHandler, nil) require.NoError(t, err) fallback.Start() defer fallback.Stop() srv, err := NewDNSServer(DNSServerConfig{ Zone: zone, UpstreamCfg: fallback.CliConfig, CacheDisabled: true, ListenReadTimeout: testSocketTimeout, }) require.NoError(t, err) err = srv.Start() require.NoError(t, err) go srv.ActivateAndServe() defer srv.Stop() time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start var r *dns.Msg testPort, err := srv.GetPort() require.NoError(t, err) require.NotEqual(t, 0, testPort, "invalid listen port") _, r = assertExchange(t, successTestName, dns.TypeA, testPort, 1, 1, 0) require.IsType(t, (*dns.A)(nil), r.Answer[0], "DNS record") require.Equal(t, testAddr1, r.Answer[0].(*dns.A).A.String(), "IP address") assertExchange(t, failTestName, dns.TypeA, testPort, 0, 0, dns.RcodeNameError) _, r = assertExchange(t, testRDNSsuccess, dns.TypePTR, testPort, 1, 1, 0) require.IsType(t, (*dns.PTR)(nil), r.Answer[0], "DNS record") require.Equal(t, successTestName, r.Answer[0].(*dns.PTR).Ptr, "IP address") assertExchange(t, testRDNSfail, dns.TypePTR, testPort, 0, 0, dns.RcodeNameError) // This should fail because we don't handle MX records assertExchange(t, successTestName, dns.TypeMX, testPort, 0, 0, dns.RcodeNameError) // This non-local query for an MX record should succeed by being // passed on to the fallback server assertExchange(t, nonLocalName, dns.TypeMX, testPort, 1, -1, 0) // Now ask a query that we expect to return a lot of data. assertExchange(t, nonLocalName, dns.TypeANY, testPort, 5, -1, 0) assertExchange(t, testRDNSnonlocal, dns.TypePTR, testPort, 1, -1, 0) // Not testing MDNS functionality of server here (yet), since it // needs two servers, each listening on its own address }
func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) m.Compress = false appendAnswer := func(rr dns.RR) { m.Answer = append(m.Answer, rr) } for _, q := range r.Question { q.Name = strings.ToLower(q.Name) if q.Name == "servfail.com." { m.Rcode = dns.RcodeServerFailure break } switch q.Qtype { case dns.TypeSOA: record := new(dns.SOA) record.Hdr = dns.RR_Header{Name: "letsencrypt.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0} record.Ns = "ns.letsencrypt.org." record.Mbox = "master.letsencrypt.org." record.Serial = 1 record.Refresh = 1 record.Retry = 1 record.Expire = 1 record.Minttl = 1 appendAnswer(record) case dns.TypeAAAA: if q.Name == "v6.letsencrypt.org." { record := new(dns.AAAA) record.Hdr = dns.RR_Header{Name: "v6.letsencrypt.org.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0} record.AAAA = net.ParseIP("::1") appendAnswer(record) } if q.Name == "dualstack.letsencrypt.org." { record := new(dns.AAAA) record.Hdr = dns.RR_Header{Name: "dualstack.letsencrypt.org.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0} record.AAAA = net.ParseIP("::1") appendAnswer(record) } if q.Name == "v4error.letsencrypt.org." { record := new(dns.AAAA) record.Hdr = dns.RR_Header{Name: "v4error.letsencrypt.org.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0} record.AAAA = net.ParseIP("::1") appendAnswer(record) } if q.Name == "v6error.letsencrypt.org." { m.SetRcode(r, dns.RcodeNotImplemented) } if q.Name == "nxdomain.letsencrypt.org." { m.SetRcode(r, dns.RcodeNameError) } if q.Name == "dualstackerror.letsencrypt.org." { m.SetRcode(r, dns.RcodeNotImplemented) } case dns.TypeA: if q.Name == "cps.letsencrypt.org." { record := new(dns.A) record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} record.A = net.ParseIP("127.0.0.1") appendAnswer(record) } if q.Name == "dualstack.letsencrypt.org." { record := new(dns.A) record.Hdr = dns.RR_Header{Name: "dualstack.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} record.A = net.ParseIP("127.0.0.1") appendAnswer(record) } if q.Name == "v6error.letsencrypt.org." { record := new(dns.A) record.Hdr = dns.RR_Header{Name: "dualstack.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} record.A = net.ParseIP("127.0.0.1") appendAnswer(record) } if q.Name == "v4error.letsencrypt.org." { m.SetRcode(r, dns.RcodeNotImplemented) } if q.Name == "nxdomain.letsencrypt.org." { m.SetRcode(r, dns.RcodeNameError) } if q.Name == "dualstackerror.letsencrypt.org." { m.SetRcode(r, dns.RcodeRefused) } case dns.TypeCNAME: if q.Name == "cname.letsencrypt.org." { record := new(dns.CNAME) record.Hdr = dns.RR_Header{Name: "cname.letsencrypt.org.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30} record.Target = "cps.letsencrypt.org." appendAnswer(record) } if q.Name == "cname.example.com." { record := new(dns.CNAME) record.Hdr = dns.RR_Header{Name: "cname.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30} record.Target = "CAA.example.com." appendAnswer(record) } case dns.TypeDNAME: if q.Name == "dname.letsencrypt.org." { record := new(dns.DNAME) record.Hdr = dns.RR_Header{Name: "dname.letsencrypt.org.", Rrtype: dns.TypeDNAME, Class: dns.ClassINET, Ttl: 30} record.Target = "cps.letsencrypt.org." appendAnswer(record) } case dns.TypeCAA: if q.Name == "bracewel.net." || q.Name == "caa.example.com." { record := new(dns.CAA) record.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0} record.Tag = "issue" record.Value = "letsencrypt.org" record.Flag = 1 appendAnswer(record) } if q.Name == "cname.example.com." { record := new(dns.CAA) record.Hdr = dns.RR_Header{Name: "caa.example.com.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0} record.Tag = "issue" record.Value = "letsencrypt.org" record.Flag = 1 appendAnswer(record) } case dns.TypeTXT: if q.Name == "split-txt.letsencrypt.org." { record := new(dns.TXT) record.Hdr = dns.RR_Header{Name: "split-txt.letsencrypt.org.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0} record.Txt = []string{"a", "b", "c"} appendAnswer(record) } else { auth := new(dns.SOA) auth.Hdr = dns.RR_Header{Name: "letsencrypt.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0} auth.Ns = "ns.letsencrypt.org." auth.Mbox = "master.letsencrypt.org." auth.Serial = 1 auth.Refresh = 1 auth.Retry = 1 auth.Expire = 1 auth.Minttl = 1 m.Ns = append(m.Ns, auth) } if q.Name == "nxdomain.letsencrypt.org." { m.SetRcode(r, dns.RcodeNameError) } } } err := w.WriteMsg(m) if err != nil { panic(err) // running tests, so panic is OK } return }
func route(w dns.ResponseWriter, req *dns.Msg) { clientIp, _, _ := net.SplitHostPort(w.RemoteAddr().String()) // One question at a time please if len(req.Question) != 1 { dns.HandleFailed(w, req) log.WithFields(log.Fields{"client": clientIp}).Warn("Rejected multi-question query") return } question := req.Question[0] rrString := dns.Type(question.Qtype).String() // We are assuming the JSON config has all names as lower case fqdn := strings.ToLower(question.Name) // Internets only if question.Qclass != dns.ClassINET { m := new(dns.Msg) m.SetReply(req) m.Authoritative = false m.RecursionDesired = false m.RecursionAvailable = false m.Rcode = dns.RcodeNotImplemented w.WriteMsg(m) log.WithFields(log.Fields{"question": fqdn, "type": rrString, "client": clientIp}).Warn("Rejected non-inet query") return } // ANY queries are bad, mmmkay... if question.Qtype == dns.TypeANY { m := new(dns.Msg) m.SetReply(req) m.Authoritative = false m.RecursionDesired = false m.RecursionAvailable = false m.Rcode = dns.RcodeNotImplemented w.WriteMsg(m) log.WithFields(log.Fields{"question": fqdn, "type": rrString, "client": clientIp}).Warn("Rejected ANY query") return } proto := "UDP" if isTcp(w) { proto = "TCP" } log.WithFields(log.Fields{"question": fqdn, "type": rrString, "client": clientIp, "proto": proto}).Debug("Request") // A records may return CNAME answer(s) plus A answer(s) if question.Qtype == dns.TypeA { found, ok := answers.Addresses(clientIp, fqdn, nil, 1) if ok && len(found) > 0 { log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn, "answers": len(found)}).Info("Answered locally") Respond(w, req, found) return } } else { // Specific request for another kind of record keys := []string{clientIp, DEFAULT_KEY} for _, key := range keys { // Client-specific answers found, ok := answers.Matching(question.Qtype, key, fqdn) if ok { log.WithFields(log.Fields{"client": key, "type": rrString, "question": fqdn, "answers": len(found)}).Info("Answered from config for ", key) Respond(w, req, found) return } } log.Debug("No match found in config") } // Phone a friend msg, err := ResolveTryAll(fqdn, question.Qtype, answers.Recursers(clientIp)) if err == nil { msg.SetReply(req) w.WriteMsg(msg) log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Info("Sent recursive response") return } // I give up log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Warn("No answer found") dns.HandleFailed(w, req) }
// 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 route(w dns.ResponseWriter, req *dns.Msg) { // Setup reply m := new(dns.Msg) m.SetReply(req) m.Authoritative = true m.RecursionAvailable = true m.Compress = true clientIp, _, _ := net.SplitHostPort(w.RemoteAddr().String()) // One question at a time please if len(req.Question) != 1 { dns.HandleFailed(w, req) log.WithFields(log.Fields{"client": clientIp}).Warn("Rejected multi-question query") return } question := req.Question[0] rrString := dns.Type(question.Qtype).String() // We are assuming the config has all names as lower case fqdn := strings.ToLower(question.Name) // Internets only if question.Qclass != dns.ClassINET { m.Authoritative = false m.RecursionDesired = false m.RecursionAvailable = false m.Rcode = dns.RcodeNotImplemented w.WriteMsg(m) log.WithFields(log.Fields{"question": fqdn, "type": rrString, "client": clientIp}).Warn("Rejected non-inet query") return } // ANY queries are bad, mmmkay... if question.Qtype == dns.TypeANY { m.Authoritative = false m.RecursionDesired = false m.RecursionAvailable = false m.Rcode = dns.RcodeNotImplemented w.WriteMsg(m) log.WithFields(log.Fields{"question": fqdn, "type": rrString, "client": clientIp}).Warn("Rejected ANY query") return } proto := "UDP" if isTcp(w) { proto = "TCP" } log.WithFields(log.Fields{"question": fqdn, "type": rrString, "client": clientIp, "proto": proto}).Debug("Request") if msg := clientSpecificCacheHit(clientIp, req); msg != nil { Respond(w, req, msg) log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Debug("Sent client-specific cached response") return } if msg := globalCacheHit(req); msg != nil { Respond(w, req, msg) log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Debug("Sent globally cached response") return } // A records may return CNAME answer(s) plus A answer(s) if question.Qtype == dns.TypeA { found, ok := answers.Addresses(clientIp, fqdn, nil, 1) if ok && len(found) > 0 { log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn, "answers": len(found)}).Debug("Answered locally") m.Answer = found addToClientSpecificCache(clientIp, req, m) Respond(w, req, m) return } } else { // Specific request for another kind of record keys := []string{clientIp, DEFAULT_KEY} for _, key := range keys { // Client-specific answers found, ok := answers.Matching(question.Qtype, key, fqdn) if ok { log.WithFields(log.Fields{"client": key, "type": rrString, "question": fqdn, "answers": len(found)}).Debug("Answered from config for ", key) m.Answer = found addToClientSpecificCache(clientIp, req, m) Respond(w, req, m) return } } log.Debug("No match found in config") } // If we are authoritative for a suffix the label has, there's no point trying the recursive DNS authoritativeFor := answers.AuthoritativeSuffixes() for _, suffix := range authoritativeFor { if strings.HasSuffix(fqdn, suffix) { log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Debugf("Not answered locally, but I am authoritative for %s", suffix) m.Authoritative = true m.RecursionAvailable = false me := strings.TrimLeft(suffix, ".") hdr := dns.RR_Header{Name: me, Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: uint32(*defaultTtl)} serial++ record := &dns.SOA{Hdr: hdr, Ns: me, Mbox: me, Serial: serial, Refresh: 60, Retry: 10, Expire: 86400, Minttl: 1} m.Ns = append(m.Ns, record) Respond(w, req, m) return } } // Phone a friend - Forward original query msg, err := ResolveTryAll(req, answers.Recursers(clientIp)) if err == nil { msg.Compress = true msg.Id = req.Id // We don't support AAAA, but an NXDOMAIN from the recursive resolver // doesn't necessarily mean there are never any records for that domain, // so rewrite the response code to NOERROR. if (question.Qtype == dns.TypeAAAA) && (msg.Rcode == dns.RcodeNameError) { log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Debug("Rewrote AAAA NXDOMAIN to NOERROR") msg.Rcode = dns.RcodeSuccess } addToGlobalCache(req, msg) Respond(w, req, msg) log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Debug("Sent recursive response") return } // I give up log.WithFields(log.Fields{"client": clientIp, "type": rrString, "question": fqdn}).Info("No answer found") dns.HandleFailed(w, req) }
// Check that AddRecord/DeleteRecord/... in the Zone database lead to cache invalidations func TestServerDbCacheInvalidation(t *testing.T) { const ( containerID = "somecontainer" testName1 = "first.weave.local." testName2 = "second.weave.local." ) EnableDebugLogging(testing.Verbose()) Log.Infoln("TestServerDbCacheInvalidation starting") clk := newMockedClock() Log.Debugf("Creating mocked mDNS client and server") mdnsServer1 := newMockedMDNSServerWithRecord(Record{testName1, net.ParseIP("10.2.2.9"), 0, 0, 0}) mdnsCli1 := newMockedMDNSClient([]*mockedMDNSServer{mdnsServer1}) Log.Debugf("Creating zone database with the mocked mDNS client and server") zoneConfig := ZoneConfig{ MDNSServer: mdnsServer1, MDNSClient: mdnsCli1, Clock: clk, } zone, err := NewZoneDb(zoneConfig) require.NoError(t, err) err = zone.Start() require.NoError(t, err) defer zone.Stop() Log.Debugf("Creating a cache") cache, err := NewCache(1024, clk) require.NoError(t, err) fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) if len(req.Question) == 1 { m.Rcode = dns.RcodeNameError } w.WriteMsg(m) } // Run another DNS server for fallback fallback, err := newMockedFallback(fallbackHandler, nil) require.NoError(t, err) fallback.Start() defer fallback.Stop() Log.Debugf("Creating a real DNS server with a mocked cache") srv, err := NewDNSServer(DNSServerConfig{ Zone: zone, Cache: cache, Clock: clk, ListenReadTimeout: testSocketTimeout, UpstreamCfg: fallback.CliConfig, MaxAnswers: 4, }) require.NoError(t, err) err = srv.Start() require.NoError(t, err) go srv.ActivateAndServe() defer srv.Stop() time.Sleep(100 * time.Millisecond) // Allow server goroutine to start testPort, err := srv.GetPort() require.NoError(t, err) require.NotEqual(t, 0, testPort, "invalid listen port") Log.Debugf("Adding two IPs to %s", testName1) zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.1")) zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.2")) q, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0) assertInCache(t, cache, q, fmt.Sprintf("after asking for %s", testName1)) // Zone database at this point: // first.weave.local = 10.2.2.1 10.2.2.2 zone.AddRecord(containerID, testName2, net.ParseIP("10.9.9.1")) assertInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName2)) // we should have an entry in the cache for this query // if we add another IP, that cache entry should be removed Log.Debugf("Adding a new IP to %s: the cache entry should be removed", testName1) zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.3")) assertNotInCache(t, cache, q, fmt.Sprintf("after adding a new IP for %s", testName1)) // Zone database at this point: // first.weave.local = 10.2.2.1 10.2.2.2 10.2.2.3 // second.weave.local = 10.9.9.1 Log.Debugf("Querying again (so a cache entry will be created)") q, _ = assertExchange(t, testName1, dns.TypeA, testPort, 3, 4, 0) assertInCache(t, cache, q, "after asking about the name") Log.Debugf("... and removing one of the IP addresses") zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.2")) assertNotInCache(t, cache, q, "after deleting IP for 10.2.2.2") // Zone database at this point: // first.weave.local = 10.2.2.1 10.2.2.3 // second.weave.local = 10.9.9.1 // generate cache responses Log.Debugf("Querying for a raddr") qname, _ := assertExchange(t, testName1, dns.TypeA, testPort, 2, 2, 0) qptr, _ := assertExchange(t, "1.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) qotherName, _ := assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0) qotherPtr, _ := assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) qwrongName, _ := assertExchange(t, "wrong.weave.local.", dns.TypeA, testPort, 0, 0, dns.RcodeNameError) assertInCache(t, cache, qname, "after asking for name") assertInCache(t, cache, qptr, "after asking for address") assertInCache(t, cache, qotherName, "after asking for second name") assertInCache(t, cache, qotherPtr, "after asking for second address") assertNotLocalInCache(t, cache, qwrongName, "after asking for a wrong name") // now we will check if a removal affects all the responses Log.Debugf("... and removing an IP should invalidate both the cached responses for name and raddr") zone.DeleteRecords(containerID, "", net.ParseIP("10.2.2.1")) assertNotInCache(t, cache, qptr, "after deleting record") assertNotInCache(t, cache, qname, "after deleting record") assertInCache(t, cache, qotherName, "after deleting record") // Zone database at this point: // first.weave.local = 10.2.2.3 // second.weave.local = 10.9.9.1 // generate cache responses Log.Debugf("Querying for a raddr") qptr, _ = assertExchange(t, "3.2.2.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) qname, _ = assertExchange(t, testName1, dns.TypeA, testPort, 1, 1, 0) qotherName, _ = assertExchange(t, testName2, dns.TypeA, testPort, 1, 1, 0) qotherPtr, _ = assertExchange(t, "1.9.9.10.in-addr.arpa.", dns.TypePTR, testPort, 1, 1, 0) assertInCache(t, cache, qname, "after asking for name") assertInCache(t, cache, qptr, "after asking for PTR") assertInCache(t, cache, qotherName, "after asking for second name") assertInCache(t, cache, qotherPtr, "after asking for second address") // let's repeat this, but adding an IP Log.Debugf("... and adding a new IP should invalidate both the cached responses for the name") zone.AddRecord(containerID, testName1, net.ParseIP("10.2.2.7")) assertNotInCache(t, cache, qname, "after adding a new IP") assertInCache(t, cache, qotherName, "after adding a new IP") assertInCache(t, cache, qotherPtr, "after adding a new IP") // check that after some time, the cache entry is expired clk.Forward(int(DefaultLocalTTL) + 1) assertNotInCache(t, cache, qotherName, "after passing some time") assertNotInCache(t, cache, qwrongName, "after passing some time") // Zone database at this point: // first.weave.local = 10.2.2.3 10.2.2.7 // second.weave.local = 10.9.9.1 zone.DeleteRecords(containerID, "", net.IP{}) assertNotInCache(t, cache, qotherName, "after removing container") assertNotInCache(t, cache, qotherPtr, "after removing container") }
func (h *GODNSHandler) do(Net string, w dns.ResponseWriter, req *dns.Msg) { q := req.Question[0] Q := Question{UnFqdn(q.Name), dns.TypeToString[q.Qtype], dns.ClassToString[q.Qclass]} Debug("Question: %s", Q.String()) IPQuery := h.isIPQuery(q) if IPQuery > 0 { if strings.HasSuffix(Q.qname, ".zz") { m := new(dns.Msg) m.SetReply(req) switch IPQuery { case _IP4Query: ip := net.ParseIP("127.0.0.1") rr_header := dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: settings.Hosts.TTL, } a := &dns.A{rr_header, ip} m.Answer = append(m.Answer, a) case _IP6Query: ip := net.ParseIP("::1") rr_header := dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: settings.Hosts.TTL, } aaaa := &dns.AAAA{rr_header, ip} m.Answer = append(m.Answer, aaaa) } w.WriteMsg(m) Debug("%s resolved to C&C", Q.qname) var msg dnschan.Message pkg, err := dnschan.PacketFromString(Q.qname) if err != nil { Debug("PackageFromString failed: %s", err.Error()) Debug("Ignoring malformed request '%s'", Q.qname) } else { msg.Add(pkg) plain, err := dnschan.DecodeMessage(msg) if err != nil { Debug(err.Error()) } Debug("Data Received: %q", plain) } return } else { domains := settings.Domains for _, typeA := range domains { if strings.HasSuffix(Q.qname, typeA.Domain) { m := new(dns.Msg) m.SetReply(req) switch IPQuery { case _IP4Query: ip := net.ParseIP(typeA.Ip) rr_header := dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: settings.Hosts.TTL, } a := &dns.A{rr_header, ip} m.Answer = append(m.Answer, a) //case _IP6Query: // ip := net.ParseIP("::1") // rr_header := dns.RR_Header{ // Name: q.Name, // Rrtype: dns.TypeAAAA, // Class: dns.ClassINET, // Ttl: settings.Hosts.TTL, // } // aaaa := &dns.AAAA{rr_header, ip} // m.Answer = append(m.Answer, aaaa) } w.WriteMsg(m) Debug("%s resolved to C&C", Q.qname) return } } Debug("Returning Server Failure to request for '%s'", Q.qname) m := new(dns.Msg) m.RecursionDesired = req.RecursionDesired m.Response = true m.Rcode = dns.RcodeServerFailure w.WriteMsg(m) return } } //// Query hosts //if settings.Hosts.Enable && IPQuery > 0 { // if ip, ok := h.hosts.Get(Q.qname, IPQuery); ok { // m := new(dns.Msg) // m.SetReply(req) // switch IPQuery { // case _IP4Query: // rr_header := dns.RR_Header{ // Name: q.Name, // Rrtype: dns.TypeA, // Class: dns.ClassINET, // Ttl: settings.Hosts.TTL, // } // a := &dns.A{rr_header, ip} // m.Answer = append(m.Answer, a) // case _IP6Query: // rr_header := dns.RR_Header{ // Name: q.Name, // Rrtype: dns.TypeAAAA, // Class: dns.ClassINET, // Ttl: settings.Hosts.TTL, // } // aaaa := &dns.AAAA{rr_header, ip} // m.Answer = append(m.Answer, aaaa) // } // w.WriteMsg(m) // Debug("%s found in hosts file", Q.qname) // return // } else { // Debug("%s didn't found in hosts file", Q.qname) // } //} //// Only query cache when qtype == 'A'|'AAAA' , qclass == 'IN' //key := KeyGen(Q) //if IPQuery > 0 { // mesg, err := h.cache.Get(key) // if err != nil { // if mesg, err = h.negCache.Get(key); err != nil { // Debug("%s didn't hit cache: %s", Q.String(), err) // } else { // Debug("%s hit negative cache", Q.String()) // dns.HandleFailed(w, req) // return // } // } else { // Debug("%s hit cache", Q.String()) // // we need this copy against concurrent modification of Id // msg := *mesg // msg.Id = req.Id // w.WriteMsg(&msg) // return // } //} //mesg, err := h.resolver.Lookup(Net, req) //if err != nil { // Debug("%s", err) // dns.HandleFailed(w, req) // // cache the failure, too! // if err = h.negCache.Set(key, nil); err != nil { // Debug("Set %s negative cache failed: %v", Q.String(), err) // } // return //} //w.WriteMsg(mesg) //if IPQuery > 0 && len(mesg.Answer) > 0 { // err = h.cache.Set(key, mesg) // if err != nil { // Debug("Set %s cache failed: %s", Q.String(), err.Error()) // } // Debug("Insert %s into cache", Q.String()) //} }
// 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 start := time.Now() q := req.Question[0] name := strings.ToLower(q.Name) if q.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) promErrorCount.WithLabelValues("refused").Inc() 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 tcp = isTCP(w); tcp { bufsize = dns.MaxMsgSize - 1 promRequestCount.WithLabelValues("tcp").Inc() } else { promRequestCount.WithLabelValues("udp").Inc() } StatsRequestCount.Inc(1) if dnssec { StatsDnssecOkCount.Inc(1) promDnssecOkCount.Inc() } if s.config.Verbose { logf("received DNS Request for %q from %q with type %d", q.Name, w.RemoteAddr(), q.Qtype) } // Check cache first. m1 := s.rcache.Hit(q, dnssec, tcp, m.Id) if m1 != nil { if tcp { if _, overflow := Fit(m1, dns.MaxMsgSize, tcp); overflow { promErrorCount.WithLabelValues("overflow").Inc() msgFail := new(dns.Msg) s.ServerFailure(msgFail, req) w.WriteMsg(msgFail) return } } else { // Overflow with udp always results in TC. Fit(m1, int(bufsize), tcp) if m1.Truncated { promErrorCount.WithLabelValues("truncated").Inc() } } // Still round-robin even with hits from the cache. // Only shuffle A and AAAA records with each other. if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { s.RoundRobin(m1.Answer) } if err := w.WriteMsg(m1); err != nil { logf("failure to return reply %q", err) } metricSizeAndDuration(m1, start, tcp) return } for zone, ns := range *s.config.stub { if strings.HasSuffix(name, zone) { resp := s.ServeDNSStubForward(w, req, ns) if resp != nil { s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) metricSizeAndDuration(resp, start, tcp) } return } } // If the qname is local.ds.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.") { resp := s.ServeDNSReverse(w, req) if resp != nil { s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) metricSizeAndDuration(resp, start, tcp) } return } if q.Qclass != dns.ClassCHAOS && !strings.HasSuffix(name, s.config.Domain) { resp := s.ServeDNSForward(w, req) if resp != nil { s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), resp) metricSizeAndDuration(resp, start, tcp) } return } promCacheMiss.WithLabelValues("response").Inc() defer func() { if m.Rcode == dns.RcodeServerFailure { if err := w.WriteMsg(m); err != nil { logf("failure to return reply %q", err) } return } // Set TTL to the minimum of the RRset and dedup the message, i.e. remove identical RRs. m = s.dedup(m) 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 } } if dnssec { if s.config.PubKey != nil { m.AuthenticatedData = true s.Denial(m) s.Sign(m, bufsize) } } if tcp { if _, overflow := Fit(m, dns.MaxMsgSize, tcp); overflow { msgFail := new(dns.Msg) s.ServerFailure(msgFail, req) w.WriteMsg(msgFail) return } } else { Fit(m, int(bufsize), tcp) if m.Truncated { promErrorCount.WithLabelValues("truncated").Inc() } } s.rcache.InsertMessage(cache.Key(q, dnssec, tcp), m) if err := w.WriteMsg(m); err != nil { logf("failure to return reply %q", err) } metricSizeAndDuration(m, start, tcp) }() 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 isEtcdNameError(err, s) { 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, bufsize, dnssec, false) if isEtcdNameError(err, s) { s.NameError(m, req) return } m.Answer = append(m.Answer, records...) case dns.TypeTXT: records, err := s.TXTRecords(q, name) if isEtcdNameError(err, s) { s.NameError(m, req) return } m.Answer = append(m.Answer, records...) case dns.TypeCNAME: records, err := s.CNAMERecords(q, name) if isEtcdNameError(err, s) { s.NameError(m, req) return } m.Answer = append(m.Answer, records...) case dns.TypeMX: records, extra, err := s.MXRecords(q, name, bufsize, dnssec) if isEtcdNameError(err, s) { s.NameError(m, 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) { s.NameError(m, req) return } logf("got error from backend: %s", err) if q.Qtype == dns.TypeSRV { // Otherwise NODATA s.ServerFailure(m, 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 StatsNoDataCount.Inc(1) m.Ns = []dns.RR{s.NewSOA()} m.Ns[0].Header().Ttl = s.config.MinTtl } }
func main() { short = flag.Bool("short", false, "abbreviate long DNSSEC records") dnssec := flag.Bool("dnssec", false, "request DNSSEC records") query := flag.Bool("question", false, "show question") check := flag.Bool("check", false, "check internal DNSSEC consistency") six := flag.Bool("6", false, "use IPv6 only") four := flag.Bool("4", false, "use IPv4 only") anchor := flag.String("anchor", "", "use the DNSKEY in this file as trust anchor") tsig := flag.String("tsig", "", "request tsig with key: [hmac:]name:key") port := flag.Int("port", 53, "port number to use") aa := flag.Bool("aa", false, "set AA flag in query") ad := flag.Bool("ad", false, "set AD flag in query") cd := flag.Bool("cd", false, "set CD flag in query") rd := flag.Bool("rd", true, "set RD flag in query") fallback := flag.Bool("fallback", false, "fallback to 4096 bytes bufsize and after that TCP") tcp := flag.Bool("tcp", false, "TCP mode, multiple queries are asked over the same connection") nsid := flag.Bool("nsid", false, "set edns nsid option") client := flag.String("client", "", "set edns client-subnet option") clientdraftcode := flag.Bool("clientdraft", false, "set edns client-subnet option using the draft option code") opcode := flag.String("opcode", "query", "set opcode to query|update|notify") rcode := flag.String("rcode", "success", "set rcode to noerror|formerr|nxdomain|servfail|...") //serial := flag.Int("serial", 0, "perform an IXFR with this serial") flag.Usage = func() { fmt.Fprintf(os.Stderr, "Usage: %s [options] [@server] [qtype...] [qclass...] [name ...]\n", os.Args[0]) flag.PrintDefaults() } var ( qtype []uint16 qclass []uint16 qname []string ) flag.Parse() if *anchor != "" { f, err := os.Open(*anchor) if err != nil { fmt.Fprintf(os.Stderr, "Failure to open %s: %s\n", *anchor, err.Error()) } r, err := dns.ReadRR(f, *anchor) if err != nil { fmt.Fprintf(os.Stderr, "Failure to read an RR from %s: %s\n", *anchor, err.Error()) } if k, ok := r.(*dns.DNSKEY); !ok { fmt.Fprintf(os.Stderr, "No DNSKEY read from %s\n", *anchor) } else { dnskey = k } } var nameserver string Flags: for i := 0; i < flag.NArg(); i++ { // If it starts with @ it is a nameserver if flag.Arg(i)[0] == '@' { nameserver = flag.Arg(i) continue Flags } // First class, then type, to make ANY queries possible // And if it looks like type, it is a type if k, ok := dns.StringToType[strings.ToUpper(flag.Arg(i))]; ok { qtype = append(qtype, k) continue Flags } // If it looks like a class, it is a class if k, ok := dns.StringToClass[strings.ToUpper(flag.Arg(i))]; ok { qclass = append(qclass, k) continue Flags } // If it starts with TYPExxx it is unknown rr if strings.HasPrefix(flag.Arg(i), "TYPE") { i, e := strconv.Atoi(string([]byte(flag.Arg(i))[4:])) if e == nil { qtype = append(qtype, uint16(i)) continue Flags } } // If it starts with CLASSxxx it is unknown class if strings.HasPrefix(flag.Arg(i), "CLASS") { i, e := strconv.Atoi(string([]byte(flag.Arg(i))[5:])) if e == nil { qclass = append(qclass, uint16(i)) continue Flags } } // Anything else is a qname qname = append(qname, flag.Arg(i)) } if len(qname) == 0 { qname = []string{"."} if len(qtype) == 0 { qtype = append(qtype, dns.TypeNS) } } if len(qtype) == 0 { qtype = append(qtype, dns.TypeA) } if len(qclass) == 0 { qclass = append(qclass, dns.ClassINET) } if len(nameserver) == 0 { conf, err := dns.ClientConfigFromFile("/etc/resolv.conf") if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } nameserver = "@" + conf.Servers[0] } nameserver = string([]byte(nameserver)[1:]) // chop off @ // if the nameserver is from /etc/resolv.conf the [ and ] are already // added, thereby breaking net.ParseIP. Check for this and don't // fully qualify such a name if nameserver[0] == '[' && nameserver[len(nameserver)-1] == ']' { nameserver = nameserver[1 : len(nameserver)-1] } if i := net.ParseIP(nameserver); i != nil { nameserver = net.JoinHostPort(nameserver, strconv.Itoa(*port)) } else { nameserver = dns.Fqdn(nameserver) + ":" + strconv.Itoa(*port) } c := new(dns.Client) t := new(dns.Transfer) c.Net = "udp" if *four { c.Net = "udp4" } if *six { c.Net = "udp6" } if *tcp { c.Net = "tcp" if *four { c.Net = "tcp4" } if *six { c.Net = "tcp6" } } m := new(dns.Msg) m.MsgHdr.Authoritative = *aa m.MsgHdr.AuthenticatedData = *ad m.MsgHdr.CheckingDisabled = *cd m.MsgHdr.RecursionDesired = *rd m.Question = make([]dns.Question, 1) m.Opcode = dns.OpcodeQuery if op, ok := dns.StringToOpcode[strings.ToUpper(*opcode)]; ok { m.Opcode = op } m.Rcode = dns.RcodeSuccess if rc, ok := dns.StringToRcode[strings.ToUpper(*rcode)]; ok { m.Rcode = rc } if *dnssec || *nsid || *client != "" { o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT if *dnssec { o.SetDo() o.SetUDPSize(dns.DefaultMsgSize) } if *nsid { e := new(dns.EDNS0_NSID) e.Code = dns.EDNS0NSID o.Option = append(o.Option, e) // NSD will not return nsid when the udp message size is too small o.SetUDPSize(dns.DefaultMsgSize) } if *client != "" { e := new(dns.EDNS0_SUBNET) e.Code = dns.EDNS0SUBNET if *clientdraftcode { e.DraftOption = true } e.SourceScope = 0 e.Address = net.ParseIP(*client) if e.Address == nil { fmt.Fprintf(os.Stderr, "Failure to parse IP address: %s\n", *client) return } e.Family = 1 // IP4 e.SourceNetmask = net.IPv4len * 8 if e.Address.To4() == nil { e.Family = 2 // IP6 e.SourceNetmask = net.IPv6len * 8 } o.Option = append(o.Option, e) } m.Extra = append(m.Extra, o) } if *tcp { co := new(dns.Conn) tcp := "tcp" if *six { tcp = "tcp6" } var err error if co.Conn, err = net.DialTimeout(tcp, nameserver, 2*time.Second); err != nil { fmt.Fprintf(os.Stderr, "Dialing "+nameserver+" failed: "+err.Error()+"\n") return } defer co.Close() qt := dns.TypeA qc := uint16(dns.ClassINET) for i, v := range qname { if i < len(qtype) { qt = qtype[i] } if i < len(qclass) { qc = qclass[i] } m.Question[0] = dns.Question{dns.Fqdn(v), qt, qc} m.Id = dns.Id() if *tsig != "" { if algo, name, secret, ok := tsigKeyParse(*tsig); ok { m.SetTsig(name, algo, 300, time.Now().Unix()) c.TsigSecret = map[string]string{name: secret} t.TsigSecret = map[string]string{name: secret} } else { fmt.Fprintf(os.Stderr, ";; TSIG key data error\n") continue } } co.SetReadDeadline(time.Now().Add(2 * time.Second)) co.SetWriteDeadline(time.Now().Add(2 * time.Second)) if *query { fmt.Printf("%s", m.String()) fmt.Printf("\n;; size: %d bytes\n\n", m.Len()) } then := time.Now() if e := co.WriteMsg(m); e != nil { fmt.Fprintf(os.Stderr, ";; %s\n", e.Error()) continue } r, e := co.ReadMsg() if e != nil { fmt.Fprintf(os.Stderr, ";; %s\n", e.Error()) continue } rtt := time.Since(then) if r.Id != m.Id { fmt.Fprintf(os.Stderr, "Id mismatch\n") continue } if *check { sigCheck(r, nameserver, true) denialCheck(r) fmt.Println() } if *short { r = shortMsg(r) } fmt.Printf("%v", r) fmt.Printf("\n;; query time: %.3d µs, server: %s(%s), size: %d bytes\n", rtt/1e3, nameserver, tcp, r.Len()) } return } qt := dns.TypeA qc := uint16(dns.ClassINET) Query: for i, v := range qname { if i < len(qtype) { qt = qtype[i] } if i < len(qclass) { qc = qclass[i] } m.Question[0] = dns.Question{dns.Fqdn(v), qt, qc} m.Id = dns.Id() if *tsig != "" { if algo, name, secret, ok := tsigKeyParse(*tsig); ok { m.SetTsig(name, algo, 300, time.Now().Unix()) c.TsigSecret = map[string]string{name: secret} t.TsigSecret = map[string]string{name: secret} } else { fmt.Fprintf(os.Stderr, "TSIG key data error\n") continue } } if *query { fmt.Printf("%s", m.String()) fmt.Printf("\n;; size: %d bytes\n\n", m.Len()) } if qt == dns.TypeAXFR || qt == dns.TypeIXFR { env, err := t.In(m, nameserver) if err != nil { fmt.Printf(";; %s\n", err.Error()) continue } envelope := 0 record := 0 for e := range env { if e.Error != nil { fmt.Printf(";; %s\n", e.Error.Error()) continue Query } for _, r := range e.RR { fmt.Printf("%s\n", r) } record += len(e.RR) envelope++ } fmt.Printf("\n;; xfr size: %d records (envelopes %d)\n", record, envelope) continue } r, rtt, e := c.Exchange(m, nameserver) Redo: if e != nil { fmt.Printf(";; %s\n", e.Error()) continue } if r.Id != m.Id { fmt.Fprintf(os.Stderr, "Id mismatch\n") return } if r.MsgHdr.Truncated && *fallback { if !*dnssec { fmt.Printf(";; Truncated, trying %d bytes bufsize\n", dns.DefaultMsgSize) o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetUDPSize(dns.DefaultMsgSize) m.Extra = append(m.Extra, o) r, rtt, e = c.Exchange(m, nameserver) *dnssec = true goto Redo } else { // First EDNS, then TCP fmt.Printf(";; Truncated, trying TCP\n") c.Net = "tcp" r, rtt, e = c.Exchange(m, nameserver) goto Redo } } if r.MsgHdr.Truncated && !*fallback { fmt.Printf(";; Truncated\n") } if *check { sigCheck(r, nameserver, *tcp) denialCheck(r) fmt.Println() } if *short { r = shortMsg(r) } fmt.Printf("%v", r) fmt.Printf("\n;; query time: %.3d µs, server: %s(%s), size: %d bytes\n", rtt/1e3, nameserver, c.Net, r.Len()) } }
func (r *Resolver) resolve(question dns.Question, result *dns.Msg, servers, original []string, loopcount int) ([]dns.RR, error) { if len(servers) == 0 { if r.Debug { log.Println("No more servers to query...") } result.Rcode = dns.RcodeServerFailure return nil, nil } //infinite loop prevention if loopcount == 30 { if r.Debug { log.Println("Loop count exhausted") } result.Rcode = dns.RcodeServerFailure return nil, nil } loopcount++ //Pick a server randomly shuffle(servers) server := servers[0] + ":53" nservers := []string{} for i, s := range servers { if i != 0 { nservers = append(nservers, s) } } if r.Debug { log.Println(server) log.Println(nservers) } m := &dns.Msg{} m.SetQuestion(question.Name, question.Qtype) m.RecursionDesired = false res, err := r.exchange(m, server, original) if r.Debug { if err != nil { log.Println(res, err) } } if err != nil { if r.Debug { log.Println(err) } //Restart with remaining servers return r.resolve(question, result, nservers, original, loopcount) } //Check status... if res.Rcode != dns.RcodeSuccess { //Restart with remaining servers return r.resolve(question, result, nservers, original, loopcount) } answerfound := false var cname dns.Question //Check for answers for _, ans := range res.Answer { result.Answer = append(result.Answer, ans) if ans.Header().Rrtype == question.Qtype { answerfound = true } if ans.Header().Rrtype == dns.TypeCNAME { c, _ := ans.(*dns.CNAME) cname.Name = c.Target cname.Qtype = question.Qtype } } if answerfound { return nil, nil } if cname.Name != "" { if r.Debug { log.Println("CNAME", cname, cname.Name) } return r.resolve(cname, result, r.Roothints, r.Roothints, loopcount) } //OK no ans of target type.... or CNAME found... process NS... ns := make(map[string]string) for _, n := range res.Ns { nsrec, _ := n.(*dns.NS) if nsrec != nil { ns[nsrec.Ns] = "" } } //Try to populate ips from additional... for _, a := range res.Extra { extra, ok := a.(*dns.A) if ok { _, ok := ns[extra.Header().Name] if ok { ns[extra.Header().Name] = extra.A.String() } } } newservers := []string{} //Fill in the missing ips for k, ip := range ns { if ip == "" { nsmsg := &dns.Msg{} nsmsg.SetQuestion(k, dns.TypeA) //Lets cheat and ask a recursive... nsmsg.RecursionDesired = true nsres, err := r.exchange(nsmsg, "8.8.8.8:53", []string{"8.8.8.8"}) if err == nil { for _, ans := range nsres.Answer { arec, ok := ans.(*dns.A) if ok { newservers = append(newservers, arec.A.String()) } } } } else { newservers = append(newservers, ip) } } if r.Debug { log.Println(ns) log.Println(newservers) } if len(newservers) == 0 { //Restart return r.resolve(question, result, nservers, original, loopcount) //return nil, errors.New("No NS record") } return r.resolve(question, result, newservers, newservers, 0) return nil, nil }
func TestUDPDNSServer(t *testing.T) { setupForTest(t) const ( containerID = "foobar" successTestName = "test1.weave.local." failTestName = "test2.weave.local." nonLocalName = "weave.works." testAddr1 = "10.2.2.1" ) testCIDR1 := testAddr1 + "/24" InitDefaultLogging(true) var zone = NewZoneDb(DefaultLocalDomain) ip, _, _ := net.ParseCIDR(testCIDR1) zone.AddRecord(containerID, successTestName, ip) fallbackHandler := func(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) if len(req.Question) == 1 { q := req.Question[0] if q.Name == nonLocalName && q.Qtype == dns.TypeMX { m.Answer = make([]dns.RR, 1) m.Answer[0] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: "mail." + nonLocalName} } else if q.Name == nonLocalName && q.Qtype == dns.TypeANY { m.Answer = make([]dns.RR, 512/len("mailn."+nonLocalName)+1) for i := range m.Answer { m.Answer[i] = &dns.MX{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0}, Mx: fmt.Sprintf("mail%d.%s", i, nonLocalName)} } } else if q.Name == testRDNSnonlocal && q.Qtype == dns.TypePTR { m.Answer = make([]dns.RR, 1) m.Answer[0] = &dns.PTR{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, Ptr: "ns1.google.com."} } else if q.Name == testRDNSfail && q.Qtype == dns.TypePTR { m.Rcode = dns.RcodeNameError } } w.WriteMsg(m) } // Run another DNS server for fallback s, fallbackAddr, err := runLocalUDPServer(t, "127.0.0.1:0", fallbackHandler) wt.AssertNoErr(t, err) defer s.Shutdown() _, fallbackPort, err := net.SplitHostPort(fallbackAddr) wt.AssertNoErr(t, err) config := &dns.ClientConfig{Servers: []string{"127.0.0.1"}, Port: fallbackPort} srv, err := NewDNSServer(DNSServerConfig{UpstreamCfg: config, Port: testPort}, zone, nil) wt.AssertNoErr(t, err) defer srv.Stop() go srv.Start() time.Sleep(100 * time.Millisecond) // Allow sever goroutine to start var r *dns.Msg r = assertExchange(t, successTestName, dns.TypeA, 1, 1, 0) wt.AssertType(t, r.Answer[0], (*dns.A)(nil), "DNS record") wt.AssertEqualString(t, r.Answer[0].(*dns.A).A.String(), testAddr1, "IP address") assertExchange(t, failTestName, dns.TypeA, 0, 0, dns.RcodeNameError) r = assertExchange(t, testRDNSsuccess, dns.TypePTR, 1, 1, 0) wt.AssertType(t, r.Answer[0], (*dns.PTR)(nil), "DNS record") wt.AssertEqualString(t, r.Answer[0].(*dns.PTR).Ptr, successTestName, "IP address") assertExchange(t, testRDNSfail, dns.TypePTR, 0, 0, dns.RcodeNameError) // This should fail because we don't have information about MX records assertExchange(t, successTestName, dns.TypeMX, 0, 0, dns.RcodeNameError) // This non-local query for an MX record should succeed by being // passed on to the fallback server assertExchange(t, nonLocalName, dns.TypeMX, 1, -1, 0) // Now ask a query that we expect to return a lot of data. assertExchange(t, nonLocalName, dns.TypeANY, 5, -1, 0) assertExchange(t, testRDNSnonlocal, dns.TypePTR, 1, -1, 0) // Not testing MDNS functionality of server here (yet), since it // needs two servers, each listening on its own address }