1// Package iprev checks if an IP has a reverse DNS name configured and that the
 
2// reverse DNS name resolves back to the IP (RFC 8601, Section 3).
 
13	"github.com/mjl-/mox/dns"
 
14	"github.com/mjl-/mox/mlog"
 
15	"github.com/mjl-/mox/stub"
 
18var xlog = mlog.New("iprev", nil)
 
21	MetricIPRev stub.HistogramVec = stub.HistogramVecIgnore{}
 
26	ErrNoRecord = errors.New("iprev: no reverse dns record")
 
27	ErrDNS      = errors.New("iprev: dns lookup") // Temporary error.
 
32// Status is the result of a lookup.
 
36	StatusPass      Status = "pass"      // Reverse and forward lookup results were in agreement.
 
37	StatusFail      Status = "fail"      // Reverse and forward lookup results were not in agreement, but at least the reverse name does exist.
 
38	StatusTemperror Status = "temperror" // Temporary error, e.g. DNS timeout.
 
39	StatusPermerror Status = "permerror" // Permanent error and later retry is unlikely to succeed. E.g. no PTR record.
 
42// Lookup checks whether an IP has a proper reverse & forward
 
43// DNS configuration. I.e. that it is explicitly associated with its domain name.
 
45// A PTR lookup is done on the IP, resulting in zero or more names. These names are
 
46// forward resolved (A or AAAA) until the original IP address is found. The first
 
47// matching name is returned as "name". All names, matching or not, are returned as
 
50// If a temporary error occurred, rerr is set.
 
51func Lookup(ctx context.Context, resolver dns.Resolver, ip net.IP) (rstatus Status, name string, names []string, authentic bool, rerr error) {
 
52	log := xlog.WithContext(ctx)
 
55		MetricIPRev.ObserveLabels(float64(time.Since(start))/float64(time.Second), string(rstatus))
 
56		log.Debugx("iprev lookup result", rerr,
 
58			slog.Any("status", rstatus),
 
59			slog.Duration("duration", time.Since(start)))
 
62	revNames, result, revErr := dns.WithPackage(resolver, "iprev").LookupAddr(ctx, ip.String())
 
63	if dns.IsNotFound(revErr) {
 
64		return StatusPermerror, "", nil, result.Authentic, ErrNoRecord
 
65	} else if revErr != nil {
 
66		return StatusTemperror, "", nil, result.Authentic, fmt.Errorf("%w: %s", ErrDNS, revErr)
 
70	authentic = result.Authentic
 
71	for _, rname := range revNames {
 
72		ips, result, err := dns.WithPackage(resolver, "iprev").LookupIP(ctx, "ip", rname)
 
73		authentic = authentic && result.Authentic
 
74		for _, fwdIP := range ips {
 
76				return StatusPass, rname, revNames, authentic, nil
 
79		if err != nil && !dns.IsNotFound(err) {
 
84		return StatusTemperror, "", revNames, authentic, fmt.Errorf("%w: %s", ErrDNS, lastErr)
 
86	return StatusFail, "", revNames, authentic, nil