bring RTT metrics inside DNSResolver

This moves the RTT metrics calculation inside of the DNSResolver. This
cleans up code in the RA and VA and makes some adding retries to the
DNSResolver less ugly to do.

Note: this will put `Rate` and `RTT` after the name of DNS query
type (`A`, `MX`, etc.). I think that's fine and desirable. We aren't
using this data in alerts or many dashboards, yet, so a flag day is
okay.

Fixes #1124
This commit is contained in:
Jeff Hodges 2015-12-15 18:51:24 -08:00
parent c65f464813
commit e36895c9c5
9 changed files with 138 additions and 120 deletions

View File

@ -13,6 +13,7 @@ import (
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
"github.com/letsencrypt/boulder/metrics"
)
var (
@ -115,11 +116,10 @@ var (
// DNSResolver defines methods used for DNS resolution
type DNSResolver interface {
ExchangeOne(string, uint16) (*dns.Msg, time.Duration, error)
LookupTXT(string) ([]string, time.Duration, error)
LookupHost(string) ([]net.IP, time.Duration, error)
LookupCAA(string) ([]*dns.CAA, time.Duration, error)
LookupMX(string) ([]string, time.Duration, error)
LookupTXT(string) ([]string, error)
LookupHost(string) ([]net.IP, error)
LookupCAA(string) ([]*dns.CAA, error)
LookupMX(string) ([]string, error)
}
// DNSResolverImpl represents a client that talks to an external resolver
@ -127,11 +127,18 @@ type DNSResolverImpl struct {
DNSClient *dns.Client
Servers []string
allowRestrictedAddresses bool
stats metrics.Scope
txtStats metrics.Scope
aStats metrics.Scope
caaStats metrics.Scope
mxStats metrics.Scope
}
var _ DNSResolver = &DNSResolverImpl{}
// NewDNSResolverImpl constructs a new DNS resolver object that utilizes the
// provided list of DNS servers for resolution.
func NewDNSResolverImpl(readTimeout time.Duration, servers []string) *DNSResolverImpl {
func NewDNSResolverImpl(readTimeout time.Duration, servers []string, stats metrics.Scope) *DNSResolverImpl {
dnsClient := new(dns.Client)
// Set timeout for underlying net.Conn
@ -142,23 +149,28 @@ func NewDNSResolverImpl(readTimeout time.Duration, servers []string) *DNSResolve
DNSClient: dnsClient,
Servers: servers,
allowRestrictedAddresses: false,
stats: stats,
txtStats: stats.NewScope("TXT"),
aStats: stats.NewScope("A"),
caaStats: stats.NewScope("CAA"),
mxStats: stats.NewScope("MX"),
}
}
// NewTestDNSResolverImpl constructs a new DNS resolver object that utilizes the
// provided list of DNS servers for resolution and will allow loopback addresses.
// This constructor should *only* be called from tests (unit or integration).
func NewTestDNSResolverImpl(readTimeout time.Duration, servers []string) *DNSResolverImpl {
resolver := NewDNSResolverImpl(readTimeout, servers)
func NewTestDNSResolverImpl(readTimeout time.Duration, servers []string, stats metrics.Scope) *DNSResolverImpl {
resolver := NewDNSResolverImpl(readTimeout, servers, stats)
resolver.allowRestrictedAddresses = true
return resolver
}
// ExchangeOne performs a single DNS exchange with a randomly chosen server
// 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) {
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)
@ -170,23 +182,32 @@ func (dnsResolver *DNSResolverImpl) ExchangeOne(hostname string, qtype uint16) (
return
}
dnsResolver.stats.Inc("Rate", 1)
// Randomly pick a server
chosenServer := dnsResolver.Servers[rand.Intn(len(dnsResolver.Servers))]
return dnsResolver.DNSClient.Exchange(m, chosenServer)
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
}
// LookupTXT sends a DNS query to find all TXT records associated with
// the provided hostname.
func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.Duration, error) {
func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, error) {
var txt []string
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeTXT)
r, err := dnsResolver.exchangeOne(hostname, dns.TypeTXT, dnsResolver.txtStats)
if err != nil {
return nil, 0, err
return nil, err
}
if r.Rcode != dns.RcodeSuccess {
err = fmt.Errorf("DNS failure: %d-%s for TXT query", r.Rcode, dns.RcodeToString[r.Rcode])
return nil, 0, err
return nil, err
}
for _, answer := range r.Answer {
@ -197,7 +218,7 @@ func (dnsResolver *DNSResolverImpl) LookupTXT(hostname string) ([]string, time.D
}
}
return txt, rtt, err
return txt, err
}
func isPrivateV4(ip net.IP) bool {
@ -212,16 +233,16 @@ func isPrivateV4(ip net.IP) bool {
// LookupHost sends a DNS query to find all A records associated with the provided
// hostname. This method assumes that the external resolver will chase CNAME/DNAME
// aliases and return relevant A records.
func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, error) {
var addrs []net.IP
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeA)
r, err := dnsResolver.exchangeOne(hostname, dns.TypeA, dnsResolver.aStats)
if err != nil {
return addrs, rtt, err
return addrs, err
}
if r.Rcode != dns.RcodeSuccess {
err = fmt.Errorf("DNS failure: %d-%s for A query", r.Rcode, dns.RcodeToString[r.Rcode])
return nil, rtt, err
return nil, err
}
for _, answer := range r.Answer {
@ -232,23 +253,23 @@ func (dnsResolver *DNSResolverImpl) LookupHost(hostname string) ([]net.IP, time.
}
}
return addrs, rtt, nil
return addrs, nil
}
// LookupCAA sends a DNS query to find all CAA records associated with
// the provided hostname. If the response code from the resolver is
// SERVFAIL an empty slice of CAA records is returned.
func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time.Duration, error) {
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeCAA)
func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, error) {
r, err := dnsResolver.exchangeOne(hostname, dns.TypeCAA, dnsResolver.caaStats)
if err != nil {
return nil, 0, err
return nil, err
}
// On resolver validation failure, or other server failures, return empty an
// set and no error.
var CAAs []*dns.CAA
if r.Rcode == dns.RcodeServerFailure {
return CAAs, rtt, nil
return CAAs, nil
}
for _, answer := range r.Answer {
@ -258,19 +279,19 @@ func (dnsResolver *DNSResolverImpl) LookupCAA(hostname string) ([]*dns.CAA, time
}
}
}
return CAAs, rtt, nil
return CAAs, nil
}
// LookupMX sends a DNS query to find a MX record associated hostname and returns the
// record target.
func (dnsResolver *DNSResolverImpl) LookupMX(hostname string) ([]string, time.Duration, error) {
r, rtt, err := dnsResolver.ExchangeOne(hostname, dns.TypeMX)
func (dnsResolver *DNSResolverImpl) LookupMX(hostname string) ([]string, error) {
r, err := dnsResolver.exchangeOne(hostname, dns.TypeMX, dnsResolver.mxStats)
if err != nil {
return nil, 0, err
return nil, err
}
if r.Rcode != dns.RcodeSuccess {
err = fmt.Errorf("DNS failure: %d-%s for MX query", r.Rcode, dns.RcodeToString[r.Rcode])
return nil, rtt, err
return nil, err
}
var results []string
@ -280,5 +301,5 @@ func (dnsResolver *DNSResolverImpl) LookupMX(hostname string) ([]string, time.Du
}
}
return results, rtt, nil
return results, nil
}

View File

@ -13,9 +13,10 @@ import (
"testing"
"time"
"github.com/letsencrypt/boulder/test"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/miekg/dns"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/test"
)
const dnsLoopbackAddr = "127.0.0.1:4053"
@ -142,116 +143,123 @@ func TestMain(m *testing.M) {
os.Exit(ret)
}
func TestDNSNoServers(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Hour, []string{})
func newTestStats() metrics.Scope {
c, _ := statsd.NewNoopClient()
return metrics.NewStatsdScope(c, "fakesvc")
}
_, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeA)
var testStats = newTestStats()
func TestDNSNoServers(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Hour, []string{}, testStats)
_, err := obj.LookupHost("letsencrypt.org")
test.AssertError(t, err, "No servers")
}
func TestDNSOneServer(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}, testStats)
_, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeSOA)
_, err := obj.LookupHost("letsencrypt.org")
test.AssertNotError(t, err, "No message")
}
func TestDNSDuplicateServers(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr, dnsLoopbackAddr})
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr, dnsLoopbackAddr}, testStats)
_, _, err := obj.ExchangeOne("letsencrypt.org", dns.TypeSOA)
_, err := obj.LookupHost("letsencrypt.org")
test.AssertNotError(t, err, "No message")
}
func TestDNSLookupsNoServer(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{})
obj := NewTestDNSResolverImpl(time.Second*10, []string{}, testStats)
_, _, err := obj.LookupTXT("letsencrypt.org")
_, err := obj.LookupTXT("letsencrypt.org")
test.AssertError(t, err, "No servers")
_, _, err = obj.LookupHost("letsencrypt.org")
_, err = obj.LookupHost("letsencrypt.org")
test.AssertError(t, err, "No servers")
_, _, err = obj.LookupCAA("letsencrypt.org")
_, err = obj.LookupCAA("letsencrypt.org")
test.AssertError(t, err, "No servers")
}
func TestDNSServFail(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}, testStats)
bad := "servfail.com"
_, _, err := obj.LookupTXT(bad)
_, err := obj.LookupTXT(bad)
test.AssertError(t, err, "LookupTXT didn't return an error")
_, _, err = obj.LookupHost(bad)
_, err = obj.LookupHost(bad)
test.AssertError(t, err, "LookupHost didn't return an error")
// CAA lookup ignores validation failures from the resolver for now
// and returns an empty list of CAA records.
emptyCaa, _, err := obj.LookupCAA(bad)
emptyCaa, err := obj.LookupCAA(bad)
test.Assert(t, len(emptyCaa) == 0, "Query returned non-empty list of CAA records")
test.AssertNotError(t, err, "LookupCAA returned an error")
}
func TestDNSLookupTXT(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}, testStats)
a, rtt, err := obj.LookupTXT("letsencrypt.org")
t.Logf("A: %v RTT %s", a, rtt)
a, err := obj.LookupTXT("letsencrypt.org")
t.Logf("A: %v", a)
test.AssertNotError(t, err, "No message")
a, rtt, err = obj.LookupTXT("split-txt.letsencrypt.org")
t.Logf("A: %v RTT %s", a, rtt)
a, err = obj.LookupTXT("split-txt.letsencrypt.org")
t.Logf("A: %v ", a)
test.AssertNotError(t, err, "No message")
test.AssertEquals(t, len(a), 1)
test.AssertEquals(t, a[0], "abc")
}
func TestDNSLookupHost(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}, testStats)
ip, _, err := obj.LookupHost("servfail.com")
ip, err := obj.LookupHost("servfail.com")
t.Logf("servfail.com - IP: %s, Err: %s", ip, err)
test.AssertError(t, err, "Server failure")
test.Assert(t, len(ip) == 0, "Should not have IPs")
ip, _, err = obj.LookupHost("nonexistent.letsencrypt.org")
ip, err = obj.LookupHost("nonexistent.letsencrypt.org")
t.Logf("nonexistent.letsencrypt.org - IP: %s, Err: %s", ip, err)
test.AssertNotError(t, err, "Not an error to not exist")
test.Assert(t, len(ip) == 0, "Should not have IPs")
// Single IPv4 address
ip, _, err = obj.LookupHost("cps.letsencrypt.org")
ip, err = obj.LookupHost("cps.letsencrypt.org")
t.Logf("cps.letsencrypt.org - IP: %s, Err: %s", ip, err)
test.AssertNotError(t, err, "Not an error to exist")
test.Assert(t, len(ip) == 1, "Should have IP")
ip, _, err = obj.LookupHost("cps.letsencrypt.org")
ip, err = obj.LookupHost("cps.letsencrypt.org")
t.Logf("cps.letsencrypt.org - IP: %s, Err: %s", ip, err)
test.AssertNotError(t, err, "Not an error to exist")
test.Assert(t, len(ip) == 1, "Should have IP")
// No IPv6
ip, _, err = obj.LookupHost("v6.letsencrypt.org")
ip, err = obj.LookupHost("v6.letsencrypt.org")
t.Logf("v6.letsencrypt.org - IP: %s, Err: %s", ip, err)
test.AssertNotError(t, err, "Not an error to exist")
test.Assert(t, len(ip) == 0, "Should not have IPs")
}
func TestDNSLookupCAA(t *testing.T) {
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr})
obj := NewTestDNSResolverImpl(time.Second*10, []string{dnsLoopbackAddr}, testStats)
caas, _, err := obj.LookupCAA("bracewel.net")
caas, err := obj.LookupCAA("bracewel.net")
test.AssertNotError(t, err, "CAA lookup failed")
test.Assert(t, len(caas) > 0, "Should have CAA records")
caas, _, err = obj.LookupCAA("nonexistent.letsencrypt.org")
caas, err = obj.LookupCAA("nonexistent.letsencrypt.org")
test.AssertNotError(t, err, "CAA lookup failed")
test.Assert(t, len(caas) == 0, "Shouldn't have CAA records")
caas, _, err = obj.LookupCAA("cname.example.com")
caas, err = obj.LookupCAA("cname.example.com")
test.AssertNotError(t, err, "CAA lookup failed")
test.Assert(t, len(caas) > 0, "Should follow CNAME to find CAA")
}

View File

@ -11,6 +11,7 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/policy"
"github.com/letsencrypt/boulder/sa"
@ -62,10 +63,11 @@ func main() {
rai.PA = pa
raDNSTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse RA DNS timeout")
scoped := metrics.NewStatsdScope(stats, "RA", "DNS")
if !c.Common.DNSAllowLoopbackAddresses {
rai.DNSResolver = bdns.NewDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver})
rai.DNSResolver = bdns.NewDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver}, scoped)
} else {
rai.DNSResolver = bdns.NewTestDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver})
rai.DNSResolver = bdns.NewTestDNSResolverImpl(raDNSTimeout, []string{c.Common.DNSResolver}, scoped)
}
rai.VA = vac

View File

@ -11,6 +11,7 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/cmd"
blog "github.com/letsencrypt/boulder/log"
@ -45,10 +46,11 @@ func main() {
vai := va.NewValidationAuthorityImpl(pc, sbc, stats, clock.Default())
dnsTimeout, err := time.ParseDuration(c.Common.DNSTimeout)
cmd.FailOnError(err, "Couldn't parse DNS timeout")
scoped := metrics.NewStatsdScope(stats, "VA", "DNS")
if !c.Common.DNSAllowLoopbackAddresses {
vai.DNSResolver = bdns.NewDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver})
vai.DNSResolver = bdns.NewDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver}, scoped)
} else {
vai.DNSResolver = bdns.NewTestDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver})
vai.DNSResolver = bdns.NewTestDNSResolverImpl(dnsTimeout, []string{c.Common.DNSResolver}, scoped)
}
vai.UserAgent = c.VA.UserAgent
vai.IssuerDomain = c.VA.IssuerDomain

View File

@ -32,17 +32,12 @@ import (
type DNSResolver struct {
}
// ExchangeOne is a mock
func (mock *DNSResolver) ExchangeOne(hostname string, qt uint16) (rsp *dns.Msg, rtt time.Duration, err error) {
return nil, 0, nil
}
// LookupTXT is a mock
func (mock *DNSResolver) LookupTXT(hostname string) ([]string, time.Duration, error) {
func (mock *DNSResolver) LookupTXT(hostname string) ([]string, error) {
if hostname == "_acme-challenge.servfail.com" {
return nil, 0, fmt.Errorf("SERVFAIL")
return nil, fmt.Errorf("SERVFAIL")
}
return []string{"hostname"}, 0, nil
return []string{"hostname"}, nil
}
// TimeoutError returns a a net.OpError for which Timeout() returns true.
@ -65,31 +60,31 @@ func (t timeoutError) Timeout() bool {
//
// Note: see comments on LookupMX regarding email.only
//
func (mock *DNSResolver) LookupHost(hostname string) ([]net.IP, time.Duration, error) {
func (mock *DNSResolver) LookupHost(hostname string) ([]net.IP, error) {
if hostname == "always.invalid" ||
hostname == "invalid.invalid" ||
hostname == "email.only" {
return []net.IP{}, 0, nil
return []net.IP{}, nil
}
if hostname == "always.timeout" {
return []net.IP{}, 0, TimeoutError()
return []net.IP{}, TimeoutError()
}
if hostname == "always.error" {
return []net.IP{}, 0, &net.OpError{
return []net.IP{}, &net.OpError{
Err: errors.New("some net error"),
}
}
ip := net.ParseIP("127.0.0.1")
return []net.IP{ip}, 0, nil
return []net.IP{ip}, nil
}
// LookupCAA is a mock
func (mock *DNSResolver) LookupCAA(domain string) ([]*dns.CAA, time.Duration, error) {
func (mock *DNSResolver) LookupCAA(domain string) ([]*dns.CAA, error) {
var results []*dns.CAA
var record dns.CAA
switch strings.TrimRight(domain, ".") {
case "caa-timeout.com":
return nil, 0, TimeoutError()
return nil, TimeoutError()
case "reserved.com":
record.Tag = "issue"
record.Value = "symantec.com"
@ -108,9 +103,9 @@ func (mock *DNSResolver) LookupCAA(domain string) ([]*dns.CAA, time.Duration, er
// reaches a public suffix.
fallthrough
case "servfail.com":
return results, 0, fmt.Errorf("SERVFAIL")
return results, fmt.Errorf("SERVFAIL")
}
return results, 0, nil
return results, nil
}
// LookupMX is a mock
@ -120,16 +115,16 @@ func (mock *DNSResolver) LookupCAA(domain string) ([]*dns.CAA, time.Duration, er
// all domains except for special cases, so MX-only domains must be
// handled in both LookupHost and LookupMX.
//
func (mock *DNSResolver) LookupMX(domain string) ([]string, time.Duration, error) {
func (mock *DNSResolver) LookupMX(domain string) ([]string, error) {
switch strings.TrimRight(domain, ".") {
case "letsencrypt.org":
fallthrough
case "email.only":
fallthrough
case "email.com":
return []string{"mail.email.com"}, 0, nil
return []string{"mail.email.com"}, nil
}
return nil, 0, nil
return nil, nil
}
// StorageAuthority is a mock

View File

@ -84,27 +84,24 @@ const (
emptyDNSResponseDetail = "empty DNS response"
)
func validateEmail(address string, resolver bdns.DNSResolver) (rtt time.Duration, count int64, prob *probs.ProblemDetails) {
func validateEmail(address string, resolver bdns.DNSResolver) (prob *probs.ProblemDetails) {
_, err := mail.ParseAddress(address)
if err != nil {
return 0, 0, &probs.ProblemDetails{
return &probs.ProblemDetails{
Type: probs.InvalidEmailProblem,
Detail: unparseableEmailDetail,
}
}
splitEmail := strings.SplitN(address, "@", -1)
domain := strings.ToLower(splitEmail[len(splitEmail)-1])
var rtt1, rtt2 time.Duration
var resultMX []string
var resultA []net.IP
resultMX, rtt1, err = resolver.LookupMX(domain)
count++
resultMX, err = resolver.LookupMX(domain)
if err == nil && len(resultMX) == 0 {
resultA, rtt2, err = resolver.LookupHost(domain)
count++
resultA, err = resolver.LookupHost(domain)
if err == nil && len(resultA) == 0 {
return rtt1 + rtt2, count, &probs.ProblemDetails{
return &probs.ProblemDetails{
Type: probs.InvalidEmailProblem,
Detail: emptyDNSResponseDetail,
}
@ -112,14 +109,13 @@ func validateEmail(address string, resolver bdns.DNSResolver) (rtt time.Duration
}
if err != nil {
dnsProblem := bdns.ProblemDetailsFromDNSError(err)
return rtt, count, &probs.ProblemDetails{
return &probs.ProblemDetails{
Type: probs.InvalidEmailProblem,
Detail: dnsProblem.Detail,
}
}
rtt = rtt1 + rtt2
return rtt, count, nil
return nil
}
type certificateRequestEvent struct {
@ -245,17 +241,15 @@ func (ra *RegistrationAuthorityImpl) validateContacts(contacts []*core.AcmeURL)
case "tel":
continue
case "mailto":
// Note: the stats handling here is a bit of a lie,
// since validateEmail() mainly does MX lookups and
// only does A lookups when the MX is missing.
rtt, count, problem := validateEmail(contact.Opaque, ra.DNSResolver)
if count > 0 {
ra.stats.TimingDuration("RA.DNS.RTT.A", time.Duration(int64(rtt)/count), 1.0)
ra.stats.Inc("RA.DNS.Rate", count, 1.0)
}
start := ra.clk.Now()
ra.stats.Inc("RA.ValidateEmail.Calls", 1, 1.0)
problem := validateEmail(contact.Opaque, ra.DNSResolver)
ra.stats.TimingDuration("RA.ValidateEmail.Latency", ra.clk.Now().Sub(start), 1.0)
if problem != nil {
ra.stats.Inc("RA.ValidateEmail.Errors", 1, 1.0)
return problem
}
ra.stats.Inc("RA.ValidateEmail.Successes", 1, 1.0)
default:
err = core.MalformedRequestError(fmt.Sprintf("Contact method %s is not supported", contact.Scheme))
return

View File

@ -325,7 +325,7 @@ func TestValidateEmail(t *testing.T) {
"b@email.only",
}
for _, tc := range testFailures {
_, _, problem := validateEmail(tc.input, &mocks.DNSResolver{})
problem := validateEmail(tc.input, &mocks.DNSResolver{})
if problem.Type != probs.InvalidEmailProblem {
t.Errorf("validateEmail(%q): got problem type %#v, expected %#v", tc.input, problem.Type, probs.InvalidEmailProblem)
}
@ -336,7 +336,7 @@ func TestValidateEmail(t *testing.T) {
}
for _, addr := range testSuccesses {
if _, _, prob := validateEmail(addr, &mocks.DNSResolver{}); prob != nil {
if prob := validateEmail(addr, &mocks.DNSResolver{}); prob != nil {
t.Errorf("validateEmail(%q): expected success, but it failed: %s",
addr, prob)
}

View File

@ -89,14 +89,12 @@ type verificationRequestEvent struct {
// net/http, except we only send A queries and accept IPv4 addresses.
// TODO(#593): Add IPv6 support
func (va ValidationAuthorityImpl) getAddr(hostname string) (addr net.IP, addrs []net.IP, problem *probs.ProblemDetails) {
addrs, rtt, err := va.DNSResolver.LookupHost(hostname)
addrs, err := va.DNSResolver.LookupHost(hostname)
if err != nil {
problem = bdns.ProblemDetailsFromDNSError(err)
va.log.Debug(fmt.Sprintf("%s DNS failure: %s", hostname, err))
return
}
va.stats.TimingDuration("VA.DNS.RTT.A", rtt, 1.0)
va.stats.Inc("VA.DNS.Rate", 1, 1.0)
if len(addrs) == 0 {
problem = &probs.ProblemDetails{
@ -431,9 +429,7 @@ func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier,
// Look for the required record in the DNS
challengeSubdomain := fmt.Sprintf("%s.%s", core.DNSPrefix, identifier.Value)
txts, rtt, err := va.DNSResolver.LookupTXT(challengeSubdomain)
va.stats.TimingDuration("VA.DNS.RTT.TXT", rtt, 1.0)
va.stats.Inc("VA.DNS.Rate", 1, 1.0)
txts, err := va.DNSResolver.LookupTXT(challengeSubdomain)
if err != nil {
va.log.Debug(fmt.Sprintf("%s [%s] DNS failure: %s", challenge.Type, identifier, err))
@ -608,12 +604,10 @@ func (va *ValidationAuthorityImpl) getCAASet(hostname string) (*CAASet, error) {
if tld, err := publicsuffix.ICANNTLD(name); err != nil || tld == name {
break
}
CAAs, caaRtt, err := va.DNSResolver.LookupCAA(name)
CAAs, err := va.DNSResolver.LookupCAA(name)
if err != nil {
return nil, err
}
va.stats.TimingDuration("VA.DNS.RTT.CAA", caaRtt, 1.0)
va.stats.Inc("VA.DNS.Rate", 1, 1.0)
if len(CAAs) > 0 {
return newCAASet(CAAs), nil
}

View File

@ -29,6 +29,7 @@ import (
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/core"
@ -813,9 +814,10 @@ func TestDNSValidationServFail(t *testing.T) {
}
func TestDNSValidationNoServer(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, nil, stats, clock.Default())
va.DNSResolver = bdns.NewTestDNSResolverImpl(time.Second*5, []string{})
c, _ := statsd.NewNoopClient()
stats := metrics.NewNoopScope()
va := NewValidationAuthorityImpl(&PortConfig{}, nil, c, clock.Default())
va.DNSResolver = bdns.NewTestDNSResolverImpl(time.Second*5, []string{}, stats)
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA