// LookupCAA uses a DNSSEC-enabled query to find all CAA records associated with // the provided hostname. If the query fails due to DNSSEC, error will be // set to ErrorDNSSEC. func (dnsResolver *DNSResolver) LookupCAA(domain string, alias bool) ([]*dns.CAA, error) { if alias { // Check if there is a CNAME record for domain canonName, err := dnsResolver.LookupCNAME(domain) if err != nil { return nil, err } if canonName == "" || canonName == domain { return []*dns.CAA{}, nil } domain = canonName } m := new(dns.Msg) m.SetQuestion(dns.Fqdn(domain), dns.TypeCAA) r, _, err := dnsResolver.LookupDNSSEC(m) if err != nil { return nil, err } var CAAs []*dns.CAA for _, answer := range r.Answer { if answer.Header().Rrtype == dns.TypeCAA { caaR, ok := answer.(*dns.CAA) if !ok { err = errors.New("Badly formatted record") return nil, err } CAAs = append(CAAs, caaR) } } return CAAs, nil }
// LookupDNSSEC sends the provided DNS message to a randomly chosen server (see // ExchangeOne) with DNSSEC enabled. If the lookup fails, this method sends a // clarification query to determine if it's because DNSSEC was invalid or just // a run-of-the-mill error. If it's because of DNSSEC, it returns ErrorDNSSEC. func (dnsResolver *DNSResolver) LookupDNSSEC(m *dns.Msg) (*dns.Msg, time.Duration, error) { // Set DNSSEC OK bit m.SetEdns0(4096, true) r, rtt, err := dnsResolver.ExchangeOne(m) if err != nil { return r, rtt, err } if r.Rcode != dns.RcodeSuccess && r.Rcode != dns.RcodeNameError && r.Rcode != dns.RcodeNXRrset { if r.Rcode == dns.RcodeServerFailure { // Re-send query with +cd to see if SERVFAIL was caused by DNSSEC // validation failure at the resolver m.CheckingDisabled = true checkR, _, err := dnsResolver.ExchangeOne(m) if err != nil { return r, rtt, err } if checkR.Rcode != dns.RcodeServerFailure { // DNSSEC error, so we return the testable object. err = DNSSECError{} return r, rtt, err } } err = fmt.Errorf("Invalid response code: %d-%s", r.Rcode, dns.RcodeToString[r.Rcode]) return r, rtt, err } return r, rtt, err }
// LookupHost uses a DNSSEC-enabled query to find all A/AAAA records associated with // the provided hostname. If the query fails due to DNSSEC, error will be // set to ErrorDNSSEC. func (dnsResolver *DNSResolver) LookupHost(hostname string) ([]net.IP, time.Duration, error) { var addrs []net.IP var answers []dns.RR m := new(dns.Msg) m.SetQuestion(dns.Fqdn(hostname), dns.TypeA) r, aRtt, err := dnsResolver.LookupDNSSEC(m) if err != nil { return addrs, aRtt, err } answers = append(answers, r.Answer...) m.SetQuestion(dns.Fqdn(hostname), dns.TypeAAAA) r, aaaaRtt, err := dnsResolver.LookupDNSSEC(m) if err != nil { return addrs, aRtt + aaaaRtt, err } answers = append(answers, r.Answer...) for _, answer := range answers { if answer.Header().Rrtype == dns.TypeA { a := answer.(*dns.A) addrs = append(addrs, a.A) } else if answer.Header().Rrtype == dns.TypeAAAA { aaaa := answer.(*dns.AAAA) addrs = append(addrs, aaaa.AAAA) } } return addrs, aRtt + aaaaRtt, nil }
// exchangeOne performs a single DNS exchange with a randomly chosen server // out of the server list, returning the response, time, and error (if any). // This method sets the DNSSEC OK bit on the message to true before sending // it to the resolver in case validation isn't the resolvers default behaviour. func (dnsResolver *DNSResolverImpl) exchangeOne(hostname string, qtype uint16, msgStats metrics.Scope) (rsp *dns.Msg, err error) { m := new(dns.Msg) // Set question type m.SetQuestion(dns.Fqdn(hostname), qtype) // Set DNSSEC OK bit for resolver m.SetEdns0(4096, true) if len(dnsResolver.Servers) < 1 { err = fmt.Errorf("Not configured with at least one DNS Server") return } dnsResolver.stats.Inc("Rate", 1) // Randomly pick a server chosenServer := dnsResolver.Servers[rand.Intn(len(dnsResolver.Servers))] msg, rtt, err := dnsResolver.DNSClient.Exchange(m, chosenServer) msgStats.TimingDuration("RTT", rtt) if err == nil { msgStats.Inc("Successes", 1) } else { msgStats.Inc("Errors", 1) } return msg, err }
func TestDNSDuplicateServers(t *testing.T) { obj := NewDNSResolver(time.Second*10, []string{"8.8.8.8:53", "8.8.8.8:53"}) m := new(dns.Msg) m.SetQuestion("letsencrypt.org.", dns.TypeSOA) _, _, err := obj.ExchangeOne(m) test.AssertNotError(t, err, "No message") }
// exchangeOne performs a single DNS exchange with a randomly chosen server // out of the server list, returning the response, time, and error (if any). // This method sets the DNSSEC OK bit on the message to true before sending // it to the resolver in case validation isn't the resolvers default behaviour. func (dnsResolver *DNSResolverImpl) exchangeOne(ctx context.Context, hostname string, qtype uint16, msgStats metrics.Scope) (*dns.Msg, error) { m := new(dns.Msg) // Set question type m.SetQuestion(dns.Fqdn(hostname), qtype) // Set DNSSEC OK bit for resolver m.SetEdns0(4096, true) if len(dnsResolver.Servers) < 1 { return nil, fmt.Errorf("Not configured with at least one DNS Server") } dnsResolver.stats.Inc("Rate", 1) // Randomly pick a server chosenServer := dnsResolver.Servers[rand.Intn(len(dnsResolver.Servers))] client := dnsResolver.DNSClient tries := 1 start := dnsResolver.clk.Now() msgStats.Inc("Calls", 1) defer msgStats.TimingDuration("Latency", dnsResolver.clk.Now().Sub(start)) for { msgStats.Inc("Tries", 1) ch := make(chan dnsResp, 1) go func() { rsp, rtt, err := client.Exchange(m, chosenServer) msgStats.TimingDuration("SingleTryLatency", rtt) ch <- dnsResp{m: rsp, err: err} }() select { case <-ctx.Done(): msgStats.Inc("Cancels", 1) msgStats.Inc("Errors", 1) return nil, ctx.Err() case r := <-ch: if r.err != nil { msgStats.Inc("Errors", 1) operr, ok := r.err.(*net.OpError) isRetryable := ok && operr.Temporary() hasRetriesLeft := tries < dnsResolver.maxTries if isRetryable && hasRetriesLeft { tries++ continue } else if isRetryable && !hasRetriesLeft { msgStats.Inc("RanOutOfTries", 1) } } else { msgStats.Inc("Successes", 1) } return r.m, r.err } } }
// ExchangeOne performs a single DNS exchange with a randomly chosen server // out of the server list, returning the response, time, and error (if any). // This method sets the DNSSEC OK bit on the message to true before sending // it to the resolver in case validation isn't the resolvers default behaviour. func (dnsResolver *DNSResolverImpl) ExchangeOne(hostname string, qtype uint16) (rsp *dns.Msg, rtt time.Duration, err error) { m := new(dns.Msg) // Set question type m.SetQuestion(dns.Fqdn(hostname), qtype) // Set DNSSEC OK bit for resolver m.SetEdns0(4096, true) if len(dnsResolver.Servers) < 1 { err = fmt.Errorf("Not configured with at least one DNS Server") return } // Randomly pick a server chosenServer := dnsResolver.Servers[rand.Intn(len(dnsResolver.Servers))] return dnsResolver.DNSClient.Exchange(m, chosenServer) }
// LookupCNAME uses a DNSSEC-enabled query to records for domain and returns either // the target, "", or a if the query fails due to DNSSEC, error will be set to // ErrorDNSSEC. func (dnsResolver *DNSResolver) LookupCNAME(domain string) (string, error) { m := new(dns.Msg) m.SetQuestion(dns.Fqdn(domain), dns.TypeCNAME) r, _, err := dnsResolver.LookupDNSSEC(m) if err != nil { return "", err } for _, answer := range r.Answer { if cname, ok := answer.(*dns.CNAME); ok { return cname.Target, nil } } return "", nil }
func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) { defer w.Close() m := new(dns.Msg) m.SetReply(r) m.Compress = false for _, q := range r.Question { if q.Name == "servfail.com." { m.Rcode = dns.RcodeServerFailure w.WriteMsg(m) return } switch q.Qtype { case dns.TypeSOA: record := new(dns.SOA) record.Hdr = dns.RR_Header{Name: "letsencrypt.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0} record.Ns = "ns.letsencrypt.org." record.Mbox = "master.letsencrypt.org." record.Serial = 1 record.Refresh = 1 record.Retry = 1 record.Expire = 1 record.Minttl = 1 m.Answer = append(m.Answer, record) w.WriteMsg(m) return case dns.TypeA: if q.Name == "cps.letsencrypt.org." { record := new(dns.A) record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} record.A = net.ParseIP("127.0.0.1") m.Answer = append(m.Answer, record) w.WriteMsg(m) return } case dns.TypeCAA: if q.Name == "bracewel.net." { record := new(dns.CAA) record.Hdr = dns.RR_Header{Name: "bracewel.net.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0} record.Tag = "issue" record.Value = "letsencrypt.org" record.Flag = 1 m.Answer = append(m.Answer, record) w.WriteMsg(m) return } } } w.WriteMsg(m) return }
// LookupTXT uses a DNSSEC-enabled query to find all TXT records associated with // the provided hostname. If the query fails due to DNSSEC, error will be // set to ErrorDNSSEC. func (dnsResolver *DNSResolver) LookupTXT(hostname string) ([]string, time.Duration, error) { var txt []string m := new(dns.Msg) m.SetQuestion(dns.Fqdn(hostname), dns.TypeTXT) r, rtt, err := dnsResolver.LookupDNSSEC(m) if err != nil { return nil, 0, err } for _, answer := range r.Answer { if answer.Header().Rrtype == dns.TypeTXT { txtRec := answer.(*dns.TXT) for _, field := range txtRec.Txt { txt = append(txt, field) } } } return txt, rtt, err }
func TestDNSSEC(t *testing.T) { goodServer := NewDNSResolver(time.Second*10, []string{"8.8.8.8:53"}) m := new(dns.Msg) m.SetQuestion(dns.Fqdn("sigfail.verteiltesysteme.net"), dns.TypeA) _, _, err := goodServer.LookupDNSSEC(m) test.AssertError(t, err, "DNSSEC failure") _, ok := err.(DNSSECError) test.Assert(t, ok, "Should have been a DNSSECError") m.SetQuestion(dns.Fqdn("sigok.verteiltesysteme.net"), dns.TypeA) _, _, err = goodServer.LookupDNSSEC(m) test.AssertNotError(t, err, "DNSSEC should have worked") badServer := NewDNSResolver(time.Second*10, []string{"127.0.0.1:99"}) _, _, err = badServer.LookupDNSSEC(m) test.AssertError(t, err, "Should have failed") _, ok = err.(DNSSECError) test.Assert(t, !ok, "Shouldn't have been a DNSSECError") }
func dnsHandler(w dns.ResponseWriter, r *dns.Msg) { defer w.Close() m := new(dns.Msg) m.SetReply(r) m.Compress = false // Normally this test DNS server will return 127.0.0.1 for everything. // However, in some situations (for instance Docker), it's useful to return a // different hardcoded host. You can do so by setting the FAKE_DNS environment // variable. fakeDNS := os.Getenv("FAKE_DNS") if fakeDNS == "" { fakeDNS = "127.0.0.1" } for _, q := range r.Question { fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype]) switch q.Qtype { case dns.TypeA: record := new(dns.A) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0, } record.A = net.ParseIP(fakeDNS) m.Answer = append(m.Answer, record) case dns.TypeMX: record := new(dns.MX) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0, } record.Mx = "mail." + q.Name record.Preference = 10 m.Answer = append(m.Answer, record) } } w.WriteMsg(m) return }
func dnsHandler(w dns.ResponseWriter, r *dns.Msg) { defer w.Close() m := new(dns.Msg) m.SetReply(r) m.Compress = false for _, q := range r.Question { fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype]) switch q.Qtype { case dns.TypeA: record := new(dns.A) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0, } record.A = net.ParseIP("127.0.0.1") m.Answer = append(m.Answer, record) case dns.TypeMX: record := new(dns.MX) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0, } record.Mx = "mail." + q.Name record.Preference = 10 m.Answer = append(m.Answer, record) } } w.WriteMsg(m) return }
func mockDNSQuery(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) m.Compress = false appendAnswer := func(rr dns.RR) { m.Answer = append(m.Answer, rr) } for _, q := range r.Question { q.Name = strings.ToLower(q.Name) if q.Name == "servfail.com." { m.Rcode = dns.RcodeServerFailure break } switch q.Qtype { case dns.TypeSOA: record := new(dns.SOA) record.Hdr = dns.RR_Header{Name: "letsencrypt.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0} record.Ns = "ns.letsencrypt.org." record.Mbox = "master.letsencrypt.org." record.Serial = 1 record.Refresh = 1 record.Retry = 1 record.Expire = 1 record.Minttl = 1 appendAnswer(record) case dns.TypeAAAA: if q.Name == "v6.letsencrypt.org." { record := new(dns.AAAA) record.Hdr = dns.RR_Header{Name: "v6.letsencrypt.org.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: 0} record.AAAA = net.ParseIP("::1") appendAnswer(record) } case dns.TypeA: if q.Name == "cps.letsencrypt.org." { record := new(dns.A) record.Hdr = dns.RR_Header{Name: "cps.letsencrypt.org.", Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0} record.A = net.ParseIP("127.0.0.1") appendAnswer(record) } if q.Name == "nxdomain.letsencrypt.org." { m.SetRcode(r, dns.RcodeNameError) } case dns.TypeCNAME: if q.Name == "cname.letsencrypt.org." { record := new(dns.CNAME) record.Hdr = dns.RR_Header{Name: "cname.letsencrypt.org.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30} record.Target = "cps.letsencrypt.org." appendAnswer(record) } if q.Name == "cname.example.com." { record := new(dns.CNAME) record.Hdr = dns.RR_Header{Name: "cname.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 30} record.Target = "CAA.example.com." appendAnswer(record) } case dns.TypeDNAME: if q.Name == "dname.letsencrypt.org." { record := new(dns.DNAME) record.Hdr = dns.RR_Header{Name: "dname.letsencrypt.org.", Rrtype: dns.TypeDNAME, Class: dns.ClassINET, Ttl: 30} record.Target = "cps.letsencrypt.org." appendAnswer(record) } case dns.TypeCAA: if q.Name == "bracewel.net." || q.Name == "caa.example.com." { record := new(dns.CAA) record.Hdr = dns.RR_Header{Name: q.Name, Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0} record.Tag = "issue" record.Value = "letsencrypt.org" record.Flag = 1 appendAnswer(record) } if q.Name == "cname.example.com." { record := new(dns.CAA) record.Hdr = dns.RR_Header{Name: "caa.example.com.", Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0} record.Tag = "issue" record.Value = "letsencrypt.org" record.Flag = 1 appendAnswer(record) } case dns.TypeTXT: if q.Name == "split-txt.letsencrypt.org." { record := new(dns.TXT) record.Hdr = dns.RR_Header{Name: "split-txt.letsencrypt.org.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0} record.Txt = []string{"a", "b", "c"} appendAnswer(record) } else { auth := new(dns.SOA) auth.Hdr = dns.RR_Header{Name: "letsencrypt.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0} auth.Ns = "ns.letsencrypt.org." auth.Mbox = "master.letsencrypt.org." auth.Serial = 1 auth.Refresh = 1 auth.Retry = 1 auth.Expire = 1 auth.Minttl = 1 m.Ns = append(m.Ns, auth) } if q.Name == "nxdomain.letsencrypt.org." { m.SetRcode(r, dns.RcodeNameError) } } } w.WriteMsg(m) return }
func (ts *testSrv) dnsHandler(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) m.Compress = false // Normally this test DNS server will return 127.0.0.1 for everything. // However, in some situations (for instance Docker), it's useful to return a // different hardcoded host. You can do so by setting the FAKE_DNS environment // variable. fakeDNS := os.Getenv("FAKE_DNS") if fakeDNS == "" { fakeDNS = "127.0.0.1" } for _, q := range r.Question { fmt.Printf("dns-srv: Query -- [%s] %s\n", q.Name, dns.TypeToString[q.Qtype]) switch q.Qtype { case dns.TypeA: record := new(dns.A) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 0, } record.A = net.ParseIP(fakeDNS) m.Answer = append(m.Answer, record) case dns.TypeMX: record := new(dns.MX) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: 0, } record.Mx = "mail." + q.Name record.Preference = 10 m.Answer = append(m.Answer, record) case dns.TypeTXT: ts.mu.RLock() value, present := ts.txtRecords[q.Name] ts.mu.RUnlock() if !present { continue } record := new(dns.TXT) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 0, } record.Txt = []string{value} m.Answer = append(m.Answer, record) case dns.TypeCAA: if q.Name == "bad-caa-reserved.com." || q.Name == "good-caa-reserved.com." { record := new(dns.CAA) record.Hdr = dns.RR_Header{ Name: q.Name, Rrtype: dns.TypeCAA, Class: dns.ClassINET, Ttl: 0, } record.Tag = "issue" if q.Name == "bad-caa-reserved.com." { record.Value = "sad-hacker-ca.invalid" } else if q.Name == "good-caa-reserved.com." { record.Value = "happy-hacker-ca.invalid" } m.Answer = append(m.Answer, record) } } } auth := new(dns.SOA) auth.Hdr = dns.RR_Header{Name: "boulder.invalid.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 0} auth.Ns = "ns.boulder.invalid." auth.Mbox = "master.boulder.invalid." auth.Serial = 1 auth.Refresh = 1 auth.Retry = 1 auth.Expire = 1 auth.Minttl = 1 m.Ns = append(m.Ns, auth) w.WriteMsg(m) return }