// Handles requests with questions for records of type `AAAA`. // If IPv6 routing is enabled, the request will undergo the same // treatment as `A` types in `handleV4Hijack()`; otherwise an empty // packet is sent back as a reply. func handleV6Hijack(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { var m *dns.Msg // if an IPv6 gateway address was specified, AAAA records will be forwarded // and processed; otherwise a fake reply is sent back telling the user agent // that there are no IPv6 addresses present at this address if routev6 { // forward request to the specified nameservers m = getServerReply(w, req) // iterate through the answers in the reply and handle based on type for _, ans := range m.Answer { if ans.Header().Rrtype == dns.TypeAAAA { // add IP address to local list and then routing table ip := ans.(*dns.AAAA).AAAA.String() routedv6[ip] = struct{}{} log.Print("Re-routing ", ip, " for ", ans.Header().Name, "/", dns.Type(ans.Header().Rrtype).String()) if runtime.GOOS == "windows" { runAndLog(router, "add", ip+"/128", *gateway6) } else { runAndLog(router, "add", ip+"/128", "gw", *gateway6) } } else if ans.Header().Rrtype == dns.TypeA { // sanity check for now, shouldn't happen afaik log.Print("WARNING: A response in ", ans.Header().Name, "/AAAA") } } } else { if *verbose { log.Print("Hijacking ", req.Question[0].Name, "/", dns.Type(req.Question[0].Qtype).String()) } // create new empty reply packet with no errors indicated // if `RCODE` is set to `NXDOMAIN` instead of the current empty packet method, // some web browsers (e.g. Chrome) will display an error message stating // `DNS_PROBE_FINISHED_NXDOMAIN`, even though a request for `A` records types is // sent by the user agent and properly replied to (with addresses) by the server m = getEmptyMsg(w, req, 0) } return m }
// Forwards a DNS request to the specified nameservers. On success, the // original reply packet will be returned to the caller. On failure, a // new packet will be returned with `RCODE` set to `SERVFAIL`. // Even though the original `ResponseWriter` object is taken as an argument, // this function does not send a reply to the client. Instead, the // packet is returned for further processing by the caller. func getServerReply(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { if *verbose { log.Print("Forwarding ", req.Question[0].Name, "/", dns.Type(req.Question[0].Qtype).String()) } // create a new DNS client client := &dns.Client{Net: "udp", ReadTimeout: 4 * time.Second, WriteTimeout: 4 * time.Second, SingleInflight: true} if _, tcp := w.RemoteAddr().(*net.TCPAddr); tcp { client.Net = "tcp" } var r *dns.Msg var err error // loop through the specified nameservers and forward them the request // the request ID is used as a starting point in order to introduce at least // some element of randomness, instead of always hitting the first nameserver for i := 0; i < len(nameservers); i++ { r, _, err = client.Exchange(req, nameservers[(int(req.Id)+i)%len(nameservers)]) if err == nil { r.Compress = true return r } } log.Print("Failed to forward request.", err) return getEmptyMsg(w, req, dns.RcodeServerFailure) }
func TestLookup(t *testing.T) { for _, serv := range services { set(t, etc, serv.Key, 0, serv) defer delete(t, etc, serv.Key) } for _, tc := range dnsTestCases { m := tc.Msg() rec := middleware.NewResponseRecorder(&test.ResponseWriter{}) _, err := etc.ServeDNS(ctxt, rec, m) if err != nil { t.Errorf("expected no error, got: %v for %s %s\n", err, m.Question[0].Name, dns.Type(m.Question[0].Qtype)) return } resp := rec.Msg() sort.Sort(test.RRSet(resp.Answer)) sort.Sort(test.RRSet(resp.Ns)) sort.Sort(test.RRSet(resp.Extra)) if !test.Header(t, tc, resp) { t.Logf("%v\n", resp) continue } if !test.Section(t, tc, test.Answer, resp.Answer) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Ns, resp.Ns) { t.Logf("%v\n", resp) } if !test.Section(t, tc, test.Extra, resp.Extra) { t.Logf("%v\n", resp) } } }
// Handles requests with questions for records of type `A`. // Forwards the request to the specified nameservers and returns the // result to the caller to be sent back to the client. However, before // a reply is sent back, the IP addresses found within the response // packet are added to the routing table. func handleV4Hijack(w dns.ResponseWriter, req *dns.Msg) *dns.Msg { // forward request to the specified nameservers m := getServerReply(w, req) // iterate through the answers in the reply and handle based on type for _, ans := range m.Answer { if ans.Header().Rrtype == dns.TypeA { // add IP address to local list and then routing table ip := ans.(*dns.A).A.String() routedv4[ip] = struct{}{} log.Print("Re-routing ", ip, " for ", ans.Header().Name, "/", dns.Type(ans.Header().Rrtype).String()) if runtime.GOOS == "windows" { runAndLog(router, "add", ip+"/32", *gateway4) } else { runAndLog(router, "add", ip+"/32", "gw", *gateway4) } } else if ans.Header().Rrtype == dns.TypeAAAA { // sanity check for now, shouldn't happen afaik log.Print("WARNING: AAAA response in ", ans.Header().Name, "/A") } } return m }
func dnsQueryServe(cfg *Config, cache *dnscache.Cache, w dns.ResponseWriter, req *dns.Msg) { start := time.Now() if req.MsgHdr.Response == true { // supposed responses sent to us are bogus q := req.Question[0] log.Printf("DNS Query IS BOGUS %s %s from %s.\n", q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr()) return } // TODO: handle AXFR/IXFR (full and incremental) *someday* for use by non-netcore slaves // ... also if we do that, also handle sending NOTIFY to listed slaves attached to the SOA record // Process questions in parallel pending := make([]chan []dns.RR, 0, len(req.Question)) // Slice of answer channels for i := range req.Question { q := &req.Question[i] log.Printf("DNS Query [%d/%d] %s %s from %s\n", i+1, len(req.Question), q.Name, dns.Type(q.Qtype).String(), w.RemoteAddr()) pending = append(pending, serveQuestion(cfg, cache, q, start)) } // Assemble answers according to the order of the questions var answers []dns.RR for _, ch := range pending { answers = append(answers, <-ch...) } for _, answer := range answers { log.Printf(" [%9.04fms] ANSWER %s\n", msElapsed(start, time.Now()), answer.String()) } if len(answers) > 0 { //log.Printf("OUR DATA: [%+v]\n", answerMsg) answerMsg := prepareAnswerMsg(req, answers) w.WriteMsg(answerMsg) return } //log.Printf("NO DATA: [%+v]\n", answerMsg) failMsg := prepareFailureMsg(req) w.WriteMsg(failMsg) }
func fetchEntry(cfg *Config, q *dns.Question, rrType uint16) chan dnsEntryResult { out := make(chan dnsEntryResult) go func() { entry, err := cfg.db.GetDNS(q.Name, dns.Type(rrType).String()) out <- dnsEntryResult{ Entry: entry, RType: rrType, Err: err, } }() return out }
func isForeign(r *dns.Msg) (foreign bool) { foreign = true check_ip: for _, answer := range r.Answer { if "A" == dns.Type(answer.Header().Rrtype).String() { for _, ipNet := range netRange { a := answer.(*dns.A).A if ipNet.Contains(a) { foreign = false break check_ip } } } } return }
// Create a dns answer with func (h *ENUMHandler) createAnswer(request *dns.Msg) (answer *dns.Msg, err error) { if len(request.Question) != 1 { err = errors.New("Received more than one question") return } question := request.Question[0] if question.Qtype != dns.TypeNAPTR { err = errors.New("Received an unsupported query type '" + dns.Type(question.Qtype).String() + "'") return } var number uint64 if number, err = extractE164FromName(question.Name); err != nil { return } var numberrange enum.NumberRange h.Trace.Printf("backend.RangesBetween(%d, %d, 1)", number, number) ranges, err := (*h.Backend).RangesBetween(number, number, 1) if err != nil || len(ranges) != 1 { return } numberrange = ranges[0] answer = h.answerForRequest(request) // Create and populate the NAPTR answers. for _, record := range numberrange.Records { naptr := new(dns.NAPTR) naptr.Hdr = dns.RR_Header{Name: question.Name, Rrtype: question.Qtype, Class: question.Qclass, Ttl: 0} naptr.Regexp = record.Regexp naptr.Preference = record.Preference naptr.Service = record.Service naptr.Flags = record.Flags naptr.Order = record.Order naptr.Replacement = record.Replacement answer.Answer = append(answer.Answer, naptr) } return }
// Convert some DNS records into a route53 ResourceRecordSet. The records should have been // previously grouped by matching name, type and (if applicable) identifier. func ConvertBindToRRSet(records []dns.RR) *route53.ResourceRecordSet { rrs := []*route53.ResourceRecord{} for _, record := range records { if rr, ok := record.(*dns.PrivateRR); ok { return ConvertAliasToRRSet(rr) } else if rec, ok := record.(*AWSRR); ok { rrs = append(rrs, ConvertBindToRR(rec.RR)...) } else { rrs = append(rrs, ConvertBindToRR(record)...) } } if len(rrs) > 0 { hdr := records[0].Header() rrset := &route53.ResourceRecordSet{ Type: aws.String(dns.Type(hdr.Rrtype).String()), Name: aws.String(hdr.Name), ResourceRecords: rrs, TTL: aws.Int64(int64(hdr.Ttl)), } if awsrr, ok := records[0].(*AWSRR); ok { switch route := awsrr.Route.(type) { case *FailoverRoute: rrset.Failover = aws.String(route.Failover) case *GeoLocationRoute: rrset.GeoLocation = &route53.GeoLocation{ CountryCode: route.CountryCode, ContinentCode: route.ContinentCode, SubdivisionCode: route.SubdivisionCode, } case *LatencyRoute: rrset.Region = aws.String(route.Region) case *WeightedRoute: rrset.Weight = aws.Int64(route.Weight) } if awsrr.HealthCheckId != nil { rrset.HealthCheckId = awsrr.HealthCheckId } rrset.SetIdentifier = aws.String(awsrr.Identifier) } return rrset } return nil }
// Report is a plain reporting function that the server can use for REFUSED and other // queries that are turned down because they don't match any middleware. func Report(state middleware.State, zone, rcode string, size int, start time.Time) { if requestCount == nil { // no metrics are enabled return } // Proto and Family net := state.Proto() fam := "1" if state.Family() == 2 { fam = "2" } typ := state.QType() requestCount.WithLabelValues(zone, net, fam).Inc() requestDuration.WithLabelValues(zone).Observe(float64(time.Since(start) / time.Millisecond)) if state.Do() { requestDo.WithLabelValues(zone).Inc() } if _, known := monitorType[typ]; known { requestType.WithLabelValues(zone, dns.Type(typ).String()).Inc() } else { requestType.WithLabelValues(zone, other).Inc() } if typ == dns.TypeIXFR || typ == dns.TypeAXFR { responseTransferSize.WithLabelValues(zone, net).Observe(float64(size)) requestTransferSize.WithLabelValues(zone, net).Observe(float64(state.Size())) } else { responseSize.WithLabelValues(zone, net).Observe(float64(size)) requestSize.WithLabelValues(zone, net).Observe(float64(state.Size())) } responseRcode.WithLabelValues(zone, rcode).Inc() }
func TestDNSResponse(t *testing.T) { const TestAddr = "127.0.0.1:9953" config := NewConfig() config.dnsAddr = TestAddr server := NewDNSServer(config) go server.Start() // Allow some time for server to start time.Sleep(250 * time.Millisecond) server.AddService("foo", Service{Name: "foo", Image: "bar", Ip: net.ParseIP("127.0.0.1")}) server.AddService("baz", Service{Name: "baz", Image: "bar", Ip: net.ParseIP("127.0.0.1"), Ttl: -1}) server.AddService("biz", Service{Name: "hey", Image: "", Ip: net.ParseIP("127.0.0.4")}) server.AddService("joe", Service{Name: "joe", Image: "", Ip: net.ParseIP("127.0.0.5"), Aliases: []string{"lala.docker", "super-alias", "alias.domain"}}) var inputs = []struct { query string expected int qType string rcode int }{ {"google.com.", -1, "A", dns.RcodeSuccess}, {"google.com.", -1, "MX", 0}, {"google.com.", -1, "AAAA", 0}, // google has AAAA records {"docker.", 5, "A", 0}, {"docker.", 5, "MX", 0}, {"*.docker.", 5, "A", 0}, {"*.docker.", 5, "MX", 0}, {"bar.docker.", 2, "A", 0}, {"bar.docker.", 2, "MX", 0}, {"bar.docker.", 0, "AAAA", 0}, {"foo.docker.", 0, "A", dns.RcodeNameError}, {"foo.docker.", 0, "MX", dns.RcodeNameError}, {"baz.bar.docker.", 1, "A", 0}, {"baz.bar.docker.", 1, "MX", 0}, {"joe.docker.", 1, "A", 0}, {"joe.docker.", 1, "MX", 0}, {"joe.docker.", 0, "AAAA", 0}, {"super-alias.", 1, "A", 0}, {"super-alias.", 1, "MX", 0}, {"alias.domain.", 1, "A", 0}, {"alias.domain.", 1, "MX", 0}, {"1.0.0.127.in-addr.arpa.", 4, "PTR", 0}, // two services match with two domains each {"5.0.0.127.in-addr.arpa.", 4, "PTR", 0}, // one service match with three aliases {"4.0.0.127.in-addr.arpa.", 1, "PTR", 0}, // only one service with a single domain {"2.0.0.127.in-addr.arpa.", 0, "PTR", dns.RcodeNameError}, // no match } c := new(dns.Client) for _, input := range inputs { t.Log("Query", input.query, input.qType) qType := dns.StringToType[input.qType] m := new(dns.Msg) m.SetQuestion(input.query, qType) r, _, err := c.Exchange(m, TestAddr) if err != nil { t.Error("Error response from the server", err) break } if len(r.Answer) == 0 { if _, ok := r.Ns[0].(*dns.SOA); !ok { t.Error(input, "No SOA answer") } } if input.expected > 0 && len(r.Answer) != input.expected { t.Error(input, "Expected:", input.expected, " Got:", len(r.Answer)) } if input.expected < 0 && len(r.Answer) == 0 { t.Error(input, "Expected at least one record but got none") } if r.Rcode != input.rcode { t.Error(input, "Rcode expected:", dns.RcodeToString[input.rcode], " got:", dns.RcodeToString[r.Rcode]) } for _, a := range r.Answer { rrType := dns.Type(a.Header().Rrtype).String() if input.qType != rrType { t.Error("Did not receive ", input.qType, " resource record") } else { t.Log("Received expected response RR type", rrType, "code", dns.RcodeToString[input.rcode]) } } } }
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) }
func rrHeaderFmt(hdr dns.RR_Header) string { return fmt.Sprintf("'%s' %s ttl: %d", hdr.Name, dns.Type(hdr.Rrtype).String(), hdr.Ttl) }
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) }
func textConvertMessage(m *Message, s *bytes.Buffer) { isQuery := false printQueryAddress := false switch *m.Type { case Message_CLIENT_QUERY, Message_RESOLVER_QUERY, Message_AUTH_QUERY, Message_FORWARDER_QUERY, Message_TOOL_QUERY: isQuery = true case Message_CLIENT_RESPONSE, Message_RESOLVER_RESPONSE, Message_AUTH_RESPONSE, Message_FORWARDER_RESPONSE, Message_TOOL_RESPONSE: isQuery = false default: s.WriteString("[unhandled Message.Type]\n") return } if isQuery { textConvertTime(s, m.QueryTimeSec, m.QueryTimeNsec) } else { textConvertTime(s, m.ResponseTimeSec, m.ResponseTimeNsec) } s.WriteString(" ") switch *m.Type { case Message_CLIENT_QUERY, Message_CLIENT_RESPONSE: { s.WriteString("C") } case Message_RESOLVER_QUERY, Message_RESOLVER_RESPONSE: { s.WriteString("R") } case Message_AUTH_QUERY, Message_AUTH_RESPONSE: { s.WriteString("A") } case Message_FORWARDER_QUERY, Message_FORWARDER_RESPONSE: { s.WriteString("F") } case Message_STUB_QUERY, Message_STUB_RESPONSE: { s.WriteString("S") } case Message_TOOL_QUERY, Message_TOOL_RESPONSE: { s.WriteString("T") } } if isQuery { s.WriteString("Q ") } else { s.WriteString("R ") } switch *m.Type { case Message_CLIENT_QUERY, Message_CLIENT_RESPONSE, Message_AUTH_QUERY, Message_AUTH_RESPONSE: printQueryAddress = true } if printQueryAddress { textConvertIP(s, m.QueryAddress) } else { textConvertIP(s, m.ResponseAddress) } s.WriteString(" ") if m.SocketProtocol != nil { s.WriteString(m.SocketProtocol.String()) } s.WriteString(" ") var err error msg := new(dns.Msg) if isQuery { s.WriteString(strconv.Itoa(len(m.QueryMessage))) s.WriteString("b ") err = msg.Unpack(m.QueryMessage) } else { s.WriteString(strconv.Itoa(len(m.ResponseMessage))) s.WriteString("b ") err = msg.Unpack(m.ResponseMessage) } if err != nil { s.WriteString("X ") } else { s.WriteString("\"" + msg.Question[0].Name + "\" ") s.WriteString(dns.Class(msg.Question[0].Qclass).String() + " ") s.WriteString(dns.Type(msg.Question[0].Qtype).String()) } s.WriteString("\n") }
// Type returns the type of the question as a string. func (s *State) Type() string { return dns.Type(s.Req.Question[0].Qtype).String() }
func answerQuestion(cfg *Config, c dnscache.Context, q *dns.Question, defaultTTL, qDepth uint32) []dns.RR { if c.Event == dnscache.Renewal && qDepth == 0 { log.Printf("DNS Renewal %s %s\n", q.Name, dns.Type(q.Qtype).String()) } else { log.Printf(" [%9.04fms] %-7s %s %s\n", msElapsed(c.Start, time.Now()), strings.ToUpper(c.Event.String()), q.Name, dns.Type(q.Qtype).String()) } answerTTL := defaultTTL var answers []dns.RR var secondaryAnswers []dns.RR var wouldLikeForwarder = true entry, rrType, err := fetchBestEntry(cfg, q) if err == nil { wouldLikeForwarder = false if entry.TTL > 0 { answerTTL = entry.TTL } log.Printf(" [%9.04fms] FOUND %s %s\n", msElapsed(c.Start, time.Now()), q.Name, dns.Type(rrType).String()) switch q.Qtype { case dns.TypeSOA: answer := answerSOA(q, entry) answers = append(answers, answer) default: // ... for answers that have values for i := range entry.Values { value := &entry.Values[i] if value.Expiration != nil { expiration := value.Expiration.Unix() now := time.Now().Unix() if expiration < now { //log.Printf("[Lookup [%s] [%s] (is expired)]\n", q.Name, qType) continue } remaining := uint32(expiration - now) if remaining < answerTTL { answerTTL = remaining log.Printf(" [%9.04fms] EXPIRES %d\n", msElapsed(c.Start, time.Now()), remaining) } } if value.TTL > 0 && value.TTL < answerTTL { answerTTL = value.TTL } switch rrType { // FIXME: Add more RR types! // http://godoc.org/github.com/miekg/dns has info as well as // http://en.wikipedia.org/wiki/List_of_DNS_record_types case dns.TypeTXT: answer := answerTXT(q, value) answers = append(answers, answer) case dns.TypeA: answer := answerA(q, value) answers = append(answers, answer) case dns.TypeAAAA: answer := answerAAAA(q, value) answers = append(answers, answer) case dns.TypeNS: answer := answerNS(q, value) answers = append(answers, answer) case dns.TypeCNAME: answer, target := answerCNAME(q, value) answers = append(answers, answer) q2 := q q2.Name = target // replace question's name with new name secondaryAnswers = append(secondaryAnswers, answerQuestion(cfg, c, q2, defaultTTL, qDepth+1)...) case dns.TypeDNAME: answer := answerDNAME(q, value) answers = append(answers, answer) wouldLikeForwarder = true case dns.TypePTR: answer := answerPTR(q, value) answers = append(answers, answer) case dns.TypeMX: answer := answerMX(q, value) // FIXME: are we supposed to be returning these in prio ordering? // ... or maybe it does that for us? or maybe it's the enduser's problem? answers = append(answers, answer) case dns.TypeSRV: answer := answerSRV(q, value) // FIXME: are we supposed to be returning these rando-weighted and in priority ordering? // ... or maybe it does that for us? or maybe it's the enduser's problem? answers = append(answers, answer) case dns.TypeSSHFP: // TODO: implement SSHFP // http://godoc.org/github.com/miekg/dns#SSHFP // NOTE: we must implement DNSSEC before using this RR type } } } } for _, answer := range answers { answer.Header().Ttl = answerTTL // FIXME: I think this might be inappropriate //log.Printf("[APPLIED TTL [%s] [%s] %d]\n", q.Name, dns.Type(q.Qtype).String(), answerTTL) } // Append the results of secondary queries, such as the results of CNAME and DNAME records answers = append(answers, secondaryAnswers...) // check to see if we host this zone; if yes, don't allow use of ext forwarders // ... also, check to see if we hit a DNAME so we can handle that aliasing // FIXME: Only forward if we are configured as a forwarder if wouldLikeForwarder && !haveAuthority(cfg, q) { log.Printf(" [%9.04fms] FORWARD %s %s\n", msElapsed(c.Start, time.Now()), q.Name, dns.Type(q.Qtype).String()) answers = append(answers, forwardQuestion(q, cfg.DNSForwarders())...) } return answers }
// ServeDNS is the entry point for every request to the address that s // is bound to. It acts as a multiplexer for the requests zonename as // defined in the request so that the correct zone // (configuration and middleware stack) will handle the request. func (s *Server) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { defer func() { // In case the user doesn't enable error middleware, we still // need to make sure that we stay alive up here if rec := recover(); rec != nil { DefaultErrorFunc(w, r, dns.RcodeServerFailure) } }() if m, err := middleware.Edns0Version(r); err != nil { // Wrong EDNS version, return at once. rc := middleware.RcodeToString(dns.RcodeBadVers) state := middleware.State{W: w, Req: r} metrics.Report(state, metrics.Dropped, rc, m.Len(), time.Now()) w.WriteMsg(m) return } // Execute the optional request callback if it exists if s.ReqCallback != nil && s.ReqCallback(w, r) { return } q := r.Question[0].Name b := make([]byte, len(q)) off, end := 0, false ctx := context.Background() for { l := len(q[off:]) for i := 0; i < l; i++ { b[i] = q[off+i] // normalize the name for the lookup if b[i] >= 'A' && b[i] <= 'Z' { b[i] |= ('a' - 'A') } } if h, ok := s.zones[string(b[:l])]; ok { if r.Question[0].Qtype != dns.TypeDS { rcode, _ := h.stack.ServeDNS(ctx, w, r) if RcodeNoClientWrite(rcode) { DefaultErrorFunc(w, r, rcode) } return } } off, end = dns.NextLabel(q, off) if end { break } } // Wildcard match, if we have found nothing try the root zone as a last resort. if h, ok := s.zones["."]; ok { rcode, _ := h.stack.ServeDNS(ctx, w, r) if RcodeNoClientWrite(rcode) { DefaultErrorFunc(w, r, rcode) } return } // Still here? Error out with REFUSED and some logging remoteHost := w.RemoteAddr().String() DefaultErrorFunc(w, r, dns.RcodeRefused) log.Printf("[INFO] \"%s %s %s\" - No such zone at %s (Remote: %s)", dns.Type(r.Question[0].Qtype), dns.Class(r.Question[0].Qclass), q, s.Addr, remoteHost) }
func main() { bytes, err := ioutil.ReadFile(*zoneFile) if err != nil { log.Panic(err) } zone := string(bytes) reader := strings.NewReader(zone) records := make(map[RecordKey]*TerraformRecord) for rr := range dns.ParseZone(reader, *domain, *zoneFile) { if rr.Error != nil { log.Printf("Error: %v\n", rr.Error) } else { header := rr.Header() recordType := dns.Type(header.Rrtype).String() isExcluded, ok := excludedTypes[recordType] if ok && isExcluded { continue } name := strings.ToLower(header.Name) data := strings.TrimPrefix(rr.String(), header.String()) if recordType == "CNAME" { data = strings.ToLower(data) } key := RecordKey{ Name: name, Type: recordType, } if rec, ok := records[key]; ok { rec.Data = append(rec.Data, data) if rr.Comment != "" { rec.Comments = append(rec.Comments, strings.TrimLeft(rr.Comment, ";")) } } else { comments := make([]string, 0) if rr.Comment != "" { comments = append(comments, strings.TrimLeft(rr.Comment, ";")) } records[key] = &TerraformRecord{ Name: key.Name, Type: key.Type, Ttl: header.Ttl, Data: []string{data}, Comments: comments, } } } } zoneName := strings.TrimRight(*domain, ".") ZoneTemplateData := ZoneTemplateData{ Id: strings.Replace(zoneName, ".", "-", -1), Domain: zoneName, } terraformZone := template.Must(template.New("zone").Parse(zoneTemplate)) terraformZone.Execute(os.Stdout, ZoneTemplateData) resource := template.Must(template.New("resource").Funcs(template.FuncMap{"ensureQuoted": ensureQuoted}).Parse(recordTemplate)) recordKeys := make(RecordKeySlice, 0, len(records)) for key, _ := range records { recordKeys = append(recordKeys, key) } sort.Sort(sort.Reverse(recordKeys)) for _, key := range recordKeys { rec := records[key] hyphenatedName := strings.Replace(strings.TrimRight(rec.Name, "."), ".", "-", -1) wildcardCleanedName := strings.Replace(hyphenatedName, "*", "wildcard", -1) id := fmt.Sprintf("%s-%s", wildcardCleanedName, rec.Type) info := RecordTemplateData{ ResourceId: id, Record: rec, Zone: ZoneTemplateData, } resource.Execute(os.Stdout, info) } }
func (h ErrorHandler) recovery(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) { rec := recover() if rec == nil { return } state := middleware.State{W: w, Req: r} // Obtain source of panic // From: https://gist.github.com/swdunlop/9629168 var name, file string // function name, file name var line int var pc [16]uintptr n := runtime.Callers(3, pc[:]) for _, pc := range pc[:n] { fn := runtime.FuncForPC(pc) if fn == nil { continue } file, line = fn.FileLine(pc) name = fn.Name() if !strings.HasPrefix(name, "runtime.") { break } } // Trim file path delim := "/coredns/" pkgPathPos := strings.Index(file, delim) if pkgPathPos > -1 && len(file) > pkgPathPos+len(delim) { file = file[pkgPathPos+len(delim):] } panicMsg := fmt.Sprintf("%s [PANIC %s %s] %s:%d - %v", time.Now().Format(timeFormat), r.Question[0].Name, dns.Type(r.Question[0].Qtype), file, line, rec) if h.Debug { // Write error and stack trace to the response rather than to a log var stackBuf [4096]byte stack := stackBuf[:runtime.Stack(stackBuf[:], false)] answer := debugMsg(dns.RcodeServerFailure, r) // add stack buf in TXT, limited to 255 chars for now. txt, _ := dns.NewRR(". IN 0 TXT " + string(stack[:255])) answer.Answer = append(answer.Answer, txt) state.SizeAndDo(answer) w.WriteMsg(answer) } else { // Currently we don't use the function name, since file:line is more conventional h.Log.Printf(panicMsg) } }
func questionToString(q dns.Question) string { return fmt.Sprintf("%s %s %s", q.Name, dns.Class(q.Qclass), dns.Type(q.Qtype)) }