func makeTruncatedReply(r *dns.Msg) *dns.Msg { // for truncated response, we create a minimal reply with the Truncated bit set reply := new(dns.Msg) reply.SetReply(r) reply.Truncated = true return reply }
// truncate removes answers until the given dns.Msg fits the permitted // length of the given transmission channel and sets the TC bit. // See https://tools.ietf.org/html/rfc1035#section-4.2.1 func truncate(m *dns.Msg, udp bool) *dns.Msg { max := dns.MinMsgSize if !udp { max = dns.MaxMsgSize } else if opt := m.IsEdns0(); opt != nil { max = int(opt.UDPSize()) } m.Truncated = m.Len() > max if !m.Truncated { return m } m.Extra = nil // Drop all extra records first if m.Len() < max { return m } answers := m.Answer[:] left, right := 0, len(m.Answer) for { if left == right { break } mid := (left + right) / 2 m.Answer = answers[:mid] if m.Len() < max { left = mid + 1 continue } right = mid } return m }
func returnTruncated(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) m.Answer = make([]dns.RR, 1) m.Answer[0] = &dns.A{Hdr: dns.RR_Header{Name: m.Question[0].Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0}, A: net.ParseIP("127.0.0.1").To4()} m.Truncated = true w.WriteMsg(m) }
func (w *truncatingWriter) WriteMsg(m *dns.Msg) error { _, isUDP := w.RemoteAddr().(*net.UDPAddr) if isUDP && len(m.Answer) > w.maxAnswers { m.Answer = m.Answer[:w.maxAnswers] m.Truncated = true } return w.ResponseWriter.WriteMsg(m) }
func cacheMsg(m *dns.Msg, tc cacheTestCase) *dns.Msg { m.RecursionAvailable = tc.RecursionAvailable m.AuthenticatedData = tc.AuthenticatedData m.Authoritative = tc.Authoritative m.Truncated = tc.Truncated m.Answer = tc.in.Answer m.Ns = tc.in.Ns // m.Extra = tc.in.Extra , not the OPT record! return m }
// 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 }
func sendTruncated(w dns.ResponseWriter, msgHdr dns.MsgHdr) { emptyResp := new(dns.Msg) emptyResp.MsgHdr = msgHdr emptyResp.Response = true if _, isTCP := w.RemoteAddr().(*net.TCPAddr); isTCP { dns.HandleFailed(w, emptyResp) return } emptyResp.Truncated = true w.WriteMsg(emptyResp) }
func truncateResp(resp *dns.Msg, maxSize int, isTCP bool) { if !isTCP { resp.Truncated = true } // trim the Answer RRs one by one till the whole message fits // within the reply size for resp.Len() > maxSize { resp.Answer = resp.Answer[:len(resp.Answer)-1] } }
func Respond(w dns.ResponseWriter, req *dns.Msg, records []dns.RR) { m := new(dns.Msg) m.SetReply(req) m.Authoritative = true m.RecursionAvailable = true m.Compress = true m.Answer = records // Figure out the max response size bufsize := uint16(512) tcp := isTcp(w) if o := req.IsEdns0(); o != nil { bufsize = o.UDPSize() } if tcp { bufsize = dns.MaxMsgSize - 1 } else if bufsize < 512 { bufsize = 512 } if m.Len() > dns.MaxMsgSize { fqdn := dns.Fqdn(req.Question[0].Name) log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response too big, dropping Extra") m.Extra = nil if m.Len() > dns.MaxMsgSize { log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response still too big") m := new(dns.Msg) m.SetRcode(m, dns.RcodeServerFailure) } } if m.Len() > int(bufsize) && !tcp { log.Debug("Too big 1") m.Extra = nil if m.Len() > int(bufsize) { log.Debug("Too big 2") m.Answer = nil m.Truncated = true } } err := w.WriteMsg(m) if err != nil { log.Warn("Failed to return reply: ", err, m.Len()) } }
func truncateResp(resp *dns.Msg, maxSize int, isTCP bool) { if !isTCP { resp.Truncated = true } srv := resp.Question[0].Qtype == dns.TypeSRV // trim the Answer RRs one by one till the whole message fits // within the reply size for resp.Len() > maxSize { resp.Answer = resp.Answer[:len(resp.Answer)-1] if srv && len(resp.Extra) > 0 { resp.Extra = resp.Extra[:len(resp.Extra)-1] } } }
// Scrub scrubs the reply message so that it will fit the client's buffer. If even after dropping // the additional section, it still does not fit the TC bit will be set on the message. Note, // the TC bit will be set regardless of protocol, even TCP message will get the bit, the client // should then retry with pigeons. // TODO(referral). func (s *State) Scrub(reply *dns.Msg) (*dns.Msg, Result) { size := s.Size() l := reply.Len() if size >= l { return reply, ScrubIgnored } // TODO(miek): check for delegation // If not delegation, drop additional section. reply.Extra = nil s.SizeAndDo(reply) l = reply.Len() if size >= l { return reply, ScrubDone } // Still?!! does not fit. reply.Truncated = true return reply, ScrubDone }
// Fit will make m fit the size. If a message is larger than size then entire // additional section is dropped. If it is still to large and the transport // is udp we return a truncated message. // If the transport is tcp we are going to drop RR from the answer section // until it fits. When this is case the returned bool is true. func Fit(m *dns.Msg, size int, tcp bool) (*dns.Msg, bool) { if m.Len() > size { // Check for OPT Records at the end and keep those. TODO(miek) m.Extra = nil } if m.Len() < size { return m, false } // With TCP setting TC does not mean anything. if !tcp { m.Truncated = true // fall through here, so we at least return a message that can // fit the udp buffer. } // Additional section is gone, binary search until we have length that fits. min, max := 0, len(m.Answer) original := make([]dns.RR, len(m.Answer)) copy(original, m.Answer) for { if min == max { break } mid := (min + max) / 2 m.Answer = original[:mid] if m.Len() < size { min++ continue } max = mid } if max > 1 { max-- } m.Answer = m.Answer[:max] return m, true }
// 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 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 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 { continue } if sig, err := s.signSet(r, now, incep, expir); err == nil { m.Extra = append(m.Extra, sig) } } if bufsize >= 512 || bufsize <= 4096 { 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 }
func Respond(w dns.ResponseWriter, req *dns.Msg, m *dns.Msg) { // Figure out the max response size bufsize := uint16(512) tcp := isTcp(w) if o := req.IsEdns0(); o != nil { bufsize = o.UDPSize() } if tcp { bufsize = dns.MaxMsgSize - 1 } else if bufsize < 512 { bufsize = 512 } // Make sure the payload fits the buffer size. If the message is too large we strip the Extra section. // If it's still too large we return a truncated message for UDP queries and ServerFailure for TCP queries. if m.Len() > int(bufsize) { fqdn := dns.Fqdn(req.Question[0].Name) log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response too big, dropping Authority and Extra") m.Extra = nil if m.Len() > int(bufsize) { if tcp { log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response still too big, return ServerFailure") m = new(dns.Msg) m.SetRcode(req, dns.RcodeServerFailure) } else { log.WithFields(log.Fields{"fqdn": fqdn}).Debug("Response still too big, return truncated message") m.Answer = nil m.Truncated = true } } } err := w.WriteMsg(m) if err != nil { log.Warn("Failed to return reply: ", err, m.Len()) } }
func truncateResponse(response *dns.Msg, maxSize int) { if len(response.Answer) <= 1 || maxSize <= 0 { return } // take a copy of answers, as we're going to mutate response answers := response.Answer // search for smallest i that is too big i := sort.Search(len(response.Answer), func(i int) bool { // return true if too big response.Answer = answers[:i+1] return response.Len() > maxSize }) if i == len(answers) { response.Answer = answers return } response.Answer = answers[:i] response.Truncated = true }
// preparedQueryLookup is used to handle a prepared query. func (d *DNSServer) preparedQueryLookup(network, datacenter, query string, req, resp *dns.Msg) { // Execute the prepared query. args := structs.PreparedQueryExecuteRequest{ Datacenter: datacenter, QueryIDOrName: query, QueryOptions: structs.QueryOptions{ Token: d.agent.config.ACLToken, AllowStale: *d.config.AllowStale, }, // Always pass the local agent through. In the DNS interface, there // is no provision for passing additional query parameters, so we // send the local agent's data through to allow distance sorting // relative to ourself on the server side. Agent: structs.QuerySource{ Datacenter: d.agent.config.Datacenter, Node: d.agent.config.NodeName, }, } // TODO (slackpad) - What's a safe limit we can set here? It seems like // with dup filtering done at this level we need to get everything to // match the previous behavior. We can optimize by pushing more filtering // into the query execution, but for now I think we need to get the full // response. We could also choose a large arbitrary number that will // likely work in practice, like 10*maxUDPAnswerLimit which should help // reduce bandwidth if there are thousands of nodes available. endpoint := d.agent.getEndpoint(preparedQueryEndpoint) var out structs.PreparedQueryExecuteResponse RPC: if err := d.agent.RPC(endpoint+".Execute", &args, &out); err != nil { // If they give a bogus query name, treat that as a name error, // not a full on server error. We have to use a string compare // here since the RPC layer loses the type information. if err.Error() == consul.ErrQueryNotFound.Error() { d.addSOA(d.domain, resp) resp.SetRcode(req, dns.RcodeNameError) return } d.logger.Printf("[ERR] dns: rpc error: %v", err) resp.SetRcode(req, dns.RcodeServerFailure) return } // Verify that request is not too stale, redo the request. if args.AllowStale && out.LastContact > d.config.MaxStale { args.AllowStale = false d.logger.Printf("[WARN] dns: Query results too stale, re-requesting") goto RPC } // Determine the TTL. The parse should never fail since we vet it when // the query is created, but we check anyway. If the query didn't // specify a TTL then we will try to use the agent's service-specific // TTL configs. var ttl time.Duration if out.DNS.TTL != "" { var err error ttl, err = time.ParseDuration(out.DNS.TTL) if err != nil { d.logger.Printf("[WARN] dns: Failed to parse TTL '%s' for prepared query '%s', ignoring", out.DNS.TTL, query) } } else if d.config.ServiceTTL != nil { var ok bool ttl, ok = d.config.ServiceTTL[out.Service] if !ok { ttl = d.config.ServiceTTL["*"] } } // If we have no nodes, return not found! if len(out.Nodes) == 0 { d.addSOA(d.domain, resp) resp.SetRcode(req, dns.RcodeNameError) return } // Add various responses depending on the request. qType := req.Question[0].Qtype if qType == dns.TypeSRV { d.serviceSRVRecords(out.Datacenter, out.Nodes, req, resp, ttl) } else { d.serviceNodeRecords(out.Datacenter, out.Nodes, req, resp, ttl) } // If the network is not TCP, restrict the number of responses. if network != "tcp" { wasTrimmed := trimUDPResponse(d.config, resp) // Flag that there are more records to return in the UDP response if wasTrimmed && d.config.EnableTruncate { resp.Truncated = true } } // If the answer is empty and the response isn't truncated, return not found if len(resp.Answer) == 0 && !resp.Truncated { d.addSOA(d.domain, resp) return } }
func handleReflect(w dns.ResponseWriter, r *dns.Msg) { reflectHandled += 1 if reflectHandled%1000 == 0 { fmt.Printf("Served %d reflections\n", reflectHandled) } var ( v4 bool rr dns.RR str string a net.IP ) m := new(dns.Msg) m.SetReply(r) m.Compress = *compress if ip, ok := w.RemoteAddr().(*net.UDPAddr); ok { str = "Port: " + strconv.Itoa(ip.Port) + " (udp)" a = ip.IP v4 = a.To4() != nil } if ip, ok := w.RemoteAddr().(*net.TCPAddr); ok { str = "Port: " + strconv.Itoa(ip.Port) + " (tcp)" a = ip.IP v4 = a.To4() != nil } if v4 { rr = new(dns.A) rr.(*dns.A).Hdr = dns.RR_Header{Name: dom, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} rr.(*dns.A).A = a.To4() } else { rr = new(dns.AAAA) rr.(*dns.AAAA).Hdr = dns.RR_Header{Name: dom, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0} rr.(*dns.AAAA).AAAA = a } t := new(dns.TXT) t.Hdr = dns.RR_Header{Name: dom, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0} t.Txt = []string{str} switch r.Question[0].Qtype { case dns.TypeTXT: m.Answer = append(m.Answer, t) m.Extra = append(m.Extra, rr) default: fallthrough case dns.TypeAAAA, dns.TypeA: m.Answer = append(m.Answer, rr) m.Extra = append(m.Extra, t) case dns.TypeAXFR, dns.TypeIXFR: c := make(chan *dns.Envelope) tr := new(dns.Transfer) defer close(c) err := tr.Out(w, r, c) if err != nil { return } soa, _ := dns.NewRR(`whoami.miek.nl. 0 IN SOA linode.atoom.net. miek.miek.nl. 2009032802 21600 7200 604800 3600`) c <- &dns.Envelope{RR: []dns.RR{soa, t, rr, soa}} w.Hijack() // w.Close() // Client closes connection return } if r.IsTsig() != nil { if w.TsigStatus() == nil { m.SetTsig(r.Extra[len(r.Extra)-1].(*dns.TSIG).Hdr.Name, dns.HmacMD5, 300, time.Now().Unix()) } else { println("Status", w.TsigStatus().Error()) } } if *printf { fmt.Printf("%v\n", m.String()) } // set TC when question is tc.miek.nl. if m.Question[0].Name == "tc.miek.nl." { m.Truncated = true // send half a message buf, _ := m.Pack() w.Write(buf[:len(buf)/2]) return } w.WriteMsg(m) }
// 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 } }
// serviceLookup is used to handle a service query func (d *DNSServer) serviceLookup(network, datacenter, service, tag string, req, resp *dns.Msg) { // Make an RPC request args := structs.ServiceSpecificRequest{ Datacenter: datacenter, ServiceName: service, ServiceTag: tag, TagFilter: tag != "", QueryOptions: structs.QueryOptions{ Token: d.agent.config.ACLToken, AllowStale: d.config.AllowStale, }, } var out structs.IndexedCheckServiceNodes RPC: if err := d.agent.RPC("Health.ServiceNodes", &args, &out); err != nil { d.logger.Printf("[ERR] dns: rpc error: %v", err) resp.SetRcode(req, dns.RcodeServerFailure) return } // Verify that request is not too stale, redo the request if args.AllowStale && out.LastContact > d.config.MaxStale { args.AllowStale = false d.logger.Printf("[WARN] dns: Query results too stale, re-requesting") goto RPC } // If we have no nodes, return not found! if len(out.Nodes) == 0 { d.addSOA(d.domain, resp) resp.SetRcode(req, dns.RcodeNameError) return } // Determine the TTL var ttl time.Duration if d.config.ServiceTTL != nil { var ok bool ttl, ok = d.config.ServiceTTL[service] if !ok { ttl = d.config.ServiceTTL["*"] } } // Filter out any service nodes due to health checks out.Nodes = d.filterServiceNodes(out.Nodes) // Perform a random shuffle shuffleServiceNodes(out.Nodes) // If the network is not TCP, restrict the number of responses if network != "tcp" && len(out.Nodes) > maxServiceResponses { out.Nodes = out.Nodes[:maxServiceResponses] // Flag that there are more records to return in the UDP response if d.config.EnableTruncate { resp.Truncated = true } } // Add various responses depending on the request qType := req.Question[0].Qtype d.serviceNodeRecords(out.Nodes, req, resp, ttl) if qType == dns.TypeSRV { d.serviceSRVRecords(datacenter, out.Nodes, req, resp, ttl) } }
// 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. func (s *server) sign(m *dns.Msg, bufsize uint16) { now := time.Now().UTC() incep := uint32(now.Add(-2 * time.Hour).Unix()) // 2 hours, be sure to catch daylight saving time and such expir := uint32(now.Add(7 * 24 * time.Hour).Unix()) // sign for a week // TODO(miek): repeating this two times? for _, r := range rrSets(m.Answer) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } key := cache.key(r) if s := cache.search(key); s != nil { if s.ValidityPeriod(now.Add(-24 * time.Hour)) { m.Answer = append(m.Answer, s) continue } cache.remove(key) } sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { sig1 := s.NewRRSIG(incep, expir) if r[0].Header().Rrtype == dns.TypeNSEC3 { sig1.OrigTtl = s.config.MinTtl sig1.Header().Ttl = s.config.MinTtl } e := sig1.Sign(s.config.PrivKey, r) if e != nil { log.Printf("failed to sign: %s\n", e.Error()) } return sig1, e }) if err != nil { continue } if !shared { // is it possible to miss this, due the the c.dups > 0 in Do()? TODO(miek) cache.insert(key, sig) } m.Answer = append(m.Answer, dns.Copy(sig).(*dns.RRSIG)) } for _, r := range rrSets(m.Ns) { if r[0].Header().Rrtype == dns.TypeRRSIG { continue } key := cache.key(r) if s := cache.search(key); s != nil { if s.ValidityPeriod(now.Add(-24 * time.Hour)) { m.Ns = append(m.Ns, s) continue } cache.remove(key) } sig, err, shared := inflight.Do(key, func() (*dns.RRSIG, error) { sig1 := s.NewRRSIG(incep, expir) if r[0].Header().Rrtype == dns.TypeNSEC3 { sig1.OrigTtl = s.config.MinTtl sig1.Header().Ttl = s.config.MinTtl } e := sig1.Sign(s.config.PrivKey, r) if e != nil { log.Printf("failed to sign: %s\n", e.Error()) } return sig1, e }) if err != nil { continue } if !shared { // is it possible to miss this, due the the c.dups > 0 in Do()? TODO(miek) cache.insert(key, sig) } m.Ns = append(m.Ns, dns.Copy(sig).(*dns.RRSIG)) } // TODO(miek): Forget the additional section for now if bufsize >= 512 || bufsize <= 4096 { m.Truncated = m.Len() > int(bufsize) } o := new(dns.OPT) o.Hdr.Name = "." o.Hdr.Rrtype = dns.TypeOPT o.SetDo() o.SetUDPSize(4096) m.Extra = append(m.Extra, o) return }
// truncate sets the TC bit in the given dns.Msg if its length exceeds the // permitted length of the given transmission channel. // See https://tools.ietf.org/html/rfc1035#section-4.2.1 func truncate(m *dns.Msg, udp bool) *dns.Msg { m.Truncated = udp && m.Len() > dns.MinMsgSize return m }
// 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 } }