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:
parent
c65f464813
commit
e36895c9c5
83
bdns/dns.go
83
bdns/dns.go
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue