Example #1
0
// 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
}
Example #2
0
// 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)
}
Example #3
0
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)
		}
	}
}
Example #4
0
// 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
}
Example #5
0
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)
}
Example #6
0
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
}
Example #7
0
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
}
Example #8
0
// 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

}
Example #9
0
// 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
}
Example #10
0
// 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()
}
Example #11
0
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])
			}
		}
	}
}
Example #12
0
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)
}
Example #13
0
func rrHeaderFmt(hdr dns.RR_Header) string {
	return fmt.Sprintf("'%s' %s ttl: %d", hdr.Name, dns.Type(hdr.Rrtype).String(), hdr.Ttl)
}
Example #14
0
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")
}
Example #16
0
// Type returns the type of the question as a string.
func (s *State) Type() string { return dns.Type(s.Req.Question[0].Qtype).String() }
Example #17
0
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
}
Example #18
0
// 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)
}
Example #19
0
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)
	}
}
Example #20
0
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)
	}
}
Example #21
0
func questionToString(q dns.Question) string {
	return fmt.Sprintf("%s %s %s", q.Name, dns.Class(q.Qclass), dns.Type(q.Qtype))
}