Repository: letsencrypt/boulder

Issue:
Refactor //issuance to support OCSP
When we issue a certificate, we call [`issuance.Issuer{}.Issue()`](https://github.com/letsencrypt/boulder/blob/0340b574d903ce23a681feade84b389ab9bd6ae1/issuance/issuance.go#L605). This method double-checks that the certificate profile stored on the Issuer is valid, combines that with a tightly-constrained IssuanceRequest, performs pre-issuance linting, and then finally creates the certificate.

When we issue an OCSP response, we call [`ocsp.CreateResponse(issuance.Issuer{}.Signer)`](https://github.com/letsencrypt/boulder/blob/0340b574d903ce23a681feade84b389ab9bd6ae1/ca/ocsp.go#L146). This simply extracts the underlying signer from an existing Issuer and passes it in to the stdlib's ocsp-generation routine. Although the surrounding function does use various configuration parameters to construct an ocsp response template, this means that our construction and enforcement of the OCSP profile is less centralized and auditable.

Similarly, when we issue a CRL, we call [`x509.CreateRevocationList(issuance.Issuer{}.Signer`](https://github.com/letsencrypt/boulder/blob/0340b574d903ce23a681feade84b389ab9bd6ae1/ca/crl.go#L141-L146). All of the CRL configuration and "profile" logic is contained in the CRL-specific file, not built in to the Issuer itself.

There's nothing inherently wrong with having OCSP and CRL profile configuration happen in OCSP- and CRL-specific files. But it means that any work we do in the //issuance package (such as restricting how or from where keys can be loaded, or enforcing that all signatures be validated before they're returned) isn't _necessarily_ shared by those codepaths. It might be nice to have all issuance go through `Issuer.IssueCert()`, `Issuer.IssueOCSP()`, and `Issuer.IssueCRL()`, or something like that.

Discussion:
@aarongable: The work for CRLs is being tracked in https://github.com/letsencrypt/boulder/issues/7159
Updating this ticket to only track the work for OCSP.@aarongable: As of #7159, the issuance package now supports CRLs. As we [announced late last year](https://letsencrypt.org/2024/12/05/ending-ocsp/), we are replacing OCSP URIs in our certificates with CRL URLs and turning down our OCSP service. We do not intend to make further improvements to our OCSP-responder infrastructure between now and when we decommission that service.

Solution patch:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index fe416c3cd..f0cd015c0 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -20,8 +20,8 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@v4
       - name: Initialize CodeQL
-        uses: github/codeql-action/init@v2
+        uses: github/codeql-action/init@v3
       - name: Autobuild
-        uses: github/codeql-action/autobuild@v2
+        uses: github/codeql-action/autobuild@v3
       - name: Perform CodeQL Analysis
-        uses: github/codeql-action/analyze@v2
+        uses: github/codeql-action/analyze@v3
diff --git a/bdns/dns.go b/bdns/dns.go
index b993d2cf0..775d99383 100644
--- a/bdns/dns.go
+++ b/bdns/dns.go
@@ -10,6 +10,7 @@ import (
 	"net"
 	"net/http"
 	"net/url"
+	"slices"
 	"strconv"
 	"strings"
 	"sync"
@@ -150,11 +151,17 @@ var (
 	}
 )
 
+// ResolverAddrs contains DNS resolver(s) that were chosen to perform a
+// validation request or CAA recheck. A ResolverAddr will be in the form of
+// host:port, A:host:port, or AAAA:host:port depending on which type of lookup
+// was done.
+type ResolverAddrs []string
+
 // Client queries for DNS records
 type Client interface {
-	LookupTXT(context.Context, string) (txts []string, err error)
-	LookupHost(context.Context, string) ([]net.IP, error)
-	LookupCAA(context.Context, string) ([]*dns.CAA, string, error)
+	LookupTXT(context.Context, string) (txts []string, resolver ResolverAddrs, err error)
+	LookupHost(context.Context, string) ([]net.IP, ResolverAddrs, error)
+	LookupCAA(context.Context, string) ([]*dns.CAA, string, ResolverAddrs, error)
 }
 
 // impl represents a client that talks to an external resolver
@@ -284,7 +291,7 @@ func NewTest(
 // out of the server list, returning the response, time, and error (if any).
 // We assume that the upstream resolver requests and validates DNSSEC records
 // itself.
-func (dnsClient *impl) exchangeOne(ctx context.Context, hostname string, qtype uint16) (resp *dns.Msg, err error) {
+func (dnsClient *impl) exchangeOne(ctx context.Context, hostname string, qtype uint16) (resp *dns.Msg, resolver string, err error) {
 	m := new(dns.Msg)
 	// Set question type
 	m.SetQuestion(dns.Fqdn(hostname), qtype)
@@ -302,10 +309,11 @@ func (dnsClient *impl) exchangeOne(ctx context.Context, hostname string, qtype u
 
 	servers, err := dnsClient.servers.Addrs()
 	if err != nil {
-		return nil, fmt.Errorf("failed to list DNS servers: %w", err)
+		return nil, "", fmt.Errorf("failed to list DNS servers: %w", err)
 	}
 	chosenServerIndex := 0
 	chosenServer := servers[chosenServerIndex]
+	resolver = chosenServer
 
 	// Strip off the IP address part of the server address because
 	// we talk to the same server on multiple ports, and don't want
@@ -414,6 +422,7 @@ func (dnsClient *impl) exchangeOne(ctx context.Context, hostname string, qtype u
 					// list.
 					chosenServerIndex = (chosenServerIndex + 1) % len(servers)
 					chosenServer = servers[chosenServerIndex]
+					resolver = chosenServer
 					continue
 				} else if isRetryable && !hasRetriesLeft {
 					dnsClient.timeoutCounter.With(prometheus.Labels{
@@ -450,13 +459,13 @@ type dnsResp struct {
 // LookupTXT sends a DNS query to find all TXT records associated with
 // the provided hostname which it returns along with the returned
 // DNS authority section.
-func (dnsClient *impl) LookupTXT(ctx context.Context, hostname string) ([]string, error) {
+func (dnsClient *impl) LookupTXT(ctx context.Context, hostname string) ([]string, ResolverAddrs, error) {
 	var txt []string
 	dnsType := dns.TypeTXT
-	r, err := dnsClient.exchangeOne(ctx, hostname, dnsType)
+	r, resolver, err := dnsClient.exchangeOne(ctx, hostname, dnsType)
 	errWrap := wrapErr(dnsType, hostname, r, err)
 	if errWrap != nil {
-		return nil, errWrap
+		return nil, ResolverAddrs{resolver}, errWrap
 	}
 
 	for _, answer := range r.Answer {
@@ -467,7 +476,7 @@ func (dnsClient *impl) LookupTXT(ctx context.Context, hostname string) ([]string
 		}
 	}
 
-	return txt, err
+	return txt, ResolverAddrs{resolver}, err
 }
 
 func isPrivateV4(ip net.IP) bool {
@@ -488,13 +497,23 @@ func isPrivateV6(ip net.IP) bool {
 	return false
 }
 
-func (dnsClient *impl) lookupIP(ctx context.Context, hostname string, ipType uint16) ([]dns.RR, error) {
-	resp, err := dnsClient.exchangeOne(ctx, hostname, ipType)
+func (dnsClient *impl) lookupIP(ctx context.Context, hostname string, ipType uint16) ([]dns.RR, string, error) {
+	resp, resolver, err := dnsClient.exchangeOne(ctx, hostname, ipType)
+	switch ipType {
+	case dns.TypeA:
+		if resolver != "" {
+			resolver = "A:" + resolver
+		}
+	case dns.TypeAAAA:
+		if resolver != "" {
+			resolver = "AAAA:" + resolver
+		}
+	}
 	errWrap := wrapErr(ipType, hostname, resp, err)
 	if errWrap != nil {
-		return nil, errWrap
+		return nil, resolver, errWrap
 	}
-	return resp.Answer, nil
+	return resp.Answer, resolver, nil
 }
 
 // LookupHost sends a DNS query to find all A and AAAA records associated with
@@ -502,23 +521,29 @@ func (dnsClient *impl) lookupIP(ctx context.Context, hostname string, ipType uin
 // chase CNAME/DNAME aliases and return relevant records. It will retry
 // requests in the case of temporary network errors. It returns an error if
 // both the A and AAAA lookups fail or are empty, but succeeds otherwise.
-func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.IP, error) {
+func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.IP, ResolverAddrs, error) {
 	var recordsA, recordsAAAA []dns.RR
 	var errA, errAAAA error
+	var resolverA, resolverAAAA string
 	var wg sync.WaitGroup
 
 	wg.Add(1)
 	go func() {
 		defer wg.Done()
-		recordsA, errA = dnsClient.lookupIP(ctx, hostname, dns.TypeA)
+		recordsA, resolverA, errA = dnsClient.lookupIP(ctx, hostname, dns.TypeA)
 	}()
 	wg.Add(1)
 	go func() {
 		defer wg.Done()
-		recordsAAAA, errAAAA = dnsClient.lookupIP(ctx, hostname, dns.TypeAAAA)
+		recordsAAAA, resolverAAAA, errAAAA = dnsClient.lookupIP(ctx, hostname, dns.TypeAAAA)
 	}()
 	wg.Wait()
 
+	resolvers := ResolverAddrs{resolverA, resolverAAAA}
+	resolvers = slices.DeleteFunc(resolvers, func(a string) bool {
+		return a == ""
+	})
+
 	var addrsA []net.IP
 	if errA == nil {
 		for _, answer := range recordsA {
@@ -555,19 +580,19 @@ func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.I
 		// branching. We don't use ProblemDetails and SubProblemDetails here, because
 		// this error will get wrapped in a DNSError and further munged by higher
 		// layers in the stack.
-		return nil, fmt.Errorf("%w; %s", errA, errAAAA)
+		return nil, resolvers, fmt.Errorf("%w; %s", errA, errAAAA)
 	}
 
-	return append(addrsA, addrsAAAA...), nil
+	return append(addrsA, addrsAAAA...), resolvers, nil
 }
 
 // LookupCAA sends a DNS query to find all CAA records associated with
 // the provided hostname and the complete dig-style RR `response`. This
 // response is quite verbose, however it's only populated when the CAA
 // response is non-empty.
-func (dnsClient *impl) LookupCAA(ctx context.Context, hostname string) ([]*dns.CAA, string, error) {
+func (dnsClient *impl) LookupCAA(ctx context.Context, hostname string) ([]*dns.CAA, string, ResolverAddrs, error) {
 	dnsType := dns.TypeCAA
-	r, err := dnsClient.exchangeOne(ctx, hostname, dnsType)
+	r, resolver, err := dnsClient.exchangeOne(ctx, hostname, dnsType)
 
 	// Special case: when checking CAA for non-TLD names, treat NXDOMAIN as a
 	// successful response containing an empty set of records. This can come up in
@@ -576,12 +601,12 @@ func (dnsClient *impl) LookupCAA(ctx context.Context, hostname string) ([]*dns.C
 	// rechecking. But allow NXDOMAIN for TLDs to fall through to the error code
 	// below, so we don't issue for gTLDs that have been removed by ICANN.
 	if err == nil && r.Rcode == dns.RcodeNameError && strings.Contains(hostname, ".") {
-		return nil, "", nil
+		return nil, "", ResolverAddrs{resolver}, nil
 	}
 
 	errWrap := wrapErr(dnsType, hostname, r, err)
 	if errWrap != nil {
-		return nil, "", errWrap
+		return nil, "", ResolverAddrs{resolver}, errWrap
 	}
 
 	var CAAs []*dns.CAA
@@ -594,7 +619,7 @@ func (dnsClient *impl) LookupCAA(ctx context.Context, hostname string) ([]*dns.C
 	if len(CAAs) > 0 {
 		response = r.String()
 	}
-	return CAAs, response, nil
+	return CAAs, response, ResolverAddrs{resolver}, nil
 }
 
 // logDNSError logs the provided err result from making a query for hostname to
diff --git a/bdns/mocks.go b/bdns/mocks.go
index eb4eeb470..36bf2e88d 100644
--- a/bdns/mocks.go
+++ b/bdns/mocks.go
@@ -18,36 +18,36 @@ type MockClient struct {
 }
 
 // LookupTXT is a mock
-func (mock *MockClient) LookupTXT(_ context.Context, hostname string) ([]string, error) {
+func (mock *MockClient) LookupTXT(_ context.Context, hostname string) ([]string, ResolverAddrs, error) {
 	if hostname == "_acme-challenge.servfail.com" {
-		return nil, fmt.Errorf("SERVFAIL")
+		return nil, ResolverAddrs{"MockClient"}, fmt.Errorf("SERVFAIL")
 	}
 	if hostname == "_acme-challenge.good-dns01.com" {
 		// base64(sha256("LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
 		//               + "." + "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"))
 		// expected token + test account jwk thumbprint
-		return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, nil
+		return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, ResolverAddrs{"MockClient"}, nil
 	}
 	if hostname == "_acme-challenge.wrong-dns01.com" {
-		return []string{"a"}, nil
+		return []string{"a"}, ResolverAddrs{"MockClient"}, nil
 	}
 	if hostname == "_acme-challenge.wrong-many-dns01.com" {
-		return []string{"a", "b", "c", "d", "e"}, nil
+		return []string{"a", "b", "c", "d", "e"}, ResolverAddrs{"MockClient"}, nil
 	}
 	if hostname == "_acme-challenge.long-dns01.com" {
-		return []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, nil
+		return []string{"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, ResolverAddrs{"MockClient"}, nil
 	}
 	if hostname == "_acme-challenge.no-authority-dns01.com" {
 		// base64(sha256("LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
 		//               + "." + "9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"))
 		// expected token + test account jwk thumbprint
-		return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, nil
+		return []string{"LPsIwTo7o8BoG0-vjCyGQGBWSVIPxI-i_X336eUOQZo"}, ResolverAddrs{"MockClient"}, nil
 	}
 	// empty-txts.com always returns zero TXT records
 	if hostname == "_acme-challenge.empty-txts.com" {
-		return []string{}, nil
+		return []string{}, ResolverAddrs{"MockClient"}, nil
 	}
-	return []string{"hostname"}, nil
+	return []string{"hostname"}, ResolverAddrs{"MockClient"}, nil
 }
 
 // makeTimeoutError returns a a net.OpError for which Timeout() returns true.
@@ -67,13 +67,13 @@ func (t timeoutError) Timeout() bool {
 }
 
 // LookupHost is a mock
-func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
+func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]net.IP, ResolverAddrs, error) {
 	if hostname == "always.invalid" ||
 		hostname == "invalid.invalid" {
-		return []net.IP{}, nil
+		return []net.IP{}, ResolverAddrs{"MockClient"}, nil
 	}
 	if hostname == "always.timeout" {
-		return []net.IP{}, &Error{dns.TypeA, "always.timeout", makeTimeoutError(), -1, nil}
+		return []net.IP{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, "always.timeout", makeTimeoutError(), -1, nil}
 	}
 	if hostname == "always.error" {
 		err := &net.OpError{
@@ -86,7 +86,7 @@ func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]net.IP
 		m.AuthenticatedData = true
 		m.SetEdns0(4096, false)
 		logDNSError(mock.Log, "mock.server", hostname, m, nil, err)
-		return []net.IP{}, &Error{dns.TypeA, hostname, err, -1, nil}
+		return []net.IP{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
 	}
 	if hostname == "id.mismatch" {
 		err := dns.ErrId
@@ -100,25 +100,25 @@ func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]net.IP
 		record.A = net.ParseIP("127.0.0.1")
 		r.Answer = append(r.Answer, record)
 		logDNSError(mock.Log, "mock.server", hostname, m, r, err)
-		return []net.IP{}, &Error{dns.TypeA, hostname, err, -1, nil}
+		return []net.IP{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
 	}
 	// dual-homed host with an IPv6 and an IPv4 address
 	if hostname == "ipv4.and.ipv6.localhost" {
 		return []net.IP{
 			net.ParseIP("::1"),
 			net.ParseIP("127.0.0.1"),
-		}, nil
+		}, ResolverAddrs{"MockClient"}, nil
 	}
 	if hostname == "ipv6.localhost" {
 		return []net.IP{
 			net.ParseIP("::1"),
-		}, nil
+		}, ResolverAddrs{"MockClient"}, nil
 	}
 	ip := net.ParseIP("127.0.0.1")
-	return []net.IP{ip}, nil
+	return []net.IP{ip}, ResolverAddrs{"MockClient"}, nil
 }
 
 // LookupCAA returns mock records for use in tests.
-func (mock *MockClient) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, error) {
-	return nil, "", nil
+func (mock *MockClient) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, ResolverAddrs, error) {
+	return nil, "", ResolverAddrs{"MockClient"}, nil
 }
diff --git a/ca/ca.go b/ca/ca.go
index 3d23a8bed..f9d75fd8d 100644
--- a/ca/ca.go
+++ b/ca/ca.go
@@ -60,6 +60,7 @@ type certificateAuthorityImpl struct {
 	sa      sapb.StorageAuthorityCertificateClient
 	pa      core.PolicyAuthority
 	issuers issuerMaps
+	profile *issuance.Profile
 
 	// This is temporary, and will be used for testing and slow roll-out
 	// of ECDSA issuance, but will then be removed.
@@ -103,6 +104,7 @@ func NewCertificateAuthorityImpl(
 	sa sapb.StorageAuthorityCertificateClient,
 	pa core.PolicyAuthority,
 	boulderIssuers []*issuance.Issuer,
+	certificateProfile *issuance.Profile,
 	ecdsaAllowList *ECDSAAllowList,
 	certExpiry time.Duration,
 	certBackdate time.Duration,
@@ -134,6 +136,10 @@ func NewCertificateAuthorityImpl(
 		return nil, errors.New("must have at least one issuer")
 	}
 
+	if certificateProfile == nil {
+		return nil, errors.New("must have at least one certificate profile")
+	}
+
 	issuers := makeIssuerMaps(boulderIssuers)
 
 	lintErrorCount := prometheus.NewCounter(
@@ -147,6 +153,7 @@ func NewCertificateAuthorityImpl(
 		sa:             sa,
 		pa:             pa,
 		issuers:        issuers,
+		profile:        certificateProfile,
 		validityPeriod: certExpiry,
 		backdate:       certBackdate,
 		prefix:         serialPrefix,
@@ -281,7 +288,7 @@ func (ca *certificateAuthorityImpl) IssueCertificateForPrecertificate(ctx contex
 	ca.log.AuditInfof("Signing cert: serial=[%s] regID=[%d] names=[%s] precert=[%s]",
 		serialHex, req.RegistrationID, names, hex.EncodeToString(precert.Raw))
 
-	_, issuanceToken, err := issuer.Prepare(issuanceReq)
+	_, issuanceToken, err := issuer.Prepare(ca.profile, issuanceReq)
 	if err != nil {
 		ca.log.AuditErrf("Preparing cert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
 			serialHex, req.RegistrationID, names, err)
@@ -439,7 +446,7 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
 		NotAfter:          validity.NotAfter,
 	}
 
-	lintCertBytes, issuanceToken, err := issuer.Prepare(req)
+	lintCertBytes, issuanceToken, err := issuer.Prepare(ca.profile, req)
 	if err != nil {
 		ca.log.AuditErrf("Preparing precert failed: serial=[%s] regID=[%d] names=[%s] err=[%v]",
 			serialHex, issueReq.RegistrationID, strings.Join(csr.DNSNames, ", "), err)
diff --git a/ca/crl.go b/ca/crl.go
index 3232ab419..4d62ca5a6 100644
--- a/ca/crl.go
+++ b/ca/crl.go
@@ -12,6 +12,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/zmap/zlint/v3/lint"
+
 	capb "github.com/letsencrypt/boulder/ca/proto"
 	"github.com/letsencrypt/boulder/core"
 	corepb "github.com/letsencrypt/boulder/core/proto"
@@ -22,7 +24,9 @@ import (
 
 type crlImpl struct {
 	capb.UnimplementedCRLGeneratorServer
-	issuers   map[issuance.NameID]*issuance.Issuer
+	issuers map[issuance.NameID]*issuance.Issuer
+	// TODO(#7159): Move this into the CRL profile
+	lints     lint.Registry
 	lifetime  time.Duration
 	idpBase   string
 	maxLogLen int
@@ -34,7 +38,7 @@ type crlImpl struct {
 // issue CRLs from. lifetime sets the validity period (inclusive) of the
 // resulting CRLs. idpBase is the base URL from which IssuingDistributionPoint
 // URIs will constructed; it must use the http:// scheme.
-func NewCRLImpl(issuers []*issuance.Issuer, lifetime time.Duration, idpBase string, maxLogLen int, logger blog.Logger) (*crlImpl, error) {
+func NewCRLImpl(issuers []*issuance.Issuer, lints lint.Registry, lifetime time.Duration, idpBase string, maxLogLen int, logger blog.Logger) (*crlImpl, error) {
 	issuersByNameID := make(map[issuance.NameID]*issuance.Issuer, len(issuers))
 	for _, issuer := range issuers {
 		issuersByNameID[issuer.NameID()] = issuer
@@ -58,6 +62,7 @@ func NewCRLImpl(issuers []*issuance.Issuer, lifetime time.Duration, idpBase stri
 
 	return &crlImpl{
 		issuers:   issuersByNameID,
+		lints:     lints,
 		lifetime:  lifetime,
 		idpBase:   idpBase,
 		maxLogLen: maxLogLen,
@@ -152,7 +157,7 @@ func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error
 
 	template.RevokedCertificateEntries = rcs
 
-	err = issuer.Linter.CheckCRL(template)
+	err = issuer.Linter.CheckCRL(template, ci.lints)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/boulder-ca/main.go b/cmd/boulder-ca/main.go
index f9deb2f41..112418256 100644
--- a/cmd/boulder-ca/main.go
+++ b/cmd/boulder-ca/main.go
@@ -18,7 +18,6 @@ import (
 	"github.com/letsencrypt/boulder/goodkey/sagoodkey"
 	bgrpc "github.com/letsencrypt/boulder/grpc"
 	"github.com/letsencrypt/boulder/issuance"
-	"github.com/letsencrypt/boulder/linter"
 	"github.com/letsencrypt/boulder/policy"
 	sapb "github.com/letsencrypt/boulder/sa/proto"
 )
@@ -115,34 +114,6 @@ type Config struct {
 	OpenTelemetry cmd.OpenTelemetryConfig
 }
 
-func loadBoulderIssuers(profileConfig issuance.ProfileConfig, issuerConfigs []issuance.IssuerConfig, ignoredLints []string) ([]*issuance.Issuer, error) {
-	issuers := make([]*issuance.Issuer, 0, len(issuerConfigs))
-	for _, issuerConfig := range issuerConfigs {
-		profile, err := issuance.NewProfile(profileConfig, issuerConfig)
-		if err != nil {
-			return nil, err
-		}
-
-		cert, signer, err := issuance.LoadIssuer(issuerConfig.Location)
-		if err != nil {
-			return nil, err
-		}
-
-		linter, err := linter.New(cert.Certificate, signer, ignoredLints)
-		if err != nil {
-			return nil, err
-		}
-
-		issuer, err := issuance.NewIssuer(cert, signer, profile, linter, cmd.Clock())
-		if err != nil {
-			return nil, err
-		}
-
-		issuers = append(issuers, issuer)
-	}
-	return issuers, nil
-}
-
 func main() {
 	grpcAddr := flag.String("addr", "", "gRPC listen address override")
 	debugAddr := flag.String("debug-addr", "", "Debug server address override")
@@ -212,9 +183,15 @@ func main() {
 		cmd.FailOnError(err, "Failed to load CT Log List")
 	}
 
-	var boulderIssuers []*issuance.Issuer
-	boulderIssuers, err = loadBoulderIssuers(c.CA.Issuance.Profile, c.CA.Issuance.Issuers, c.CA.Issuance.IgnoredLints)
-	cmd.FailOnError(err, "Couldn't load issuers")
+	issuers := make([]*issuance.Issuer, 0, len(c.CA.Issuance.Issuers))
+	for _, issuerConfig := range c.CA.Issuance.Issuers {
+		issuer, err := issuance.LoadIssuer(issuerConfig, cmd.Clock())
+		cmd.FailOnError(err, "Loading issuer")
+		issuers = append(issuers, issuer)
+	}
+
+	profile, err := issuance.NewProfile(c.CA.Issuance.Profile, c.CA.Issuance.IgnoredLints)
+	cmd.FailOnError(err, "Couldn't load issuance profile")
 
 	tlsConfig, err := c.CA.TLS.Load(scope)
 	cmd.FailOnError(err, "TLS config")
@@ -241,7 +218,7 @@ func main() {
 
 	if !c.CA.DisableOCSPService {
 		ocspi, err := ca.NewOCSPImpl(
-			boulderIssuers,
+			issuers,
 			c.CA.LifespanOCSP.Duration,
 			c.CA.OCSPLogMaxLength,
 			c.CA.OCSPLogPeriod.Duration,
@@ -260,7 +237,8 @@ func main() {
 
 	if !c.CA.DisableCRLService {
 		crli, err := ca.NewCRLImpl(
-			boulderIssuers,
+			issuers,
+			profile.Lints,
 			c.CA.LifespanCRL.Duration,
 			c.CA.CRLDPBase,
 			c.CA.OCSPLogMaxLength,
@@ -275,7 +253,8 @@ func main() {
 		cai, err := ca.NewCertificateAuthorityImpl(
 			sa,
 			pa,
-			boulderIssuers,
+			issuers,
+			profile,
 			ecdsaAllowList,
 			c.CA.Expiry.Duration,
 			c.CA.Backdate.Duration,
diff --git a/cmd/boulder-wfe2/main.go b/cmd/boulder-wfe2/main.go
index d39b4b425..3da9928e9 100644
--- a/cmd/boulder-wfe2/main.go
+++ b/cmd/boulder-wfe2/main.go
@@ -158,6 +158,13 @@ type Config struct {
 			// default rate limits.
 			Overrides string
 		}
+
+		// MaxNames is the maximum number of subjectAltNames in a single cert.
+		// The value supplied SHOULD be greater than 0 and no more than 100,
+		// defaults to 100. These limits are per section 7.1 of our combined
+		// CP/CPS, under "DV-SSL Subscriber Certificate". The value must match
+		// the CA and RA configurations.
+		MaxNames int `validate:"min=0,max=100"`
 	}
 
 	Syslog        cmd.SyslogConfig
@@ -299,6 +306,11 @@ func main() {
 	if *debugAddr != "" {
 		c.WFE.DebugAddr = *debugAddr
 	}
+	maxNames := c.WFE.MaxNames
+	if maxNames == 0 {
+		// Default to 100 names per cert.
+		maxNames = 100
+	}
 
 	certChains := map[issuance.NameID][][]byte{}
 	issuerCerts := map[issuance.NameID]*issuance.Certificate{}
@@ -393,6 +405,7 @@ func main() {
 		accountGetter,
 		limiter,
 		txnBuilder,
+		maxNames,
 	)
 	cmd.FailOnError(err, "Unable to create WFE")
 
diff --git a/cmd/config.go b/cmd/config.go
index 0ca4a9003..114e2397d 100644
--- a/cmd/config.go
+++ b/cmd/config.go
@@ -274,8 +274,8 @@ type GRPCClientConfig struct {
 	// services {
 	//   id      = "some-unique-id-2"
 	//   name    = "foo"
-	//   address = "10.88.88.88"
-	//   port    = 8080
+	//   address = "10.77.77.77"
+	//   port    = 8180
 	//   tags    = ["tcp"]
 	// }
 	//
@@ -532,15 +532,15 @@ type DNSProvider struct {
 	//   id      = "unbound-1" // Must be unique
 	//   name    = "unbound"
 	//   address = "10.77.77.77"
-	//   port    = 53
+	//   port    = 8053
 	//   tags    = ["udp"]
 	// }
 	//
 	// services {
 	//   id      = "unbound-2" // Must be unique
 	//   name    = "unbound"
-	//   address = "10.88.88.88"
-	//   port    = 53
+	//   address = "10.77.77.77"
+	//   port    = 8153
 	//   tags    = ["udp"]
 	// }
 	//
@@ -548,7 +548,7 @@ type DNSProvider struct {
 	// Consul) then you should be able to resolve the following dig query:
 	//
 	// $ dig @10.55.55.10 -t SRV _unbound._udp.service.consul +short
-	// 1 1 53 0a585858.addr.dc1.consul.
-	// 1 1 53 0a4d4d4d.addr.dc1.consul.
+	// 1 1 8053 0a4d4d4d.addr.dc1.consul.
+	// 1 1 8153 0a4d4d4d.addr.dc1.consul.
 	SRVLookup ServiceDomain `validate:"required"`
 }
diff --git a/core/objects.go b/core/objects.go
index b52f0f5e0..90a24a2d5 100644
--- a/core/objects.go
+++ b/core/objects.go
@@ -119,7 +119,7 @@ type Registration struct {
 }
 
 // ValidationRecord represents a validation attempt against a specific URL/hostname
-// and the IP addresses that were resolved and used
+// and the IP addresses that were resolved and used.
 type ValidationRecord struct {
 	// SimpleHTTP only
 	URL string `json:"url,omitempty"`
@@ -144,6 +144,10 @@ type ValidationRecord struct {
 	//   ...
 	// }
 	AddressesTried []net.IP `json:"addressesTried,omitempty"`
+	// ResolverAddrs is the host:port of the DNS resolver(s) that fulfilled the
+	// lookup for AddressUsed. During recursive A and AAAA lookups, a record may
+	// instead look like A:host:port or AAAA:host:port
+	ResolverAddrs []string `json:"resolverAddrs,omitempty"`
 }
 
 func looksLikeKeyAuthorization(str string) error {
@@ -225,6 +229,8 @@ func (ch Challenge) RecordsSane() bool {
 	switch ch.Type {
 	case ChallengeTypeHTTP01:
 		for _, rec := range ch.ValidationRecord {
+			// TODO(#7140): Add a check for ResolverAddress == "" only after the
+			// core.proto change has been deployed.
 			if rec.URL == "" || rec.Hostname == "" || rec.Port == "" || rec.AddressUsed == nil ||
 				len(rec.AddressesResolved) == 0 {
 				return false
@@ -237,6 +243,8 @@ func (ch Challenge) RecordsSane() bool {
 		if ch.ValidationRecord[0].URL != "" {
 			return false
 		}
+		// TODO(#7140): Add a check for ResolverAddress == "" only after the
+		// core.proto change has been deployed.
 		if ch.ValidationRecord[0].Hostname == "" || ch.ValidationRecord[0].Port == "" ||
 			ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
 			return false
@@ -245,6 +253,8 @@ func (ch Challenge) RecordsSane() bool {
 		if len(ch.ValidationRecord) > 1 {
 			return false
 		}
+		// TODO(#7140): Add a check for ResolverAddress == "" only after the
+		// core.proto change has been deployed.
 		if ch.ValidationRecord[0].Hostname == "" {
 			return false
 		}
diff --git a/core/proto/core.pb.go b/core/proto/core.pb.go
index b22c625bf..a2ae1ffb6 100644
--- a/core/proto/core.pb.go
+++ b/core/proto/core.pb.go
@@ -138,6 +138,7 @@ type ValidationRecord struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
+	// Next unused field number: 9
 	Hostname          string   `protobuf:"bytes,1,opt,name=hostname,proto3" json:"hostname,omitempty"`
 	Port              string   `protobuf:"bytes,2,opt,name=port,proto3" json:"port,omitempty"`
 	AddressesResolved [][]byte `protobuf:"bytes,3,rep,name=addressesResolved,proto3" json:"addressesResolved,omitempty"` // net.IP.MarshalText()
@@ -148,6 +149,7 @@ type ValidationRecord struct {
 	// core/objects.go and the comment on the ValidationRecord structure
 	// definition for more information.
 	AddressesTried [][]byte `protobuf:"bytes,7,rep,name=addressesTried,proto3" json:"addressesTried,omitempty"` // net.IP.MarshalText()
+	ResolverAddrs  []string `protobuf:"bytes,8,rep,name=resolverAddrs,proto3" json:"resolverAddrs,omitempty"`
 }
 
 func (x *ValidationRecord) Reset() {
@@ -231,6 +233,13 @@ func (x *ValidationRecord) GetAddressesTried() [][]byte {
 	return nil
 }
 
+func (x *ValidationRecord) GetResolverAddrs() []string {
+	if x != nil {
+		return x.ResolverAddrs
+	}
+	return nil
+}
+
 type ProblemDetails struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -898,7 +907,7 @@ var file_core_proto_rawDesc = []byte{
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
 	0x6d, 0x70, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x64, 0x4a, 0x04, 0x08,
 	0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x0b, 0x10, 0x0c, 0x22,
-	0xee, 0x01, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+	0x94, 0x02, 0x0a, 0x10, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
 	0x63, 0x6f, 0x72, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
 	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65,
 	0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
@@ -913,127 +922,129 @@ var file_core_proto_rawDesc = []byte{
 	0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x26, 0x0a, 0x0e, 0x61, 0x64, 0x64, 0x72,
 	0x65, 0x73, 0x73, 0x65, 0x73, 0x54, 0x72, 0x69, 0x65, 0x64, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0c,
 	0x52, 0x0e, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x54, 0x72, 0x69, 0x65, 0x64,
-	0x22, 0x6a, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69,
-	0x6c, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x79, 0x70,
-	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a,
-	0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xed, 0x01, 0x0a,
-	0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x26, 0x0a, 0x0e,
-	0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06,
-	0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69,
-	0x67, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x0c, 0x52, 0x03, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64,
-	0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
+	0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72,
+	0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65,
+	0x72, 0x41, 0x64, 0x64, 0x72, 0x73, 0x22, 0x6a, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65,
+	0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x62,
+	0x6c, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70,
+	0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x65,
+	0x74, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x65, 0x74, 0x61,
+	0x69, 0x6c, 0x12, 0x1e, 0x0a, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x68, 0x74, 0x74, 0x70, 0x53, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x22, 0xed, 0x01, 0x0a, 0x0b, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+	0x74, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69,
+	0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65,
+	0x72, 0x69, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69,
+	0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x65,
+	0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x64, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x06,
+	0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
+	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
+	0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64,
+	0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65,
+	0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06,
+	0x10, 0x07, 0x22, 0xd5, 0x03, 0x0a, 0x11, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61,
+	0x74, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69,
+	0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c,
+	0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x44, 0x0a, 0x0f, 0x6f, 0x63, 0x73, 0x70,
+	0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x6f,
+	0x63, 0x73, 0x70, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x3c,
+	0x0a, 0x0b, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
+	0x0b, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0d,
+	0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x03, 0x52, 0x0d, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73,
+	0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x67, 0x53, 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x15, 0x6c,
+	0x61, 0x73, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x67,
+	0x53, 0x65, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72,
+	0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61,
-	0x6d, 0x70, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x64, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78,
-	0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
-	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73,
-	0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x22, 0xd5, 0x03, 0x0a,
-	0x11, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x74, 0x61, 0x74,
-	0x75, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74,
-	0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74,
-	0x75, 0x73, 0x12, 0x44, 0x0a, 0x0f, 0x6f, 0x63, 0x73, 0x70, 0x4c, 0x61, 0x73, 0x74, 0x55, 0x70,
-	0x64, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
-	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x6f, 0x63, 0x73, 0x70, 0x4c, 0x61, 0x73,
-	0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x3c, 0x0a, 0x0b, 0x72, 0x65, 0x76, 0x6f,
-	0x6b, 0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
-	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
-	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0b, 0x72, 0x65, 0x76, 0x6f, 0x6b,
-	0x65, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65,
-	0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x72,
-	0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x50, 0x0a, 0x15,
-	0x6c, 0x61, 0x73, 0x74, 0x45, 0x78, 0x70, 0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61,
-	0x67, 0x53, 0x65, 0x6e, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
-	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x15, 0x6c, 0x61, 0x73, 0x74, 0x45, 0x78, 0x70,
-	0x69, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x61, 0x67, 0x53, 0x65, 0x6e, 0x74, 0x12, 0x36,
-	0x0a, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b,
+	0x6d, 0x70, 0x52, 0x08, 0x6e, 0x6f, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09,
+	0x69, 0x73, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52,
+	0x09, 0x69, 0x73, 0x45, 0x78, 0x70, 0x69, 0x72, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73,
+	0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x73,
+	0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x04,
+	0x10, 0x05, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04,
+	0x08, 0x08, 0x10, 0x09, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x88, 0x02, 0x0a, 0x0c, 0x52,
+	0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b,
+	0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a,
+	0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07,
+	0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61,
+	0x63, 0x74, 0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e,
+	0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12,
+	0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x50, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x0c, 0x52, 0x09, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x50, 0x12, 0x38, 0x0a,
+	0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b,
 	0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62,
-	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x6e, 0x6f,
-	0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x73, 0x45, 0x78, 0x70, 0x69,
-	0x72, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x45, 0x78, 0x70,
-	0x69, 0x72, 0x65, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44,
-	0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44,
-	0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, 0x08, 0x05,
-	0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x4a, 0x04,
-	0x08, 0x09, 0x10, 0x0a, 0x22, 0x88, 0x02, 0x0a, 0x0c, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61,
-	0x63, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63,
-	0x74, 0x12, 0x28, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x50, 0x72, 0x65,
-	0x73, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, 0x6f, 0x6e, 0x74,
-	0x61, 0x63, 0x74, 0x73, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x61,
-	0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
-	0x61, 0x67, 0x72, 0x65, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x69, 0x6e, 0x69,
-	0x74, 0x69, 0x61, 0x6c, 0x49, 0x50, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x69, 0x6e,
-	0x69, 0x74, 0x69, 0x61, 0x6c, 0x49, 0x50, 0x12, 0x38, 0x0a, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
-	0x65, 0x64, 0x41, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f,
-	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d,
-	0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41,
-	0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x22,
-	0xf8, 0x01, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69,
-	0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65,
-	0x72, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
-	0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61,
-	0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
-	0x73, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01,
-	0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07,
-	0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c,
-	0x65, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f,
-	0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, 0x68,
-	0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04,
-	0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, 0x22, 0x9b, 0x03, 0x0a, 0x05, 0x4f,
-	0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03,
-	0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65,
-	0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x34, 0x0a, 0x07,
-	0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
+	0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72,
+	0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75,
+	0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x4a,
+	0x04, 0x08, 0x07, 0x10, 0x08, 0x22, 0xf8, 0x01, 0x0a, 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72,
+	0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74,
+	0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65,
+	0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
+	0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
+	0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12,
+	0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72,
+	0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73,
+	0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x2f, 0x0a,
+	0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e,
+	0x67, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x4a, 0x04,
+	0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09,
+	0x22, 0x9b, 0x03, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65,
+	0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x49, 0x44, 0x12, 0x34, 0x0a, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x18, 0x0c, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52,
+	0x07, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
+	0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x50,
+	0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x05, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
+	0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69,
+	0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61,
+	0x6d, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73,
+	0x12, 0x28, 0x0a, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+	0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e,
+	0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72,
+	0x65, 0x61, 0x74, 0x65, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
+	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69,
+	0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
+	0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x76, 0x32, 0x41, 0x75,
+	0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x04, 0x08, 0x03,
+	0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x22, 0x7a,
+	0x0a, 0x08, 0x43, 0x52, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65,
+	0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69,
+	0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65,
+	0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e,
 	0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
-	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x65, 0x78, 0x70, 0x69, 0x72,
-	0x65, 0x73, 0x12, 0x2a, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
-	0x0b, 0x32, 0x14, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x62, 0x6c, 0x65, 0x6d,
-	0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x2c,
-	0x0a, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72,
-	0x69, 0x61, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x63, 0x65, 0x72, 0x74, 0x69,
-	0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06,
-	0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74,
-	0x61, 0x74, 0x75, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x08, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x0f, 0x62, 0x65,
-	0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20,
-	0x01, 0x28, 0x08, 0x52, 0x0f, 0x62, 0x65, 0x67, 0x61, 0x6e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73,
-	0x73, 0x69, 0x6e, 0x67, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x18,
-	0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d,
-	0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32,
-	0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b,
-	0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06,
-	0x10, 0x07, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x22, 0x7a, 0x0a, 0x08, 0x43, 0x52, 0x4c, 0x45,
-	0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06,
-	0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x65,
-	0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41,
-	0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74,
-	0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04,
-	0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63,
-	0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62,
-	0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b,
+	0x65, 0x64, 0x41, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69,
+	0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63,
+	0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72,
+	0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/core/proto/core.proto b/core/proto/core.proto
index 5b5b639b1..e4f488a36 100644
--- a/core/proto/core.proto
+++ b/core/proto/core.proto
@@ -22,6 +22,7 @@ message Challenge {
 }
 
 message ValidationRecord {
+  // Next unused field number: 9
   string hostname = 1;
   string port = 2;
   repeated bytes addressesResolved = 3; // net.IP.MarshalText()
@@ -33,6 +34,7 @@ message ValidationRecord {
   // core/objects.go and the comment on the ValidationRecord structure
   // definition for more information.
   repeated bytes addressesTried = 7; // net.IP.MarshalText()
+  repeated string resolverAddrs = 8;
 }
 
 message ProblemDetails {
diff --git a/docker-compose.yml b/docker-compose.yml
index 6a918c2e2..7fc8f8de5 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -136,8 +136,6 @@ services:
         ipv4_address: 10.55.55.10
       bluenet:
         ipv4_address: 10.77.77.10
-      rednet:
-        ipv4_address: 10.88.88.10
     command: "consul agent -dev -config-format=hcl -config-file=/test/consul/config.hcl"
 
   bjaeger:
diff --git a/go.mod b/go.mod
index a3232754f..2a8790de7 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,7 @@ require (
 	github.com/letsencrypt/challtestsrv v1.2.1
 	github.com/letsencrypt/pkcs11key/v4 v4.0.0
 	github.com/letsencrypt/validator/v10 v10.0.0-20230215210743-a0c7dfc17158
-	github.com/miekg/dns v1.1.55
+	github.com/miekg/dns v1.1.58
 	github.com/miekg/pkcs11 v1.1.1
 	github.com/nxadm/tail v1.4.11
 	github.com/prometheus/client_golang v1.15.1
@@ -36,7 +36,7 @@ require (
 	go.opentelemetry.io/otel/trace v1.21.0
 	golang.org/x/crypto v0.18.0
 	golang.org/x/net v0.20.0
-	golang.org/x/sync v0.4.0
+	golang.org/x/sync v0.6.0
 	golang.org/x/term v0.16.0
 	golang.org/x/text v0.14.0
 	google.golang.org/grpc v1.60.1
@@ -80,9 +80,9 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect
 	go.opentelemetry.io/otel/metric v1.21.0 // indirect
 	go.opentelemetry.io/proto/otlp v1.0.0 // indirect
-	golang.org/x/mod v0.10.0 // indirect
+	golang.org/x/mod v0.14.0 // indirect
 	golang.org/x/sys v0.16.0 // indirect
-	golang.org/x/tools v0.8.0 // indirect
+	golang.org/x/tools v0.17.0 // indirect
 	google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20231212172506-995d672761c0 // indirect
 	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
diff --git a/go.sum b/go.sum
index db3d8b639..2b9efe656 100644
--- a/go.sum
+++ b/go.sum
@@ -177,8 +177,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
-github.com/miekg/dns v1.1.55 h1:GoQ4hpsj0nFLYe+bWiCToyrBEJXkQfOOIvFGFy0lEgo=
-github.com/miekg/dns v1.1.55/go.mod h1:uInx36IzPl7FYnDcMeVWxj9byh7DutNykX4G9Sj60FY=
+github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
+github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
 github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
 github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
 github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@@ -307,8 +307,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
-golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
+golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -340,8 +340,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
-golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -397,8 +397,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.0.0-20200313205530-4303120df7d8/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
-golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
-golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/grpc/pb-marshalling.go b/grpc/pb-marshalling.go
index 2900c21f6..33c2a5b63 100644
--- a/grpc/pb-marshalling.go
+++ b/grpc/pb-marshalling.go
@@ -151,6 +151,7 @@ func ValidationRecordToPB(record core.ValidationRecord) (*corepb.ValidationRecor
 		AddressUsed:       addrUsed,
 		Url:               record.URL,
 		AddressesTried:    addrsTried,
+		ResolverAddrs:     record.ResolverAddrs,
 	}, nil
 }
 
@@ -178,6 +179,7 @@ func PBToValidationRecord(in *corepb.ValidationRecord) (record core.ValidationRe
 		AddressUsed:       addrUsed,
 		URL:               in.Url,
 		AddressesTried:    addrsTried,
+		ResolverAddrs:     in.ResolverAddrs,
 	}, nil
 }
 
diff --git a/issuance/cert.go b/issuance/cert.go
index 1545bf213..b91e40cd7 100644
--- a/issuance/cert.go
+++ b/issuance/cert.go
@@ -19,8 +19,10 @@ import (
 	cttls "github.com/google/certificate-transparency-go/tls"
 	ctx509 "github.com/google/certificate-transparency-go/x509"
 	"github.com/jmhodges/clock"
+	"github.com/zmap/zlint/v3/lint"
 
 	"github.com/letsencrypt/boulder/config"
+	"github.com/letsencrypt/boulder/linter"
 	"github.com/letsencrypt/boulder/precert"
 )
 
@@ -45,45 +47,34 @@ type PolicyConfig struct {
 
 // Profile is the validated structure created by reading in ProfileConfigs and IssuerConfigs
 type Profile struct {
-	useForRSALeaves   bool
-	useForECDSALeaves bool
-
 	allowMustStaple bool
 	allowCTPoison   bool
 	allowSCTList    bool
 	allowCommonName bool
 
-	sigAlg    x509.SignatureAlgorithm
-	ocspURL   string
-	crlURL    string
-	issuerURL string
-
 	maxBackdate time.Duration
 	maxValidity time.Duration
+
+	// TODO(#7159): Make this private when CRLs have their own profile.
+	Lints lint.Registry
 }
 
 // NewProfile synthesizes the profile config and issuer config into a single
 // object, and checks various aspects for correctness.
-func NewProfile(profileConfig ProfileConfig, issuerConfig IssuerConfig) (*Profile, error) {
-	if issuerConfig.IssuerURL == "" {
-		return nil, errors.New("Issuer URL is required")
-	}
-	if issuerConfig.OCSPURL == "" {
-		return nil, errors.New("OCSP URL is required")
+func NewProfile(profileConfig ProfileConfig, skipLints []string) (*Profile, error) {
+	reg, err := linter.NewRegistry(skipLints)
+	if err != nil {
+		return nil, fmt.Errorf("creating lint registry: %w", err)
 	}
 
 	sp := &Profile{
-		useForRSALeaves:   issuerConfig.UseForRSALeaves,
-		useForECDSALeaves: issuerConfig.UseForECDSALeaves,
-		allowMustStaple:   profileConfig.AllowMustStaple,
-		allowCTPoison:     profileConfig.AllowCTPoison,
-		allowSCTList:      profileConfig.AllowSCTList,
-		allowCommonName:   profileConfig.AllowCommonName,
-		issuerURL:         issuerConfig.IssuerURL,
-		crlURL:            issuerConfig.CRLURL,
-		ocspURL:           issuerConfig.OCSPURL,
-		maxBackdate:       profileConfig.MaxValidityBackdate.Duration,
-		maxValidity:       profileConfig.MaxValidityPeriod.Duration,
+		allowMustStaple: profileConfig.AllowMustStaple,
+		allowCTPoison:   profileConfig.AllowCTPoison,
+		allowSCTList:    profileConfig.AllowSCTList,
+		allowCommonName: profileConfig.AllowCommonName,
+		maxBackdate:     profileConfig.MaxValidityBackdate.Duration,
+		maxValidity:     profileConfig.MaxValidityPeriod.Duration,
+		Lints:           reg,
 	}
 
 	return sp, nil
@@ -91,14 +82,14 @@ func NewProfile(profileConfig ProfileConfig, issuerConfig IssuerConfig) (*Profil
 
 // requestValid verifies the passed IssuanceRequest against the profile. If the
 // request doesn't match the signing profile an error is returned.
-func (p *Profile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
+func (i *Issuer) requestValid(clk clock.Clock, prof *Profile, req *IssuanceRequest) error {
 	switch req.PublicKey.(type) {
 	case *rsa.PublicKey:
-		if !p.useForRSALeaves {
+		if !i.useForRSALeaves {
 			return errors.New("cannot sign RSA public keys")
 		}
 	case *ecdsa.PublicKey:
-		if !p.useForECDSALeaves {
+		if !i.useForECDSALeaves {
 			return errors.New("cannot sign ECDSA public keys")
 		}
 	default:
@@ -109,15 +100,15 @@ func (p *Profile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
 		return errors.New("unexpected subject key ID length")
 	}
 
-	if !p.allowMustStaple && req.IncludeMustStaple {
+	if !prof.allowMustStaple && req.IncludeMustStaple {
 		return errors.New("must-staple extension cannot be included")
 	}
 
-	if !p.allowCTPoison && req.IncludeCTPoison {
+	if !prof.allowCTPoison && req.IncludeCTPoison {
 		return errors.New("ct poison extension cannot be included")
 	}
 
-	if !p.allowSCTList && req.sctList != nil {
+	if !prof.allowSCTList && req.sctList != nil {
 		return errors.New("sct list extension cannot be included")
 	}
 
@@ -125,7 +116,7 @@ func (p *Profile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
 		return errors.New("cannot include both ct poison and sct list extensions")
 	}
 
-	if !p.allowCommonName && req.CommonName != "" {
+	if !prof.allowCommonName && req.CommonName != "" {
 		return errors.New("common name cannot be included")
 	}
 
@@ -135,12 +126,12 @@ func (p *Profile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
 	if validity <= 0 {
 		return errors.New("NotAfter must be after NotBefore")
 	}
-	if validity > p.maxValidity {
-		return fmt.Errorf("validity period is more than the maximum allowed period (%s>%s)", validity, p.maxValidity)
+	if validity > prof.maxValidity {
+		return fmt.Errorf("validity period is more than the maximum allowed period (%s>%s)", validity, prof.maxValidity)
 	}
 	backdatedBy := clk.Now().Sub(req.NotBefore)
-	if backdatedBy > p.maxBackdate {
-		return fmt.Errorf("NotBefore is backdated more than the maximum allowed period (%s>%s)", backdatedBy, p.maxBackdate)
+	if backdatedBy > prof.maxBackdate {
+		return fmt.Errorf("NotBefore is backdated more than the maximum allowed period (%s>%s)", backdatedBy, prof.maxBackdate)
 	}
 	if backdatedBy < 0 {
 		return errors.New("NotBefore is in the future")
@@ -156,24 +147,22 @@ func (p *Profile) requestValid(clk clock.Clock, req *IssuanceRequest) error {
 	return nil
 }
 
-var defaultEKU = []x509.ExtKeyUsage{
-	x509.ExtKeyUsageServerAuth,
-	x509.ExtKeyUsageClientAuth,
-}
-
-func (p *Profile) generateTemplate() *x509.Certificate {
+func (i *Issuer) generateTemplate() *x509.Certificate {
 	template := &x509.Certificate{
-		SignatureAlgorithm:    p.sigAlg,
-		ExtKeyUsage:           defaultEKU,
-		OCSPServer:            []string{p.ocspURL},
-		IssuingCertificateURL: []string{p.issuerURL},
+		SignatureAlgorithm: i.sigAlg,
+		ExtKeyUsage: []x509.ExtKeyUsage{
+			x509.ExtKeyUsageServerAuth,
+			x509.ExtKeyUsageClientAuth,
+		},
+		OCSPServer:            []string{i.ocspURL},
+		IssuingCertificateURL: []string{i.issuerURL},
 		BasicConstraintsValid: true,
 		// Baseline Requirements, Section 7.1.6.1: domain-validated
 		PolicyIdentifiers: []asn1.ObjectIdentifier{{2, 23, 140, 1, 2, 1}},
 	}
 
-	if p.crlURL != "" {
-		template.CRLDistributionPoints = []string{p.crlURL}
+	if i.crlURL != "" {
+		template.CRLDistributionPoints = []string{i.crlURL}
 	}
 
 	return template
@@ -261,20 +250,21 @@ type issuanceToken struct {
 	issuer *Issuer
 }
 
-// Prepare applies this Issuer's profile to create a template certificate. It
-// then generates a linting certificate from that template and runs the linter
-// over it. If successful, returns both the linting certificate (which can be
-// stored) and an issuanceToken. The issuanceToken can be used to sign a
-// matching certificate with this Issuer's private key.
-func (i *Issuer) Prepare(req *IssuanceRequest) ([]byte, *issuanceToken, error) {
+// Prepare combines the given profile and request with the Issuer's information
+// to create a template certificate. It then generates a linting certificate
+// from that template and runs the linter over it. If successful, returns both
+// the linting certificate (which can be stored) and an issuanceToken. The
+// issuanceToken can be used to sign a matching certificate with this Issuer's
+// private key.
+func (i *Issuer) Prepare(prof *Profile, req *IssuanceRequest) ([]byte, *issuanceToken, error) {
 	// check request is valid according to the issuance profile
-	err := i.Profile.requestValid(i.Clk, req)
+	err := i.requestValid(i.Clk, prof, req)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	// generate template from the issuance profile
-	template := i.Profile.generateTemplate()
+	// generate template from the issuer's data
+	template := i.generateTemplate()
 
 	// populate template from the issuance request
 	template.NotBefore, template.NotAfter = req.NotBefore, req.NotAfter
@@ -314,7 +304,7 @@ func (i *Issuer) Prepare(req *IssuanceRequest) ([]byte, *issuanceToken, error) {
 
 	// check that the tbsCertificate is properly formed by signing it
 	// with a throwaway key and then linting it using zlint
-	lintCertBytes, err := i.Linter.Check(template, req.PublicKey)
+	lintCertBytes, err := i.Linter.Check(template, req.PublicKey, prof.Lints)
 	if err != nil {
 		return nil, nil, fmt.Errorf("tbsCertificate linting failed: %w", err)
 	}
@@ -352,6 +342,9 @@ func (i *Issuer) Issue(token *issuanceToken) ([]byte, error) {
 	return x509.CreateCertificate(rand.Reader, template, i.Cert.Certificate, token.pubKey, i.Signer)
 }
 
+// ContainsMustStaple returns true if the provided set of extensions includes
+// an entry whose OID and value both match the expected values for the OCSP
+// Must-Staple (a.k.a. id-pe-tlsFeature) extension.
 func ContainsMustStaple(extensions []pkix.Extension) bool {
 	for _, ext := range extensions {
 		if ext.Id.Equal(mustStapleExt.Id) && bytes.Equal(ext.Value, mustStapleExt.Value) {
@@ -361,6 +354,9 @@ func ContainsMustStaple(extensions []pkix.Extension) bool {
 	return false
 }
 
+// containsCTPoison returns true if the provided set of extensions includes
+// an entry whose OID and value both match the expected values for the CT
+// Poison extension.
 func containsCTPoison(extensions []pkix.Extension) bool {
 	for _, ext := range extensions {
 		if ext.Id.Equal(ctPoisonExt.Id) && bytes.Equal(ext.Value, asn1.NullBytes) {
diff --git a/issuance/issuer.go b/issuance/issuer.go
index 635144f22..74b67017e 100644
--- a/issuance/issuer.go
+++ b/issuance/issuer.go
@@ -97,7 +97,7 @@ func NewCertificate(ic *x509.Certificate) (*Certificate, error) {
 func LoadCertificate(path string) (*Certificate, error) {
 	cert, err := core.LoadCert(path)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("loading issuer certificate: %w", err)
 	}
 	return NewCertificate(cert)
 }
@@ -178,51 +178,83 @@ type IssuerLoc struct {
 	NumSessions int
 }
 
-// Issuer is capable of issuing new certificates
-// TODO(#5086): make Cert and Signer private when they're no longer needed by ca.internalIssuer
+// Issuer is capable of issuing new certificates.
 type Issuer struct {
-	Cert    *Certificate
-	Signer  crypto.Signer
-	Profile *Profile
-	Linter  *linter.Linter
-	Clk     clock.Clock
+	// TODO(#7159): make Cert, Signer, and Linter private when all signing ops
+	// are handled through this package (e.g. the CA doesn't need direct access
+	// while signing CRLs anymore).
+	Cert   *Certificate
+	Signer crypto.Signer
+	Linter *linter.Linter
+
+	sigAlg            x509.SignatureAlgorithm
+	useForRSALeaves   bool
+	useForECDSALeaves bool
+
+	issuerURL string
+	ocspURL   string
+	crlURL    string
+
+	// TODO(#7159): Make Clk private by giving ca_test.go a better way to build
+	// in-memory Issuers.
+	Clk clock.Clock
 }
 
-// NewIssuer constructs an Issuer on the heap, verifying that the profile
-// is well-formed.
-func NewIssuer(cert *Certificate, signer crypto.Signer, profile *Profile, linter *linter.Linter, clk clock.Clock) (*Issuer, error) {
+// newIssuer constructs a new Issuer from the in-memory certificate and signer.
+// It exists as a helper for LoadIssuer to make testing simpler.
+func newIssuer(config IssuerConfig, cert *Certificate, signer crypto.Signer, clk clock.Clock) (*Issuer, error) {
+	var sigAlg x509.SignatureAlgorithm
 	switch k := cert.PublicKey.(type) {
 	case *rsa.PublicKey:
-		profile.sigAlg = x509.SHA256WithRSA
+		sigAlg = x509.SHA256WithRSA
 	case *ecdsa.PublicKey:
 		switch k.Curve {
 		case elliptic.P256():
-			profile.sigAlg = x509.ECDSAWithSHA256
+			sigAlg = x509.ECDSAWithSHA256
 		case elliptic.P384():
-			profile.sigAlg = x509.ECDSAWithSHA384
+			sigAlg = x509.ECDSAWithSHA384
 		default:
-			return nil, fmt.Errorf("unsupported ECDSA curve: %s", k.Curve.Params().Name)
+			return nil, fmt.Errorf("unsupported ECDSA curve: %q", k.Curve.Params().Name)
 		}
 	default:
 		return nil, errors.New("unsupported issuer key type")
 	}
 
-	if profile.useForRSALeaves || profile.useForECDSALeaves {
-		if cert.KeyUsage&x509.KeyUsageCertSign == 0 {
-			return nil, errors.New("end-entity signing cert does not have keyUsage certSign")
-		}
+	if config.IssuerURL == "" {
+		return nil, errors.New("Issuer URL is required")
+	}
+	if config.OCSPURL == "" {
+		return nil, errors.New("OCSP URL is required")
+	}
+
+	// We require that all of our issuers be capable of both issuing certs and
+	// providing revocation information.
+	if cert.KeyUsage&x509.KeyUsageCertSign == 0 {
+		return nil, errors.New("end-entity signing cert does not have keyUsage certSign")
+	}
+	if cert.KeyUsage&x509.KeyUsageCRLSign == 0 {
+		return nil, errors.New("end-entity signing cert does not have keyUsage crlSign")
 	}
-	// TODO(#5086): Only do this check for ocsp-issuing issuers.
 	if cert.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
-		return nil, errors.New("end-entity ocsp signing cert does not have keyUsage digitalSignature")
+		return nil, errors.New("end-entity signing cert does not have keyUsage digitalSignature")
+	}
+
+	lintSigner, err := linter.New(cert.Certificate, signer)
+	if err != nil {
+		return nil, fmt.Errorf("creating fake lint signer: %w", err)
 	}
 
 	i := &Issuer{
-		Cert:    cert,
-		Signer:  signer,
-		Profile: profile,
-		Linter:  linter,
-		Clk:     clk,
+		Cert:              cert,
+		Signer:            signer,
+		Linter:            lintSigner,
+		sigAlg:            sigAlg,
+		useForRSALeaves:   config.UseForRSALeaves,
+		useForECDSALeaves: config.UseForECDSALeaves,
+		issuerURL:         config.IssuerURL,
+		ocspURL:           config.OCSPURL,
+		crlURL:            config.CRLURL,
+		Clk:               clk,
 	}
 	return i, nil
 }
@@ -232,10 +264,10 @@ func NewIssuer(cert *Certificate, signer crypto.Signer, profile *Profile, linter
 // public key algorithm or signature algorithm in this issuer's own cert.
 func (i *Issuer) Algs() []x509.PublicKeyAlgorithm {
 	var algs []x509.PublicKeyAlgorithm
-	if i.Profile.useForRSALeaves {
+	if i.useForRSALeaves {
 		algs = append(algs, x509.RSA)
 	}
-	if i.Profile.useForECDSALeaves {
+	if i.useForECDSALeaves {
 		algs = append(algs, x509.ECDSA)
 	}
 	return algs
@@ -251,25 +283,32 @@ func (i *Issuer) NameID() NameID {
 	return i.Cert.NameID()
 }
 
-// LoadIssuer loads a signer (private key) and certificate from the locations specified.
-func LoadIssuer(location IssuerLoc) (*Certificate, crypto.Signer, error) {
-	issuerCert, err := LoadCertificate(location.CertFile)
+// LoadIssuer constructs a new Issuer, loading its certificate from disk and its
+// private key material from the indicated location. It also verifies that the
+// issuer metadata (such as AIA URLs) is well-formed.
+func LoadIssuer(config IssuerConfig, clk clock.Clock) (*Issuer, error) {
+	issuerCert, err := LoadCertificate(config.Location.CertFile)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
-	signer, err := loadSigner(location, issuerCert)
+	signer, err := loadSigner(config.Location, issuerCert.PublicKey)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 
 	if !core.KeyDigestEquals(signer.Public(), issuerCert.PublicKey) {
-		return nil, nil, fmt.Errorf("Issuer key did not match issuer cert %s", location.CertFile)
+		return nil, fmt.Errorf("issuer key did not match issuer cert %q", config.Location.CertFile)
 	}
-	return issuerCert, signer, err
+
+	return newIssuer(config, issuerCert, signer, clk)
 }
 
-func loadSigner(location IssuerLoc, cert *Certificate) (crypto.Signer, error) {
+func loadSigner(location IssuerLoc, pubkey crypto.PublicKey) (crypto.Signer, error) {
+	if location.File == "" && location.ConfigFile == "" && location.PKCS11 == nil {
+		return nil, errors.New("must supply File, ConfigFile, or PKCS11")
+	}
+
 	if location.File != "" {
 		signer, _, err := privatekey.Load(location.File)
 		if err != nil {
@@ -305,5 +344,5 @@ func loadSigner(location IssuerLoc, cert *Certificate) (crypto.Signer, error) {
 	}
 
 	return pkcs11key.NewPool(numSessions, pkcs11Config.Module,
-		pkcs11Config.TokenLabel, pkcs11Config.PIN, cert.PublicKey)
+		pkcs11Config.TokenLabel, pkcs11Config.PIN, pubkey)
 }
diff --git a/linter/linter.go b/linter/linter.go
index b7a9d11d5..07ac8b029 100644
--- a/linter/linter.go
+++ b/linter/linter.go
@@ -32,12 +32,17 @@ var ErrLinting = fmt.Errorf("failed lint(s)")
 // a new signer and a new lint registry are expensive operations which
 // performance-sensitive clients may want to cache via linter.New().
 func Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) ([]byte, error) {
-	linter, err := New(realIssuer, realSigner, skipLints)
+	linter, err := New(realIssuer, realSigner)
 	if err != nil {
 		return nil, err
 	}
 
-	lintCertBytes, err := linter.Check(tbs, subjectPubKey)
+	reg, err := NewRegistry(skipLints)
+	if err != nil {
+		return nil, err
+	}
+
+	lintCertBytes, err := linter.Check(tbs, subjectPubKey, reg)
 	if err != nil {
 		return nil, err
 	}
@@ -47,11 +52,17 @@ func Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, realIssuer *x5
 
 // CheckCRL is like Check, but for CRLs.
 func CheckCRL(tbs *x509.RevocationList, realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) error {
-	linter, err := New(realIssuer, realSigner, skipLints)
+	linter, err := New(realIssuer, realSigner)
+	if err != nil {
+		return err
+	}
+
+	reg, err := NewRegistry(skipLints)
 	if err != nil {
 		return err
 	}
-	return linter.CheckCRL(tbs)
+
+	return linter.CheckCRL(tbs, reg)
 }
 
 // Linter is capable of linting a to-be-signed (TBS) certificate. It does so by
@@ -61,7 +72,6 @@ func CheckCRL(tbs *x509.RevocationList, realIssuer *x509.Certificate, realSigner
 type Linter struct {
 	issuer     *x509.Certificate
 	signer     crypto.Signer
-	registry   lint.Registry
 	realPubKey crypto.PublicKey
 }
 
@@ -70,7 +80,7 @@ type Linter struct {
 // be used to sign the lint certificate. It uses the provided list of lint names
 // to skip to filter the zlint global registry to only those lints which should
 // be run.
-func New(realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []string) (*Linter, error) {
+func New(realIssuer *x509.Certificate, realSigner crypto.Signer) (*Linter, error) {
 	lintSigner, err := makeSigner(realSigner)
 	if err != nil {
 		return nil, err
@@ -79,21 +89,17 @@ func New(realIssuer *x509.Certificate, realSigner crypto.Signer, skipLints []str
 	if err != nil {
 		return nil, err
 	}
-	reg, err := makeRegistry(skipLints)
-	if err != nil {
-		return nil, err
-	}
-	return &Linter{lintIssuer, lintSigner, reg, realSigner.Public()}, nil
+	return &Linter{lintIssuer, lintSigner, realSigner.Public()}, nil
 }
 
 // Check signs the given TBS certificate using the Linter's fake issuer cert and
-// private key, then runs the resulting certificate through all non-filtered
-// lints. If the subjectPubKey is identical to the public key of the real signer
+// private key, then runs the resulting certificate through all lints in reg.
+// If the subjectPubKey is identical to the public key of the real signer
 // used to create this linter, then the throwaway cert will have its pubkey
 // replaced with the linter's pubkey so that it appears self-signed. It returns
 // an error if any lint fails. On success it also returns the DER bytes of the
 // linting certificate.
-func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) ([]byte, error) {
+func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey, reg lint.Registry) ([]byte, error) {
 	lintPubKey := subjectPubKey
 	selfSigned, err := core.PublicKeysEqual(subjectPubKey, l.realPubKey)
 	if err != nil {
@@ -108,7 +114,7 @@ func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) ([]
 		return nil, err
 	}
 
-	lintRes := zlint.LintCertificateEx(cert, l.registry)
+	lintRes := zlint.LintCertificateEx(cert, reg)
 	err = ProcessResultSet(lintRes)
 	if err != nil {
 		return nil, err
@@ -118,14 +124,14 @@ func (l Linter) Check(tbs *x509.Certificate, subjectPubKey crypto.PublicKey) ([]
 }
 
 // CheckCRL signs the given RevocationList template using the Linter's fake
-// issuer cert and private key, then runs the resulting CRL through our suite
-// of CRL checks. It returns an error if any check fails.
-func (l Linter) CheckCRL(tbs *x509.RevocationList) error {
+// issuer cert and private key, then runs the resulting CRL through all CRL
+// lints in the registry. It returns an error if any check fails.
+func (l Linter) CheckCRL(tbs *x509.RevocationList, reg lint.Registry) error {
 	crl, err := makeLintCRL(tbs, l.issuer, l.signer)
 	if err != nil {
 		return err
 	}
-	lintRes := zlint.LintRevocationListEx(crl, l.registry)
+	lintRes := zlint.LintRevocationListEx(crl, reg)
 	return ProcessResultSet(lintRes)
 }
 
@@ -205,7 +211,9 @@ func makeIssuer(realIssuer *x509.Certificate, lintSigner crypto.Signer) (*x509.C
 	return lintIssuer, nil
 }
 
-func makeRegistry(skipLints []string) (lint.Registry, error) {
+// NewRegistry returns a zlint Registry with irrelevant (ETSI, EV) lints
+// excluded. This registry also includes all custom lints defined in Boulder.
+func NewRegistry(skipLints []string) (lint.Registry, error) {
 	reg, err := lint.GlobalRegistry().Filter(lint.FilterOptions{
 		ExcludeNames: skipLints,
 		ExcludeSources: []lint.LintSource{
diff --git a/publisher/publisher.go b/publisher/publisher.go
index d0b671b2d..2cdaac379 100644
--- a/publisher/publisher.go
+++ b/publisher/publisher.go
@@ -96,12 +96,15 @@ func NewLog(uri, b64PK, userAgent string, logger blog.Logger) (*Log, error) {
 	}
 	url.Path = strings.TrimSuffix(url.Path, "/")
 
-	pemPK := fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----",
-		b64PK)
+	derPK, err := base64.StdEncoding.DecodeString(b64PK)
+	if err != nil {
+		return nil, err
+	}
+
 	opts := jsonclient.Options{
-		Logger:    logAdaptor{logger},
-		PublicKey: pemPK,
-		UserAgent: userAgent,
+		Logger:       logAdaptor{logger},
+		PublicKeyDER: derPK,
+		UserAgent:    userAgent,
 	}
 	httpClient := &http.Client{
 		// We set the HTTP client timeout to about half of what we expect
diff --git a/ra/proto/ra.pb.go b/ra/proto/ra.pb.go
index cbe1c487c..d2da05fcc 100644
--- a/ra/proto/ra.pb.go
+++ b/ra/proto/ra.pb.go
@@ -358,16 +358,17 @@ type AdministrativelyRevokeCertificateRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	// The `cert` field may be omitted. If it is omitted,
-	// the revocation reason (`code`) must not be keyCompromise,
-	// and purging the Akamai cache will not happen because the
-	// base URL for the certificate's OCSP server is not known.
+	// Deprecated: this field is ignored.
 	Cert []byte `protobuf:"bytes,1,opt,name=cert,proto3" json:"cert,omitempty"`
 	// The `serial` field is required.
 	Serial       string `protobuf:"bytes,4,opt,name=serial,proto3" json:"serial,omitempty"`
 	Code         int64  `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"`
 	AdminName    string `protobuf:"bytes,3,opt,name=adminName,proto3" json:"adminName,omitempty"`
 	SkipBlockKey bool   `protobuf:"varint,5,opt,name=skipBlockKey,proto3" json:"skipBlockKey,omitempty"`
+	// If the malformed flag is set, the RA will not attempt to parse the
+	// certificate in question. In this case, the keyCompromise reason cannot be
+	// specified, because the key cannot be blocked.
+	Malformed bool `protobuf:"varint,6,opt,name=malformed,proto3" json:"malformed,omitempty"`
 }
 
 func (x *AdministrativelyRevokeCertificateRequest) Reset() {
@@ -437,6 +438,13 @@ func (x *AdministrativelyRevokeCertificateRequest) GetSkipBlockKey() bool {
 	return false
 }
 
+func (x *AdministrativelyRevokeCertificateRequest) GetMalformed() bool {
+	if x != nil {
+		return x.Malformed
+	}
+	return false
+}
+
 type NewOrderRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -591,7 +599,7 @@ var file_ra_proto_rawDesc = []byte{
 	0x44, 0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42,
 	0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63,
 	0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x4a,
-	0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xac, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69,
+	0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xca, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69,
 	0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65,
 	0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
 	0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c,
@@ -602,71 +610,73 @@ var file_ra_proto_rawDesc = []byte{
 	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65,
 	0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79,
 	0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63,
-	0x6b, 0x4b, 0x65, 0x79, 0x22, 0x4f, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73,
-	0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52,
-	0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12,
-	0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05,
-	0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a,
-	0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a,
-	0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72,
-	0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63,
-	0x73, 0x72, 0x32, 0xad, 0x06, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f,
-	0x4e, 0x65, 0x77, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-	0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73,
-	0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x12, 0x55, 0x70, 0x64,
-	0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-	0x1d, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73,
-	0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12,
-	0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56,
-	0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50,
-	0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41,
-	0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x46,
-	0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69,
-	0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
-	0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67,
-	0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45,
-	0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69,
-	0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f,
-	0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69,
-	0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00,
-	0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79,
-	0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52,
-	0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69,
-	0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f,
-	0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d,
-	0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43,
-	0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65,
-	0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b,
-	0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65,
-	0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
-	0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73,
-	0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43,
-	0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e,
-	0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77,
-	0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63,
-	0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46,
-	0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72,
-	0x61, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72,
-	0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
-	0x65, 0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72,
-	0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10,
-	0x2e, 0x63, 0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
-	0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75,
-	0x6c, 0x64, 0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d, 0x65,
+	0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6d, 0x61, 0x6c, 0x66, 0x6f, 0x72, 0x6d,
+	0x65, 0x64, 0x22, 0x4f, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72,
+	0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a,
+	0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61,
+	0x6d, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f,
+	0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x6f,
+	0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, 0x6f, 0x72,
+	0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x10,
+	0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72,
+	0x32, 0xad, 0x06, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65,
+	0x77, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e,
+	0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74,
+	0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e,
+	0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63,
+	0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c,
+	0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65, 0x72,
+	0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74,
+	0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x16,
+	0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65,
+	0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f,
+	0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70,
+	0x74, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61,
+	0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12,
+	0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53,
+	0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70,
+	0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76,
+	0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
+	0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67,
+	0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74,
+	0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72,
+	0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f,
+	0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21,
+	0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79,
+	0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
+	0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72,
+	0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72,
+	0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
+	0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75,
+	0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77,
+	0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72,
+	0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72,
+	0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69, 0x6e,
+	0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x2e,
+	0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65,
+	0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f,
+	0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
+	0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x63,
+	0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
+	0x42, 0x29, 0x5a, 0x27, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c,
+	0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64,
+	0x65, 0x72, 0x2f, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/ra/proto/ra.proto b/ra/proto/ra.proto
index cd2db5e26..4f5f1093d 100644
--- a/ra/proto/ra.proto
+++ b/ra/proto/ra.proto
@@ -54,16 +54,17 @@ message RevokeCertByKeyRequest {
 }
 
 message AdministrativelyRevokeCertificateRequest {
-  // The `cert` field may be omitted. If it is omitted,
-  // the revocation reason (`code`) must not be keyCompromise,
-  // and purging the Akamai cache will not happen because the
-  // base URL for the certificate's OCSP server is not known.
+  // Deprecated: this field is ignored.
   bytes cert = 1;
   // The `serial` field is required.
   string serial = 4;
   int64 code = 2;
   string adminName = 3;
   bool skipBlockKey = 5;
+  // If the malformed flag is set, the RA will not attempt to parse the
+  // certificate in question. In this case, the keyCompromise reason cannot be
+  // specified, because the key cannot be blocked.
+  bool malformed = 6;
 }
 
 message NewOrderRequest {
diff --git a/ra/ra.go b/ra/ra.go
index 369ac011a..8e94d8c74 100644
--- a/ra/ra.go
+++ b/ra/ra.go
@@ -2207,48 +2207,28 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte
 	}
 
 	reasonCode := revocation.Reason(req.Code)
-	if reasonCode == ocsp.KeyCompromise && req.Cert == nil && !req.SkipBlockKey {
-		return nil, fmt.Errorf("cannot revoke and block for KeyCompromise by serial alone")
+	if _, present := revocation.AdminAllowedReasons[reasonCode]; !present {
+		return nil, fmt.Errorf("cannot revoke for reason %d", reasonCode)
 	}
 	if req.SkipBlockKey && reasonCode != ocsp.KeyCompromise {
 		return nil, fmt.Errorf("cannot skip key blocking for reasons other than KeyCompromise")
 	}
-
-	if _, present := revocation.AdminAllowedReasons[reasonCode]; !present {
-		return nil, fmt.Errorf("cannot revoke for reason %d", reasonCode)
-	}
-
-	// If we weren't passed a cert in the request, find IssuerID from the db.
-	// We could instead look up and parse the certificate itself, but we avoid
-	// that in case we are administratively revoking the certificate because it is
-	// so badly malformed that it can't be parsed.
-	var cert *x509.Certificate
-	var issuerID issuance.NameID
-	var err error
-	if req.Cert == nil {
-		status, err := ra.SA.GetCertificateStatus(ctx, &sapb.Serial{Serial: req.Serial})
-		if err != nil {
-			return nil, fmt.Errorf("unable to confirm that serial %q was ever issued: %w", req.Serial, err)
-		}
-		issuerID = issuance.NameID(status.IssuerID)
-	} else {
-		cert, err = x509.ParseCertificate(req.Cert)
-		if err != nil {
-			return nil, err
-		}
-		issuerID = issuance.IssuerNameID(cert)
+	if reasonCode == ocsp.KeyCompromise && req.Malformed {
+		return nil, fmt.Errorf("cannot revoke malformed certificate for KeyCompromise")
 	}
 
 	logEvent := certificateRevocationEvent{
 		ID:           core.NewToken(),
-		Method:       "key",
-		AdminName:    req.AdminName,
 		SerialNumber: req.Serial,
+		Reason:       req.Code,
+		Method:       "admin",
+		AdminName:    req.AdminName,
 	}
 
 	// Below this point, do not re-declare `err` (i.e. type `err :=`) in a
 	// nested scope. Doing so will create a new `err` variable that is not
 	// captured by this closure.
+	var err error
 	defer func() {
 		if err != nil {
 			logEvent.Error = err.Error()
@@ -2256,6 +2236,44 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte
 		ra.log.AuditObject("Revocation request:", logEvent)
 	}()
 
+	var cert *x509.Certificate
+	var issuerID issuance.NameID
+	if req.Cert != nil {
+		// If the incoming request includes a certificate body, just use that and
+		// avoid doing any database queries. This code path is deprecated and will
+		// be removed when req.Cert is removed.
+		cert, err = x509.ParseCertificate(req.Cert)
+		if err != nil {
+			return nil, err
+		}
+		issuerID = issuance.IssuerNameID(cert)
+	} else if !req.Malformed {
+		// As long as we don't believe the cert will be malformed, we should
+		// get the precertificate so we can block its pubkey if necessary and purge
+		// the akamai OCSP cache.
+		var certPB *corepb.Certificate
+		certPB, err = ra.SA.GetLintPrecertificate(ctx, &sapb.Serial{Serial: req.Serial})
+		if err != nil {
+			return nil, err
+		}
+		// Note that, although the thing we're parsing here is actually a linting
+		// precertificate, it has identical issuer info (and therefore an identical
+		// issuer NameID) to the real thing.
+		cert, err = x509.ParseCertificate(certPB.Der)
+		if err != nil {
+			return nil, err
+		}
+		issuerID = issuance.IssuerNameID(cert)
+	} else {
+		// But if the cert is malformed, we at least still need its IssuerID.
+		var status *corepb.CertificateStatus
+		status, err = ra.SA.GetCertificateStatus(ctx, &sapb.Serial{Serial: req.Serial})
+		if err != nil {
+			return nil, fmt.Errorf("unable to confirm that serial %q was ever issued: %w", req.Serial, err)
+		}
+		issuerID = issuance.NameID(status.IssuerID)
+	}
+
 	var serialInt *big.Int
 	serialInt, err = core.StringToSerial(req.Serial)
 	if err != nil {
diff --git a/ratelimits/README.md b/ratelimits/README.md
index 838edc656..29fe3f1ff 100644
--- a/ratelimits/README.md
+++ b/ratelimits/README.md
@@ -35,22 +35,30 @@ NewOrdersPerAccount:
 
 ## Override Limit Settings
 
-Each override key represents a specific bucket, consisting of two elements:
-_name_ and _id_. The name here refers to the Name of the particular limit, while
-the id is a client identifier. The format of the id is dependent on the limit.
-For example, the id for 'NewRegistrationsPerIPAddress' is a subscriber IP
-address, while the id for 'NewOrdersPerAccount' is the subscriber's registration
-ID.
+Each entry in the override list is a map, where the key is a limit name,
+corresponding to the `Name` enum of the limit, and the value is a set of
+overridden parameters. These parameters are applicable to a specific list of IDs
+included in each entry. It's important that the formatting of these IDs matches
+the ID format associated with their respective limit's `Name`. For more details on
+the relationship of ID format to limit `Name`s, please refer to the the documentation
+of each `Name` in the `//ratelimits/names.go` file or the [ratelimits package
+documentation](https://pkg.go.dev/github.com/letsencrypt/boulder/ratelimits#Name).
 
 ```yaml
-NewRegistrationsPerIPAddress:10.0.0.2:
-  burst: 20
-  count: 40
-  period: 1s
-NewOrdersPerAccount:12345678:
-  burst: 300
-  count: 600
-  period: 180m
+- NewRegistrationsPerIPAddress:
+    burst: 20
+    count: 40
+    period: 1s
+    ids:
+      - 10.0.0.2
+      - 10.0.0.5
+- NewOrdersPerAccount:
+    burst: 300
+    count: 600
+    period: 180m
+    ids:
+      - 12345678
+      - 87654321
 ```
 
 The above example overrides the default limits for specific subscribers. In both
@@ -67,33 +75,33 @@ format:
 A valid IPv4 or IPv6 address.
 
 Examples:
-  - `NewRegistrationsPerIPAddress:10.0.0.1`
-  - `NewRegistrationsPerIPAddress:2001:0db8:0000:0000:0000:ff00:0042:8329`
+  - `10.0.0.1`
+  - `2001:0db8:0000:0000:0000:ff00:0042:8329`
 
 #### ipv6RangeCIDR
 
 A valid IPv6 range in CIDR notation with a /48 mask. A /48 range is typically
 assigned to a single subscriber.
 
-Example: `NewRegistrationsPerIPv6Range:2001:0db8:0000::/48`
+Example: `2001:0db8:0000::/48`
 
 #### regId
 
 An ACME account registration ID.
 
-Example: `NewOrdersPerAccount:12345678`
+Example: `12345678`
 
 #### domain
 
 A valid eTLD+1 domain name.
 
-Example: `CertificatesPerDomain:example.com`
+Example: `example.com`
 
 #### fqdnSet
 
 A comma-separated list of domain names.
 
-Example: `CertificatesPerFQDNSet:example.com,example.org`
+Example: `example.com,example.org`
 
 ## Bucket Key Definitions
 
@@ -145,10 +153,12 @@ address are allowed 20 requests to the newFoo endpoint per second, with a
 maximum burst of 20 requests at any point-in-time, or:
 
 ```yaml
-NewFoosPerIPAddress:172.23.45.22:
-  burst: 20
-  count: 20
-  period: 1s
+- NewFoosPerIPAddress:
+    burst: 20
+    count: 20
+    period: 1s
+    ids:
+      - 172.23.45.22
 ```
 
 A subscriber calls the newFoo endpoint for the first time with an IP address of
diff --git a/ratelimits/bucket.go b/ratelimits/bucket.go
index f4909c502..deda7ca42 100644
--- a/ratelimits/bucket.go
+++ b/ratelimits/bucket.go
@@ -282,7 +282,11 @@ func (builder *TransactionBuilder) FailedAuthorizationsPerAccountTransaction(reg
 //
 // When a CertificatesPerDomainPerAccount override is not configured, a check-
 // and-spend Transaction is returned for each per domain bucket.
-func (builder *TransactionBuilder) CertificatesPerDomainTransactions(regId int64, orderDomains []string) ([]Transaction, error) {
+func (builder *TransactionBuilder) CertificatesPerDomainTransactions(regId int64, orderDomains []string, maxNames int) ([]Transaction, error) {
+	if len(orderDomains) > maxNames {
+		return nil, fmt.Errorf("order contains more than %d DNS names", maxNames)
+	}
+
 	perAccountLimitBucketKey, err := newRegIdBucketKey(CertificatesPerDomainPerAccount, regId)
 	if err != nil {
 		return nil, err
diff --git a/va/caa.go b/va/caa.go
index a48611be5..40497d12a 100644
--- a/va/caa.go
+++ b/va/caa.go
@@ -10,6 +10,10 @@ import (
 	"sync"
 	"time"
 
+	"github.com/miekg/dns"
+	"github.com/prometheus/client_golang/prometheus"
+
+	"github.com/letsencrypt/boulder/bdns"
 	"github.com/letsencrypt/boulder/canceled"
 	"github.com/letsencrypt/boulder/core"
 	corepb "github.com/letsencrypt/boulder/core/proto"
@@ -19,8 +23,6 @@ import (
 	"github.com/letsencrypt/boulder/identifier"
 	"github.com/letsencrypt/boulder/probs"
 	vapb "github.com/letsencrypt/boulder/va/proto"
-	"github.com/miekg/dns"
-	"github.com/prometheus/client_golang/prometheus"
 )
 
 type caaParams struct {
@@ -309,6 +311,7 @@ type caaResult struct {
 	issuewild       []*dns.CAA
 	criticalUnknown bool
 	dig             string
+	resolvers       bdns.ResolverAddrs
 	err             error
 }
 
@@ -363,7 +366,7 @@ func (va *ValidationAuthorityImpl) parallelCAALookup(ctx context.Context, name s
 		go func(name string, r *caaResult) {
 			r.name = name
 			var records []*dns.CAA
-			records, r.dig, r.err = va.dnsClient.LookupCAA(ctx, name)
+			records, r.dig, r.resolvers, r.err = va.dnsClient.LookupCAA(ctx, name)
 			if len(records) > 0 {
 				r.present = true
 			}
diff --git a/va/dns.go b/va/dns.go
index fd8558c7a..baf3c57e4 100644
--- a/va/dns.go
+++ b/va/dns.go
@@ -8,6 +8,7 @@ import (
 	"fmt"
 	"net"
 
+	"github.com/letsencrypt/boulder/bdns"
 	"github.com/letsencrypt/boulder/core"
 	berrors "github.com/letsencrypt/boulder/errors"
 	"github.com/letsencrypt/boulder/identifier"
@@ -20,19 +21,19 @@ import (
 // used by net/http. If there is an error resolving the hostname, or if no
 // usable IP addresses are available then a berrors.DNSError instance is
 // returned with a nil net.IP slice.
-func (va ValidationAuthorityImpl) getAddrs(ctx context.Context, hostname string) ([]net.IP, error) {
-	addrs, err := va.dnsClient.LookupHost(ctx, hostname)
+func (va ValidationAuthorityImpl) getAddrs(ctx context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
+	addrs, resolvers, err := va.dnsClient.LookupHost(ctx, hostname)
 	if err != nil {
-		return nil, berrors.DNSError("%v", err)
+		return nil, resolvers, berrors.DNSError("%v", err)
 	}
 
 	if len(addrs) == 0 {
 		// This should be unreachable, as no valid IP addresses being found results
 		// in an error being returned from LookupHost.
-		return nil, berrors.DNSError("No valid IP addresses found for %s", hostname)
+		return nil, resolvers, berrors.DNSError("No valid IP addresses found for %s", hostname)
 	}
 	va.log.Debugf("Resolved addresses for %s: %s", hostname, addrs)
-	return addrs, nil
+	return addrs, resolvers, nil
 }
 
 // availableAddresses takes a ValidationRecord and splits the AddressesResolved
@@ -61,7 +62,7 @@ func (va *ValidationAuthorityImpl) validateDNS01(ctx context.Context, ident iden
 
 	// Look for the required record in the DNS
 	challengeSubdomain := fmt.Sprintf("%s.%s", core.DNSPrefix, ident.Value)
-	txts, err := va.dnsClient.LookupTXT(ctx, challengeSubdomain)
+	txts, resolvers, err := va.dnsClient.LookupTXT(ctx, challengeSubdomain)
 	if err != nil {
 		return nil, probs.DNS(err.Error())
 	}
@@ -76,7 +77,7 @@ func (va *ValidationAuthorityImpl) validateDNS01(ctx context.Context, ident iden
 	for _, element := range txts {
 		if subtle.ConstantTimeCompare([]byte(element), []byte(authorizedKeysDigest)) == 1 {
 			// Successful challenge validation
-			return []core.ValidationRecord{{Hostname: ident.Value}}, nil
+			return []core.ValidationRecord{{Hostname: ident.Value, ResolverAddrs: resolvers}}, nil
 		}
 	}
 
diff --git a/va/http.go b/va/http.go
index dfdf8d86f..8700b2a03 100644
--- a/va/http.go
+++ b/va/http.go
@@ -14,6 +14,7 @@ import (
 	"time"
 	"unicode"
 
+	"github.com/letsencrypt/boulder/bdns"
 	"github.com/letsencrypt/boulder/core"
 	berrors "github.com/letsencrypt/boulder/errors"
 	"github.com/letsencrypt/boulder/iana"
@@ -177,6 +178,8 @@ type httpValidationTarget struct {
 	next []net.IP
 	// the current IP address being used for validation (if any)
 	cur net.IP
+	// the DNS resolver(s) that will attempt to fulfill the validation request
+	resolvers bdns.ResolverAddrs
 }
 
 // nextIP changes the cur IP by removing the first entry from the next slice and
@@ -212,7 +215,7 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
 	path string,
 	query string) (*httpValidationTarget, error) {
 	// Resolve IP addresses for the hostname
-	addrs, err := va.getAddrs(ctx, host)
+	addrs, resolvers, err := va.getAddrs(ctx, host)
 	if err != nil {
 		return nil, err
 	}
@@ -223,6 +226,7 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
 		path:      path,
 		query:     query,
 		available: addrs,
+		resolvers: resolvers,
 	}
 
 	// Separate the addresses into the available v4 and v6 addresses
@@ -362,6 +366,7 @@ func (va *ValidationAuthorityImpl) setupHTTPValidation(
 		Port:              strconv.Itoa(target.port),
 		AddressesResolved: target.available,
 		URL:               reqURL,
+		ResolverAddrs:     target.resolvers,
 	}
 
 	// Get the target IP to build a preresolved dialer with
@@ -660,7 +665,6 @@ func (va *ValidationAuthorityImpl) validateHTTP01(ctx context.Context, ident ide
 	if prob != nil {
 		return validationRecords, prob
 	}
-
 	payload := strings.TrimRightFunc(string(body), unicode.IsSpace)
 
 	if payload != challenge.ProvidedKeyAuthorization {
diff --git a/va/tlsalpn.go b/va/tlsalpn.go
index 12e04098a..f73e8e9e2 100644
--- a/va/tlsalpn.go
+++ b/va/tlsalpn.go
@@ -60,12 +60,13 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(ctx context.Context,
 	identifier identifier.ACMEIdentifier, challenge core.Challenge,
 	tlsConfig *tls.Config) (*x509.Certificate, *tls.ConnectionState, []core.ValidationRecord, *probs.ProblemDetails) {
 
-	allAddrs, err := va.getAddrs(ctx, identifier.Value)
+	allAddrs, resolvers, err := va.getAddrs(ctx, identifier.Value)
 	validationRecords := []core.ValidationRecord{
 		{
 			Hostname:          identifier.Value,
 			AddressesResolved: allAddrs,
 			Port:              strconv.Itoa(va.tlsPort),
+			ResolverAddrs:     resolvers,
 		},
 	}
 	if err != nil {
diff --git a/vendor/github.com/miekg/dns/README.md b/vendor/github.com/miekg/dns/README.md
index 06bea9fab..e57d86afe 100644
--- a/vendor/github.com/miekg/dns/README.md
+++ b/vendor/github.com/miekg/dns/README.md
@@ -81,6 +81,8 @@ A not-so-up-to-date-list-that-may-be-actually-current:
 * https://addr.tools/
 * https://dnscheck.tools/
 * https://github.com/egbakou/domainverifier
+* https://github.com/semihalev/sdns
+* https://github.com/wintbiit/NineDNS
 
 
 Send pull request if you want to be listed here.
@@ -124,6 +126,7 @@ Example programs can be found in the `github.com/miekg/exdns` repository.
 *all of them*
 
 * 103{4,5} - DNS standard
+* 1183 - ISDN, X25 and other deprecated records
 * 1348 - NSAP record (removed the record)
 * 1982 - Serial Arithmetic
 * 1876 - LOC record
diff --git a/vendor/github.com/miekg/dns/acceptfunc.go b/vendor/github.com/miekg/dns/acceptfunc.go
index ab2812e33..1a59a854e 100644
--- a/vendor/github.com/miekg/dns/acceptfunc.go
+++ b/vendor/github.com/miekg/dns/acceptfunc.go
@@ -10,8 +10,6 @@ type MsgAcceptFunc func(dh Header) MsgAcceptAction
 //
 // * opcode isn't OpcodeQuery or OpcodeNotify
 //
-// * Zero bit isn't zero
-//
 // * does not have exactly 1 question in the question section
 //
 // * has more than 1 RR in the Answer section
diff --git a/vendor/github.com/miekg/dns/defaults.go b/vendor/github.com/miekg/dns/defaults.go
index c1558b79c..02d9199a4 100644
--- a/vendor/github.com/miekg/dns/defaults.go
+++ b/vendor/github.com/miekg/dns/defaults.go
@@ -22,8 +22,7 @@ func (dns *Msg) SetReply(request *Msg) *Msg {
 	}
 	dns.Rcode = RcodeSuccess
 	if len(request.Question) > 0 {
-		dns.Question = make([]Question, 1)
-		dns.Question[0] = request.Question[0]
+		dns.Question = []Question{request.Question[0]}
 	}
 	return dns
 }
@@ -292,26 +291,19 @@ func IsFqdn(s string) bool {
 	return (len(s)-i)%2 != 0
 }
 
-// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
-// This means the RRs need to have the same type, name, and class. Returns true
-// if the RR set is valid, otherwise false.
+// IsRRset reports whether a set of RRs is a valid RRset as defined by RFC 2181.
+// This means the RRs need to have the same type, name, and class.
 func IsRRset(rrset []RR) bool {
 	if len(rrset) == 0 {
 		return false
 	}
-	if len(rrset) == 1 {
-		return true
-	}
-	rrHeader := rrset[0].Header()
-	rrType := rrHeader.Rrtype
-	rrClass := rrHeader.Class
-	rrName := rrHeader.Name
 
+	baseH := rrset[0].Header()
 	for _, rr := range rrset[1:] {
-		curRRHeader := rr.Header()
-		if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
+		curH := rr.Header()
+		if curH.Rrtype != baseH.Rrtype || curH.Class != baseH.Class || curH.Name != baseH.Name {
 			// Mismatch between the records, so this is not a valid rrset for
-			//signing/verifying
+			// signing/verifying
 			return false
 		}
 	}
@@ -329,9 +321,15 @@ func Fqdn(s string) string {
 }
 
 // CanonicalName returns the domain name in canonical form. A name in canonical
-// form is lowercase and fully qualified. See Section 6.2 in RFC 4034.
+// form is lowercase and fully qualified. Only US-ASCII letters are affected. See
+// Section 6.2 in RFC 4034.
 func CanonicalName(s string) string {
-	return strings.ToLower(Fqdn(s))
+	return strings.Map(func(r rune) rune {
+		if r >= 'A' && r <= 'Z' {
+			r += 'a' - 'A'
+		}
+		return r
+	}, Fqdn(s))
 }
 
 // Copied from the official Go code.
diff --git a/vendor/github.com/miekg/dns/dnssec_keyscan.go b/vendor/github.com/miekg/dns/dnssec_keyscan.go
index f79658169..9c9972db6 100644
--- a/vendor/github.com/miekg/dns/dnssec_keyscan.go
+++ b/vendor/github.com/miekg/dns/dnssec_keyscan.go
@@ -37,7 +37,8 @@ func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, er
 		return nil, ErrPrivKey
 	}
 	// TODO(mg): check if the pubkey matches the private key
-	algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8)
+	algoStr, _, _ := strings.Cut(m["algorithm"], " ")
+	algo, err := strconv.ParseUint(algoStr, 10, 8)
 	if err != nil {
 		return nil, ErrPrivKey
 	}
@@ -159,7 +160,7 @@ func parseKey(r io.Reader, file string) (map[string]string, error) {
 			k = l.token
 		case zValue:
 			if k == "" {
-				return nil, &ParseError{file, "no private key seen", l}
+				return nil, &ParseError{file: file, err: "no private key seen", lex: l}
 			}
 
 			m[strings.ToLower(k)] = l.token
diff --git a/vendor/github.com/miekg/dns/edns.go b/vendor/github.com/miekg/dns/edns.go
index b5bdac816..1b58e8f0a 100644
--- a/vendor/github.com/miekg/dns/edns.go
+++ b/vendor/github.com/miekg/dns/edns.go
@@ -185,7 +185,7 @@ func (rr *OPT) Do() bool {
 
 // SetDo sets the DO (DNSSEC OK) bit.
 // If we pass an argument, set the DO bit to that value.
-// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored.
+// It is possible to pass 2 or more arguments, but they will be ignored.
 func (rr *OPT) SetDo(do ...bool) {
 	if len(do) == 1 {
 		if do[0] {
@@ -508,6 +508,7 @@ func (e *EDNS0_LLQ) String() string {
 		" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
 	return s
 }
+
 func (e *EDNS0_LLQ) copy() EDNS0 {
 	return &EDNS0_LLQ{e.Code, e.Version, e.Opcode, e.Error, e.Id, e.LeaseLife}
 }
diff --git a/vendor/github.com/miekg/dns/generate.go b/vendor/github.com/miekg/dns/generate.go
index ac8df34dd..a81d2bc51 100644
--- a/vendor/github.com/miekg/dns/generate.go
+++ b/vendor/github.com/miekg/dns/generate.go
@@ -35,17 +35,17 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
 		token = token[:i]
 	}
 
-	sx := strings.SplitN(token, "-", 2)
-	if len(sx) != 2 {
+	startStr, endStr, ok := strings.Cut(token, "-")
+	if !ok {
 		return zp.setParseError("bad start-stop in $GENERATE range", l)
 	}
 
-	start, err := strconv.ParseInt(sx[0], 10, 64)
+	start, err := strconv.ParseInt(startStr, 10, 64)
 	if err != nil {
 		return zp.setParseError("bad start in $GENERATE range", l)
 	}
 
-	end, err := strconv.ParseInt(sx[1], 10, 64)
+	end, err := strconv.ParseInt(endStr, 10, 64)
 	if err != nil {
 		return zp.setParseError("bad stop in $GENERATE range", l)
 	}
@@ -54,7 +54,7 @@ func (zp *ZoneParser) generate(l lex) (RR, bool) {
 	}
 
 	// _BLANK
-	l, ok := zp.c.Next()
+	l, ok = zp.c.Next()
 	if !ok || l.value != zBlank {
 		return zp.setParseError("garbage after $GENERATE range", l)
 	}
@@ -116,7 +116,7 @@ func (r *generateReader) parseError(msg string, end int) *ParseError {
 	l.token = r.s[r.si-1 : end]
 	l.column += r.si // l.column starts one zBLANK before r.s
 
-	return &ParseError{r.file, msg, l}
+	return &ParseError{file: r.file, err: msg, lex: l}
 }
 
 func (r *generateReader) Read(p []byte) (int, error) {
@@ -211,15 +211,16 @@ func (r *generateReader) ReadByte() (byte, error) {
 func modToPrintf(s string) (string, int64, string) {
 	// Modifier is { offset [ ,width [ ,base ] ] } - provide default
 	// values for optional width and type, if necessary.
-	var offStr, widthStr, base string
-	switch xs := strings.Split(s, ","); len(xs) {
-	case 1:
-		offStr, widthStr, base = xs[0], "0", "d"
-	case 2:
-		offStr, widthStr, base = xs[0], xs[1], "d"
-	case 3:
-		offStr, widthStr, base = xs[0], xs[1], xs[2]
-	default:
+	offStr, s, ok0 := strings.Cut(s, ",")
+	widthStr, s, ok1 := strings.Cut(s, ",")
+	base, _, ok2 := strings.Cut(s, ",")
+	if !ok0 {
+		widthStr = "0"
+	}
+	if !ok1 {
+		base = "d"
+	}
+	if ok2 {
 		return "", 0, "bad modifier in $GENERATE"
 	}
 
@@ -234,8 +235,8 @@ func modToPrintf(s string) (string, int64, string) {
 		return "", 0, "bad offset in $GENERATE"
 	}
 
-	width, err := strconv.ParseInt(widthStr, 10, 64)
-	if err != nil || width < 0 || width > 255 {
+	width, err := strconv.ParseUint(widthStr, 10, 8)
+	if err != nil {
 		return "", 0, "bad width in $GENERATE"
 	}
 
diff --git a/vendor/github.com/miekg/dns/listen_no_reuseport.go b/vendor/github.com/miekg/dns/listen_no_reuseport.go
index 6ed50f86b..8cebb2f17 100644
--- a/vendor/github.com/miekg/dns/listen_no_reuseport.go
+++ b/vendor/github.com/miekg/dns/listen_no_reuseport.go
@@ -7,16 +7,18 @@ import "net"
 
 const supportsReusePort = false
 
-func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
-	if reuseport {
+func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
+	if reuseport || reuseaddr {
 		// TODO(tmthrgd): return an error?
 	}
 
 	return net.Listen(network, addr)
 }
 
-func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
-	if reuseport {
+const supportsReuseAddr = false
+
+func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
+	if reuseport || reuseaddr {
 		// TODO(tmthrgd): return an error?
 	}
 
diff --git a/vendor/github.com/miekg/dns/listen_reuseport.go b/vendor/github.com/miekg/dns/listen_reuseport.go
index 89bac9034..41326f20b 100644
--- a/vendor/github.com/miekg/dns/listen_reuseport.go
+++ b/vendor/github.com/miekg/dns/listen_reuseport.go
@@ -25,19 +25,41 @@ func reuseportControl(network, address string, c syscall.RawConn) error {
 	return opErr
 }
 
-func listenTCP(network, addr string, reuseport bool) (net.Listener, error) {
+const supportsReuseAddr = true
+
+func reuseaddrControl(network, address string, c syscall.RawConn) error {
+	var opErr error
+	err := c.Control(func(fd uintptr) {
+		opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
+	})
+	if err != nil {
+		return err
+	}
+
+	return opErr
+}
+
+func listenTCP(network, addr string, reuseport, reuseaddr bool) (net.Listener, error) {
 	var lc net.ListenConfig
-	if reuseport {
+	switch {
+	case reuseaddr && reuseport:
+	case reuseport:
 		lc.Control = reuseportControl
+	case reuseaddr:
+		lc.Control = reuseaddrControl
 	}
 
 	return lc.Listen(context.Background(), network, addr)
 }
 
-func listenUDP(network, addr string, reuseport bool) (net.PacketConn, error) {
+func listenUDP(network, addr string, reuseport, reuseaddr bool) (net.PacketConn, error) {
 	var lc net.ListenConfig
-	if reuseport {
+	switch {
+	case reuseaddr && reuseport:
+	case reuseport:
 		lc.Control = reuseportControl
+	case reuseaddr:
+		lc.Control = reuseaddrControl
 	}
 
 	return lc.ListenPacket(context.Background(), network, addr)
diff --git a/vendor/github.com/miekg/dns/msg.go b/vendor/github.com/miekg/dns/msg.go
index d5049a4f9..8294d0395 100644
--- a/vendor/github.com/miekg/dns/msg.go
+++ b/vendor/github.com/miekg/dns/msg.go
@@ -501,30 +501,28 @@ func packTxtString(s string, msg []byte, offset int) (int, error) {
 	return offset, nil
 }
 
-func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) {
-	if offset >= len(msg) || len(s) > len(tmp) {
+func packOctetString(s string, msg []byte, offset int) (int, error) {
+	if offset >= len(msg) || len(s) > 256*4+1 {
 		return offset, ErrBuf
 	}
-	bs := tmp[:len(s)]
-	copy(bs, s)
-	for i := 0; i < len(bs); i++ {
+	for i := 0; i < len(s); i++ {
 		if len(msg) <= offset {
 			return offset, ErrBuf
 		}
-		if bs[i] == '\\' {
+		if s[i] == '\\' {
 			i++
-			if i == len(bs) {
+			if i == len(s) {
 				break
 			}
 			// check for \DDD
-			if isDDD(bs[i:]) {
-				msg[offset] = dddToByte(bs[i:])
+			if isDDD(s[i:]) {
+				msg[offset] = dddToByte(s[i:])
 				i += 2
 			} else {
-				msg[offset] = bs[i]
+				msg[offset] = s[i]
 			}
 		} else {
-			msg[offset] = bs[i]
+			msg[offset] = s[i]
 		}
 		offset++
 	}
@@ -896,23 +894,38 @@ func (dns *Msg) String() string {
 		return "<nil> MsgHdr"
 	}
 	s := dns.MsgHdr.String() + " "
-	s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
-	s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
-	s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
-	s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
+	if dns.MsgHdr.Opcode == OpcodeUpdate {
+		s += "ZONE: " + strconv.Itoa(len(dns.Question)) + ", "
+		s += "PREREQ: " + strconv.Itoa(len(dns.Answer)) + ", "
+		s += "UPDATE: " + strconv.Itoa(len(dns.Ns)) + ", "
+		s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
+	} else {
+		s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
+		s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
+		s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
+		s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
+	}
 	opt := dns.IsEdns0()
 	if opt != nil {
 		// OPT PSEUDOSECTION
 		s += opt.String() + "\n"
 	}
 	if len(dns.Question) > 0 {
-		s += "\n;; QUESTION SECTION:\n"
+		if dns.MsgHdr.Opcode == OpcodeUpdate {
+			s += "\n;; ZONE SECTION:\n"
+		} else {
+			s += "\n;; QUESTION SECTION:\n"
+		}
 		for _, r := range dns.Question {
 			s += r.String() + "\n"
 		}
 	}
 	if len(dns.Answer) > 0 {
-		s += "\n;; ANSWER SECTION:\n"
+		if dns.MsgHdr.Opcode == OpcodeUpdate {
+			s += "\n;; PREREQUISITE SECTION:\n"
+		} else {
+			s += "\n;; ANSWER SECTION:\n"
+		}
 		for _, r := range dns.Answer {
 			if r != nil {
 				s += r.String() + "\n"
@@ -920,7 +933,11 @@ func (dns *Msg) String() string {
 		}
 	}
 	if len(dns.Ns) > 0 {
-		s += "\n;; AUTHORITY SECTION:\n"
+		if dns.MsgHdr.Opcode == OpcodeUpdate {
+			s += "\n;; UPDATE SECTION:\n"
+		} else {
+			s += "\n;; AUTHORITY SECTION:\n"
+		}
 		for _, r := range dns.Ns {
 			if r != nil {
 				s += r.String() + "\n"
diff --git a/vendor/github.com/miekg/dns/msg_helpers.go b/vendor/github.com/miekg/dns/msg_helpers.go
index 8582fc0ad..acec21f7d 100644
--- a/vendor/github.com/miekg/dns/msg_helpers.go
+++ b/vendor/github.com/miekg/dns/msg_helpers.go
@@ -20,9 +20,7 @@ func unpackDataA(msg []byte, off int) (net.IP, int, error) {
 	if off+net.IPv4len > len(msg) {
 		return nil, len(msg), &Error{err: "overflow unpacking a"}
 	}
-	a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...)
-	off += net.IPv4len
-	return a, off, nil
+	return cloneSlice(msg[off : off+net.IPv4len]), off + net.IPv4len, nil
 }
 
 func packDataA(a net.IP, msg []byte, off int) (int, error) {
@@ -47,9 +45,7 @@ func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
 	if off+net.IPv6len > len(msg) {
 		return nil, len(msg), &Error{err: "overflow unpacking aaaa"}
 	}
-	aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...)
-	off += net.IPv6len
-	return aaaa, off, nil
+	return cloneSlice(msg[off : off+net.IPv6len]), off + net.IPv6len, nil
 }
 
 func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
@@ -410,29 +406,24 @@ func packStringTxt(s []string, msg []byte, off int) (int, error) {
 
 func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
 	var edns []EDNS0
-Option:
-	var code uint16
-	if off+4 > len(msg) {
-		return nil, len(msg), &Error{err: "overflow unpacking opt"}
-	}
-	code = binary.BigEndian.Uint16(msg[off:])
-	off += 2
-	optlen := binary.BigEndian.Uint16(msg[off:])
-	off += 2
-	if off+int(optlen) > len(msg) {
-		return nil, len(msg), &Error{err: "overflow unpacking opt"}
-	}
-	e := makeDataOpt(code)
-	if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
-		return nil, len(msg), err
-	}
-	edns = append(edns, e)
-	off += int(optlen)
-
-	if off < len(msg) {
-		goto Option
+	for off < len(msg) {
+		if off+4 > len(msg) {
+			return nil, len(msg), &Error{err: "overflow unpacking opt"}
+		}
+		code := binary.BigEndian.Uint16(msg[off:])
+		off += 2
+		optlen := binary.BigEndian.Uint16(msg[off:])
+		off += 2
+		if off+int(optlen) > len(msg) {
+			return nil, len(msg), &Error{err: "overflow unpacking opt"}
+		}
+		opt := makeDataOpt(code)
+		if err := opt.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, opt)
+		off += int(optlen)
 	}
-
 	return edns, off, nil
 }
 
@@ -461,8 +452,7 @@ func unpackStringOctet(msg []byte, off int) (string, int, error) {
 }
 
 func packStringOctet(s string, msg []byte, off int) (int, error) {
-	txtTmp := make([]byte, 256*4+1)
-	off, err := packOctetString(s, msg, off, txtTmp)
+	off, err := packOctetString(s, msg, off)
 	if err != nil {
 		return len(msg), err
 	}
diff --git a/vendor/github.com/miekg/dns/privaterr.go b/vendor/github.com/miekg/dns/privaterr.go
index d256b652e..350ea5a47 100644
--- a/vendor/github.com/miekg/dns/privaterr.go
+++ b/vendor/github.com/miekg/dns/privaterr.go
@@ -84,7 +84,7 @@ Fetch:
 
 	err := r.Data.Parse(text)
 	if err != nil {
-		return &ParseError{"", err.Error(), l}
+		return &ParseError{wrappedErr: err, lex: l}
 	}
 
 	return nil
diff --git a/vendor/github.com/miekg/dns/scan.go b/vendor/github.com/miekg/dns/scan.go
index 3083c3e5f..1f92ae421 100644
--- a/vendor/github.com/miekg/dns/scan.go
+++ b/vendor/github.com/miekg/dns/scan.go
@@ -4,7 +4,9 @@ import (
 	"bufio"
 	"fmt"
 	"io"
+	"io/fs"
 	"os"
+	"path"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -64,20 +66,26 @@ const (
 // ParseError is a parsing error. It contains the parse error and the location in the io.Reader
 // where the error occurred.
 type ParseError struct {
-	file string
-	err  string
-	lex  lex
+	file       string
+	err        string
+	wrappedErr error
+	lex        lex
 }
 
 func (e *ParseError) Error() (s string) {
 	if e.file != "" {
 		s = e.file + ": "
 	}
+	if e.err == "" && e.wrappedErr != nil {
+		e.err = e.wrappedErr.Error()
+	}
 	s += "dns: " + e.err + ": " + strconv.QuoteToASCII(e.lex.token) + " at line: " +
 		strconv.Itoa(e.lex.line) + ":" + strconv.Itoa(e.lex.column)
 	return
 }
 
+func (e *ParseError) Unwrap() error { return e.wrappedErr }
+
 type lex struct {
 	token  string // text of the token
 	err    bool   // when true, token text has lexer error
@@ -168,8 +176,9 @@ type ZoneParser struct {
 	// sub is used to parse $INCLUDE files and $GENERATE directives.
 	// Next, by calling subNext, forwards the resulting RRs from this
 	// sub parser to the calling code.
-	sub    *ZoneParser
-	osFile *os.File
+	sub  *ZoneParser
+	r    io.Reader
+	fsys fs.FS
 
 	includeDepth uint8
 
@@ -188,7 +197,7 @@ func NewZoneParser(r io.Reader, origin, file string) *ZoneParser {
 	if origin != "" {
 		origin = Fqdn(origin)
 		if _, ok := IsDomainName(origin); !ok {
-			pe = &ParseError{file, "bad initial origin name", lex{}}
+			pe = &ParseError{file: file, err: "bad initial origin name"}
 		}
 	}
 
@@ -220,6 +229,24 @@ func (zp *ZoneParser) SetIncludeAllowed(v bool) {
 	zp.includeAllowed = v
 }
 
+// SetIncludeFS provides an [fs.FS] to use when looking for the target of
+// $INCLUDE directives.  ($INCLUDE must still be enabled separately by calling
+// [ZoneParser.SetIncludeAllowed].)  If fsys is nil, [os.Open] will be used.
+//
+// When fsys is an on-disk FS, the ability of $INCLUDE to reach files from
+// outside its root directory depends upon the FS implementation.  For
+// instance, [os.DirFS] will refuse to open paths like "../../etc/passwd",
+// however it will still follow links which may point anywhere on the system.
+//
+// FS paths are slash-separated on all systems, even Windows.  $INCLUDE paths
+// containing other characters such as backslash and colon may be accepted as
+// valid, but those characters will never be interpreted by an FS
+// implementation as path element separators.  See [fs.ValidPath] for more
+// details.
+func (zp *ZoneParser) SetIncludeFS(fsys fs.FS) {
+	zp.fsys = fsys
+}
+
 // Err returns the first non-EOF error that was encountered by the
 // ZoneParser.
 func (zp *ZoneParser) Err() error {
@@ -237,7 +264,7 @@ func (zp *ZoneParser) Err() error {
 }
 
 func (zp *ZoneParser) setParseError(err string, l lex) (RR, bool) {
-	zp.parseErr = &ParseError{zp.file, err, l}
+	zp.parseErr = &ParseError{file: zp.file, err: err, lex: l}
 	return nil, false
 }
 
@@ -260,9 +287,11 @@ func (zp *ZoneParser) subNext() (RR, bool) {
 		return rr, true
 	}
 
-	if zp.sub.osFile != nil {
-		zp.sub.osFile.Close()
-		zp.sub.osFile = nil
+	if zp.sub.r != nil {
+		if c, ok := zp.sub.r.(io.Closer); ok {
+			c.Close()
+		}
+		zp.sub.r = nil
 	}
 
 	if zp.sub.Err() != nil {
@@ -402,24 +431,44 @@ func (zp *ZoneParser) Next() (RR, bool) {
 
 			// Start with the new file
 			includePath := l.token
-			if !filepath.IsAbs(includePath) {
-				includePath = filepath.Join(filepath.Dir(zp.file), includePath)
-			}
+			var r1 io.Reader
+			var e1 error
+			if zp.fsys != nil {
+				// fs.FS always uses / as separator, even on Windows, so use
+				// path instead of filepath here:
+				if !path.IsAbs(includePath) {
+					includePath = path.Join(path.Dir(zp.file), includePath)
+				}
+
+				// os.DirFS, and probably others, expect all paths to be
+				// relative, so clean the path and remove leading / if
+				// present:
+				includePath = strings.TrimLeft(path.Clean(includePath), "/")
 
-			r1, e1 := os.Open(includePath)
+				r1, e1 = zp.fsys.Open(includePath)
+			} else {
+				if !filepath.IsAbs(includePath) {
+					includePath = filepath.Join(filepath.Dir(zp.file), includePath)
+				}
+				r1, e1 = os.Open(includePath)
+			}
 			if e1 != nil {
 				var as string
-				if !filepath.IsAbs(l.token) {
+				if includePath != l.token {
 					as = fmt.Sprintf(" as `%s'", includePath)
 				}
-
-				msg := fmt.Sprintf("failed to open `%s'%s: %v", l.token, as, e1)
-				return zp.setParseError(msg, l)
+				zp.parseErr = &ParseError{
+					file:       zp.file,
+					wrappedErr: fmt.Errorf("failed to open `%s'%s: %w", l.token, as, e1),
+					lex:        l,
+				}
+				return nil, false
 			}
 
 			zp.sub = NewZoneParser(r1, neworigin, includePath)
-			zp.sub.defttl, zp.sub.includeDepth, zp.sub.osFile = zp.defttl, zp.includeDepth+1, r1
+			zp.sub.defttl, zp.sub.includeDepth, zp.sub.r = zp.defttl, zp.includeDepth+1, r1
 			zp.sub.SetIncludeAllowed(true)
+			zp.sub.SetIncludeFS(zp.fsys)
 			return zp.subNext()
 		case zExpectDirTTLBl:
 			if l.value != zBlank {
@@ -605,8 +654,6 @@ func (zp *ZoneParser) Next() (RR, bool) {
 			if !isPrivate && zp.c.Peek().token == "" {
 				// This is a dynamic update rr.
 
-				// TODO(tmthrgd): Previously slurpRemainder was only called
-				// for certain RR types, which may have been important.
 				if err := slurpRemainder(zp.c); err != nil {
 					return zp.setParseError(err.err, err.lex)
 				}
@@ -1216,42 +1263,34 @@ func stringToCm(token string) (e, m uint8, ok bool) {
 	if token[len(token)-1] == 'M' || token[len(token)-1] == 'm' {
 		token = token[0 : len(token)-1]
 	}
-	s := strings.SplitN(token, ".", 2)
-	var meters, cmeters, val int
-	var err error
-	switch len(s) {
-	case 2:
-		if cmeters, err = strconv.Atoi(s[1]); err != nil {
-			return
-		}
+
+	var (
+		meters, cmeters, val int
+		err                  error
+	)
+	mStr, cmStr, hasCM := strings.Cut(token, ".")
+	if hasCM {
 		// There's no point in having more than 2 digits in this part, and would rather make the implementation complicated ('123' should be treated as '12').
 		// So we simply reject it.
 		// We also make sure the first character is a digit to reject '+-' signs.
-		if len(s[1]) > 2 || s[1][0] < '0' || s[1][0] > '9' {
+		cmeters, err = strconv.Atoi(cmStr)
+		if err != nil || len(cmStr) > 2 || cmStr[0] < '0' || cmStr[0] > '9' {
 			return
 		}
-		if len(s[1]) == 1 {
+		if len(cmStr) == 1 {
 			// 'nn.1' must be treated as 'nn-meters and 10cm, not 1cm.
 			cmeters *= 10
 		}
-		if s[0] == "" {
-			// This will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
-			break
-		}
-		fallthrough
-	case 1:
-		if meters, err = strconv.Atoi(s[0]); err != nil {
-			return
-		}
+	}
+	// This slighly ugly condition will allow omitting the 'meter' part, like .01 (meaning 0.01m = 1cm).
+	if !hasCM || mStr != "" {
+		meters, err = strconv.Atoi(mStr)
 		// RFC1876 states the max value is 90000000.00.  The latter two conditions enforce it.
-		if s[0][0] < '0' || s[0][0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
+		if err != nil || mStr[0] < '0' || mStr[0] > '9' || meters > 90000000 || (meters == 90000000 && cmeters != 0) {
 			return
 		}
-	case 0:
-		// huh?
-		return 0, 0, false
 	}
-	ok = true
+
 	if meters > 0 {
 		e = 2
 		val = meters
@@ -1263,8 +1302,7 @@ func stringToCm(token string) (e, m uint8, ok bool) {
 		e++
 		val /= 10
 	}
-	m = uint8(val)
-	return
+	return e, uint8(val), true
 }
 
 func toAbsoluteName(name, origin string) (absolute string, ok bool) {
@@ -1337,12 +1375,12 @@ func slurpRemainder(c *zlexer) *ParseError {
 	case zBlank:
 		l, _ = c.Next()
 		if l.value != zNewline && l.value != zEOF {
-			return &ParseError{"", "garbage after rdata", l}
+			return &ParseError{err: "garbage after rdata", lex: l}
 		}
 	case zNewline:
 	case zEOF:
 	default:
-		return &ParseError{"", "garbage after rdata", l}
+		return &ParseError{err: "garbage after rdata", lex: l}
 	}
 	return nil
 }
@@ -1351,16 +1389,16 @@ func slurpRemainder(c *zlexer) *ParseError {
 // Used for NID and L64 record.
 func stringToNodeID(l lex) (uint64, *ParseError) {
 	if len(l.token) < 19 {
-		return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
+		return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
 	}
 	// There must be three colons at fixes positions, if not its a parse error
 	if l.token[4] != ':' && l.token[9] != ':' && l.token[14] != ':' {
-		return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
+		return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
 	}
 	s := l.token[0:4] + l.token[5:9] + l.token[10:14] + l.token[15:19]
 	u, err := strconv.ParseUint(s, 16, 64)
 	if err != nil {
-		return 0, &ParseError{l.token, "bad NID/L64 NodeID/Locator64", l}
+		return 0, &ParseError{file: l.token, err: "bad NID/L64 NodeID/Locator64", lex: l}
 	}
 	return u, nil
 }
diff --git a/vendor/github.com/miekg/dns/scan_rr.go b/vendor/github.com/miekg/dns/scan_rr.go
index d08c8e6a7..1a90c61f8 100644
--- a/vendor/github.com/miekg/dns/scan_rr.go
+++ b/vendor/github.com/miekg/dns/scan_rr.go
@@ -1,9 +1,9 @@
 package dns
 
 import (
-	"bytes"
 	"encoding/base64"
 	"errors"
+	"fmt"
 	"net"
 	"strconv"
 	"strings"
@@ -12,23 +12,23 @@ import (
 // A remainder of the rdata with embedded spaces, return the parsed string (sans the spaces)
 // or an error
 func endingToString(c *zlexer, errstr string) (string, *ParseError) {
-	var buffer bytes.Buffer
+	var s strings.Builder
 	l, _ := c.Next() // zString
 	for l.value != zNewline && l.value != zEOF {
 		if l.err {
-			return buffer.String(), &ParseError{"", errstr, l}
+			return s.String(), &ParseError{err: errstr, lex: l}
 		}
 		switch l.value {
 		case zString:
-			buffer.WriteString(l.token)
+			s.WriteString(l.token)
 		case zBlank: // Ok
 		default:
-			return "", &ParseError{"", errstr, l}
+			return "", &ParseError{err: errstr, lex: l}
 		}
 		l, _ = c.Next()
 	}
 
-	return buffer.String(), nil
+	return s.String(), nil
 }
 
 // A remainder of the rdata with embedded spaces, split on unquoted whitespace
@@ -37,7 +37,7 @@ func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) {
 	// Get the remaining data until we see a zNewline
 	l, _ := c.Next()
 	if l.err {
-		return nil, &ParseError{"", errstr, l}
+		return nil, &ParseError{err: errstr, lex: l}
 	}
 
 	// Build the slice
@@ -46,7 +46,7 @@ func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) {
 	empty := false
 	for l.value != zNewline && l.value != zEOF {
 		if l.err {
-			return nil, &ParseError{"", errstr, l}
+			return nil, &ParseError{err: errstr, lex: l}
 		}
 		switch l.value {
 		case zString:
@@ -73,7 +73,7 @@ func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) {
 		case zBlank:
 			if quote {
 				// zBlank can only be seen in between txt parts.
-				return nil, &ParseError{"", errstr, l}
+				return nil, &ParseError{err: errstr, lex: l}
 			}
 		case zQuote:
 			if empty && quote {
@@ -82,13 +82,13 @@ func endingToTxtSlice(c *zlexer, errstr string) ([]string, *ParseError) {
 			quote = !quote
 			empty = true
 		default:
-			return nil, &ParseError{"", errstr, l}
+			return nil, &ParseError{err: errstr, lex: l}
 		}
 		l, _ = c.Next()
 	}
 
 	if quote {
-		return nil, &ParseError{"", errstr, l}
+		return nil, &ParseError{err: errstr, lex: l}
 	}
 
 	return s, nil
@@ -103,7 +103,7 @@ func (rr *A) parse(c *zlexer, o string) *ParseError {
 	// IPv4.
 	isIPv4 := !strings.Contains(l.token, ":")
 	if rr.A == nil || !isIPv4 || l.err {
-		return &ParseError{"", "bad A A", l}
+		return &ParseError{err: "bad A A", lex: l}
 	}
 	return slurpRemainder(c)
 }
@@ -115,7 +115,7 @@ func (rr *AAAA) parse(c *zlexer, o string) *ParseError {
 	// addresses cannot include ":".
 	isIPv6 := strings.Contains(l.token, ":")
 	if rr.AAAA == nil || !isIPv6 || l.err {
-		return &ParseError{"", "bad AAAA AAAA", l}
+		return &ParseError{err: "bad AAAA AAAA", lex: l}
 	}
 	return slurpRemainder(c)
 }
@@ -124,7 +124,7 @@ func (rr *NS) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad NS Ns", l}
+		return &ParseError{err: "bad NS Ns", lex: l}
 	}
 	rr.Ns = name
 	return slurpRemainder(c)
@@ -134,7 +134,7 @@ func (rr *PTR) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad PTR Ptr", l}
+		return &ParseError{err: "bad PTR Ptr", lex: l}
 	}
 	rr.Ptr = name
 	return slurpRemainder(c)
@@ -144,7 +144,7 @@ func (rr *NSAPPTR) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad NSAP-PTR Ptr", l}
+		return &ParseError{err: "bad NSAP-PTR Ptr", lex: l}
 	}
 	rr.Ptr = name
 	return slurpRemainder(c)
@@ -154,7 +154,7 @@ func (rr *RP) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	mbox, mboxOk := toAbsoluteName(l.token, o)
 	if l.err || !mboxOk {
-		return &ParseError{"", "bad RP Mbox", l}
+		return &ParseError{err: "bad RP Mbox", lex: l}
 	}
 	rr.Mbox = mbox
 
@@ -164,7 +164,7 @@ func (rr *RP) parse(c *zlexer, o string) *ParseError {
 
 	txt, txtOk := toAbsoluteName(l.token, o)
 	if l.err || !txtOk {
-		return &ParseError{"", "bad RP Txt", l}
+		return &ParseError{err: "bad RP Txt", lex: l}
 	}
 	rr.Txt = txt
 
@@ -175,7 +175,7 @@ func (rr *MR) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad MR Mr", l}
+		return &ParseError{err: "bad MR Mr", lex: l}
 	}
 	rr.Mr = name
 	return slurpRemainder(c)
@@ -185,7 +185,7 @@ func (rr *MB) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad MB Mb", l}
+		return &ParseError{err: "bad MB Mb", lex: l}
 	}
 	rr.Mb = name
 	return slurpRemainder(c)
@@ -195,7 +195,7 @@ func (rr *MG) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad MG Mg", l}
+		return &ParseError{err: "bad MG Mg", lex: l}
 	}
 	rr.Mg = name
 	return slurpRemainder(c)
@@ -220,6 +220,29 @@ func (rr *HINFO) parse(c *zlexer, o string) *ParseError {
 
 	rr.Cpu = chunks[0]
 	rr.Os = strings.Join(chunks[1:], " ")
+	return nil
+}
+
+// according to RFC 1183 the parsing is identical to HINFO, so just use that code.
+func (rr *ISDN) parse(c *zlexer, o string) *ParseError {
+	chunks, e := endingToTxtSlice(c, "bad ISDN Fields")
+	if e != nil {
+		return e
+	}
+
+	if ln := len(chunks); ln == 0 {
+		return nil
+	} else if ln == 1 {
+		// Can we split it?
+		if out := strings.Fields(chunks[0]); len(out) > 1 {
+			chunks = out
+		} else {
+			chunks = append(chunks, "")
+		}
+	}
+
+	rr.Address = chunks[0]
+	rr.SubAddress = strings.Join(chunks[1:], " ")
 
 	return nil
 }
@@ -228,7 +251,7 @@ func (rr *MINFO) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	rmail, rmailOk := toAbsoluteName(l.token, o)
 	if l.err || !rmailOk {
-		return &ParseError{"", "bad MINFO Rmail", l}
+		return &ParseError{err: "bad MINFO Rmail", lex: l}
 	}
 	rr.Rmail = rmail
 
@@ -238,7 +261,7 @@ func (rr *MINFO) parse(c *zlexer, o string) *ParseError {
 
 	email, emailOk := toAbsoluteName(l.token, o)
 	if l.err || !emailOk {
-		return &ParseError{"", "bad MINFO Email", l}
+		return &ParseError{err: "bad MINFO Email", lex: l}
 	}
 	rr.Email = email
 
@@ -249,7 +272,7 @@ func (rr *MF) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad MF Mf", l}
+		return &ParseError{err: "bad MF Mf", lex: l}
 	}
 	rr.Mf = name
 	return slurpRemainder(c)
@@ -259,7 +282,7 @@ func (rr *MD) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad MD Md", l}
+		return &ParseError{err: "bad MD Md", lex: l}
 	}
 	rr.Md = name
 	return slurpRemainder(c)
@@ -269,7 +292,7 @@ func (rr *MX) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad MX Pref", l}
+		return &ParseError{err: "bad MX Pref", lex: l}
 	}
 	rr.Preference = uint16(i)
 
@@ -279,7 +302,7 @@ func (rr *MX) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad MX Mx", l}
+		return &ParseError{err: "bad MX Mx", lex: l}
 	}
 	rr.Mx = name
 
@@ -290,7 +313,7 @@ func (rr *RT) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil {
-		return &ParseError{"", "bad RT Preference", l}
+		return &ParseError{err: "bad RT Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 
@@ -300,7 +323,7 @@ func (rr *RT) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad RT Host", l}
+		return &ParseError{err: "bad RT Host", lex: l}
 	}
 	rr.Host = name
 
@@ -311,7 +334,7 @@ func (rr *AFSDB) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad AFSDB Subtype", l}
+		return &ParseError{err: "bad AFSDB Subtype", lex: l}
 	}
 	rr.Subtype = uint16(i)
 
@@ -321,7 +344,7 @@ func (rr *AFSDB) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad AFSDB Hostname", l}
+		return &ParseError{err: "bad AFSDB Hostname", lex: l}
 	}
 	rr.Hostname = name
 	return slurpRemainder(c)
@@ -330,7 +353,7 @@ func (rr *AFSDB) parse(c *zlexer, o string) *ParseError {
 func (rr *X25) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	if l.err {
-		return &ParseError{"", "bad X25 PSDNAddress", l}
+		return &ParseError{err: "bad X25 PSDNAddress", lex: l}
 	}
 	rr.PSDNAddress = l.token
 	return slurpRemainder(c)
@@ -340,7 +363,7 @@ func (rr *KX) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad KX Pref", l}
+		return &ParseError{err: "bad KX Pref", lex: l}
 	}
 	rr.Preference = uint16(i)
 
@@ -350,7 +373,7 @@ func (rr *KX) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad KX Exchanger", l}
+		return &ParseError{err: "bad KX Exchanger", lex: l}
 	}
 	rr.Exchanger = name
 	return slurpRemainder(c)
@@ -360,7 +383,7 @@ func (rr *CNAME) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad CNAME Target", l}
+		return &ParseError{err: "bad CNAME Target", lex: l}
 	}
 	rr.Target = name
 	return slurpRemainder(c)
@@ -370,7 +393,7 @@ func (rr *DNAME) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad DNAME Target", l}
+		return &ParseError{err: "bad DNAME Target", lex: l}
 	}
 	rr.Target = name
 	return slurpRemainder(c)
@@ -380,7 +403,7 @@ func (rr *SOA) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	ns, nsOk := toAbsoluteName(l.token, o)
 	if l.err || !nsOk {
-		return &ParseError{"", "bad SOA Ns", l}
+		return &ParseError{err: "bad SOA Ns", lex: l}
 	}
 	rr.Ns = ns
 
@@ -390,7 +413,7 @@ func (rr *SOA) parse(c *zlexer, o string) *ParseError {
 
 	mbox, mboxOk := toAbsoluteName(l.token, o)
 	if l.err || !mboxOk {
-		return &ParseError{"", "bad SOA Mbox", l}
+		return &ParseError{err: "bad SOA Mbox", lex: l}
 	}
 	rr.Mbox = mbox
 
@@ -403,16 +426,16 @@ func (rr *SOA) parse(c *zlexer, o string) *ParseError {
 	for i := 0; i < 5; i++ {
 		l, _ = c.Next()
 		if l.err {
-			return &ParseError{"", "bad SOA zone parameter", l}
+			return &ParseError{err: "bad SOA zone parameter", lex: l}
 		}
 		if j, err := strconv.ParseUint(l.token, 10, 32); err != nil {
 			if i == 0 {
 				// Serial must be a number
-				return &ParseError{"", "bad SOA zone parameter", l}
+				return &ParseError{err: "bad SOA zone parameter", lex: l}
 			}
 			// We allow other fields to be unitful duration strings
 			if v, ok = stringToTTL(l.token); !ok {
-				return &ParseError{"", "bad SOA zone parameter", l}
+				return &ParseError{err: "bad SOA zone parameter", lex: l}
 
 			}
 		} else {
@@ -442,7 +465,7 @@ func (rr *SRV) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad SRV Priority", l}
+		return &ParseError{err: "bad SRV Priority", lex: l}
 	}
 	rr.Priority = uint16(i)
 
@@ -450,7 +473,7 @@ func (rr *SRV) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next() // zString
 	i, e1 := strconv.ParseUint(l.token, 10, 16)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad SRV Weight", l}
+		return &ParseError{err: "bad SRV Weight", lex: l}
 	}
 	rr.Weight = uint16(i)
 
@@ -458,7 +481,7 @@ func (rr *SRV) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next() // zString
 	i, e2 := strconv.ParseUint(l.token, 10, 16)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad SRV Port", l}
+		return &ParseError{err: "bad SRV Port", lex: l}
 	}
 	rr.Port = uint16(i)
 
@@ -468,7 +491,7 @@ func (rr *SRV) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad SRV Target", l}
+		return &ParseError{err: "bad SRV Target", lex: l}
 	}
 	rr.Target = name
 	return slurpRemainder(c)
@@ -478,7 +501,7 @@ func (rr *NAPTR) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad NAPTR Order", l}
+		return &ParseError{err: "bad NAPTR Order", lex: l}
 	}
 	rr.Order = uint16(i)
 
@@ -486,7 +509,7 @@ func (rr *NAPTR) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next() // zString
 	i, e1 := strconv.ParseUint(l.token, 10, 16)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad NAPTR Preference", l}
+		return &ParseError{err: "bad NAPTR Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 
@@ -494,57 +517,57 @@ func (rr *NAPTR) parse(c *zlexer, o string) *ParseError {
 	c.Next()        // zBlank
 	l, _ = c.Next() // _QUOTE
 	if l.value != zQuote {
-		return &ParseError{"", "bad NAPTR Flags", l}
+		return &ParseError{err: "bad NAPTR Flags", lex: l}
 	}
 	l, _ = c.Next() // Either String or Quote
 	if l.value == zString {
 		rr.Flags = l.token
 		l, _ = c.Next() // _QUOTE
 		if l.value != zQuote {
-			return &ParseError{"", "bad NAPTR Flags", l}
+			return &ParseError{err: "bad NAPTR Flags", lex: l}
 		}
 	} else if l.value == zQuote {
 		rr.Flags = ""
 	} else {
-		return &ParseError{"", "bad NAPTR Flags", l}
+		return &ParseError{err: "bad NAPTR Flags", lex: l}
 	}
 
 	// Service
 	c.Next()        // zBlank
 	l, _ = c.Next() // _QUOTE
 	if l.value != zQuote {
-		return &ParseError{"", "bad NAPTR Service", l}
+		return &ParseError{err: "bad NAPTR Service", lex: l}
 	}
 	l, _ = c.Next() // Either String or Quote
 	if l.value == zString {
 		rr.Service = l.token
 		l, _ = c.Next() // _QUOTE
 		if l.value != zQuote {
-			return &ParseError{"", "bad NAPTR Service", l}
+			return &ParseError{err: "bad NAPTR Service", lex: l}
 		}
 	} else if l.value == zQuote {
 		rr.Service = ""
 	} else {
-		return &ParseError{"", "bad NAPTR Service", l}
+		return &ParseError{err: "bad NAPTR Service", lex: l}
 	}
 
 	// Regexp
 	c.Next()        // zBlank
 	l, _ = c.Next() // _QUOTE
 	if l.value != zQuote {
-		return &ParseError{"", "bad NAPTR Regexp", l}
+		return &ParseError{err: "bad NAPTR Regexp", lex: l}
 	}
 	l, _ = c.Next() // Either String or Quote
 	if l.value == zString {
 		rr.Regexp = l.token
 		l, _ = c.Next() // _QUOTE
 		if l.value != zQuote {
-			return &ParseError{"", "bad NAPTR Regexp", l}
+			return &ParseError{err: "bad NAPTR Regexp", lex: l}
 		}
 	} else if l.value == zQuote {
 		rr.Regexp = ""
 	} else {
-		return &ParseError{"", "bad NAPTR Regexp", l}
+		return &ParseError{err: "bad NAPTR Regexp", lex: l}
 	}
 
 	// After quote no space??
@@ -554,7 +577,7 @@ func (rr *NAPTR) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad NAPTR Replacement", l}
+		return &ParseError{err: "bad NAPTR Replacement", lex: l}
 	}
 	rr.Replacement = name
 	return slurpRemainder(c)
@@ -564,7 +587,7 @@ func (rr *TALINK) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	previousName, previousNameOk := toAbsoluteName(l.token, o)
 	if l.err || !previousNameOk {
-		return &ParseError{"", "bad TALINK PreviousName", l}
+		return &ParseError{err: "bad TALINK PreviousName", lex: l}
 	}
 	rr.PreviousName = previousName
 
@@ -574,7 +597,7 @@ func (rr *TALINK) parse(c *zlexer, o string) *ParseError {
 
 	nextName, nextNameOk := toAbsoluteName(l.token, o)
 	if l.err || !nextNameOk {
-		return &ParseError{"", "bad TALINK NextName", l}
+		return &ParseError{err: "bad TALINK NextName", lex: l}
 	}
 	rr.NextName = nextName
 
@@ -592,7 +615,7 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 32)
 	if e != nil || l.err || i > 90 {
-		return &ParseError{"", "bad LOC Latitude", l}
+		return &ParseError{err: "bad LOC Latitude", lex: l}
 	}
 	rr.Latitude = 1000 * 60 * 60 * uint32(i)
 
@@ -603,7 +626,7 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError {
 		goto East
 	}
 	if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 59 {
-		return &ParseError{"", "bad LOC Latitude minutes", l}
+		return &ParseError{err: "bad LOC Latitude minutes", lex: l}
 	} else {
 		rr.Latitude += 1000 * 60 * uint32(i)
 	}
@@ -611,7 +634,7 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError {
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 {
-		return &ParseError{"", "bad LOC Latitude seconds", l}
+		return &ParseError{err: "bad LOC Latitude seconds", lex: l}
 	} else {
 		rr.Latitude += uint32(1000 * i)
 	}
@@ -622,14 +645,14 @@ func (rr *LOC) parse(c *zlexer, o string) *ParseError {
 		goto East
 	}
 	// If still alive, flag an error
-	return &ParseError{"", "bad LOC Latitude North/South", l}
+	return &ParseError{err: "bad LOC Latitude North/South", lex: l}
 
 East:
 	// East
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 180 {
-		return &ParseError{"", "bad LOC Longitude", l}
+		return &ParseError{err: "bad LOC Longitude", lex: l}
 	} else {
 		rr.Longitude = 1000 * 60 * 60 * uint32(i)
 	}
@@ -640,14 +663,14 @@ East:
 		goto Altitude
 	}
 	if i, err := strconv.ParseUint(l.token, 10, 32); err != nil || l.err || i > 59 {
-		return &ParseError{"", "bad LOC Longitude minutes", l}
+		return &ParseError{err: "bad LOC Longitude minutes", lex: l}
 	} else {
 		rr.Longitude += 1000 * 60 * uint32(i)
 	}
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if i, err := strconv.ParseFloat(l.token, 64); err != nil || l.err || i < 0 || i >= 60 {
-		return &ParseError{"", "bad LOC Longitude seconds", l}
+		return &ParseError{err: "bad LOC Longitude seconds", lex: l}
 	} else {
 		rr.Longitude += uint32(1000 * i)
 	}
@@ -658,19 +681,19 @@ East:
 		goto Altitude
 	}
 	// If still alive, flag an error
-	return &ParseError{"", "bad LOC Longitude East/West", l}
+	return &ParseError{err: "bad LOC Longitude East/West", lex: l}
 
 Altitude:
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if l.token == "" || l.err {
-		return &ParseError{"", "bad LOC Altitude", l}
+		return &ParseError{err: "bad LOC Altitude", lex: l}
 	}
 	if l.token[len(l.token)-1] == 'M' || l.token[len(l.token)-1] == 'm' {
 		l.token = l.token[0 : len(l.token)-1]
 	}
 	if i, err := strconv.ParseFloat(l.token, 64); err != nil {
-		return &ParseError{"", "bad LOC Altitude", l}
+		return &ParseError{err: "bad LOC Altitude", lex: l}
 	} else {
 		rr.Altitude = uint32(i*100.0 + 10000000.0 + 0.5)
 	}
@@ -685,19 +708,19 @@ Altitude:
 			case 0: // Size
 				exp, m, ok := stringToCm(l.token)
 				if !ok {
-					return &ParseError{"", "bad LOC Size", l}
+					return &ParseError{err: "bad LOC Size", lex: l}
 				}
 				rr.Size = exp&0x0f | m<<4&0xf0
 			case 1: // HorizPre
 				exp, m, ok := stringToCm(l.token)
 				if !ok {
-					return &ParseError{"", "bad LOC HorizPre", l}
+					return &ParseError{err: "bad LOC HorizPre", lex: l}
 				}
 				rr.HorizPre = exp&0x0f | m<<4&0xf0
 			case 2: // VertPre
 				exp, m, ok := stringToCm(l.token)
 				if !ok {
-					return &ParseError{"", "bad LOC VertPre", l}
+					return &ParseError{err: "bad LOC VertPre", lex: l}
 				}
 				rr.VertPre = exp&0x0f | m<<4&0xf0
 			}
@@ -705,7 +728,7 @@ Altitude:
 		case zBlank:
 			// Ok
 		default:
-			return &ParseError{"", "bad LOC Size, HorizPre or VertPre", l}
+			return &ParseError{err: "bad LOC Size, HorizPre or VertPre", lex: l}
 		}
 		l, _ = c.Next()
 	}
@@ -717,14 +740,14 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad HIP PublicKeyAlgorithm", l}
+		return &ParseError{err: "bad HIP PublicKeyAlgorithm", lex: l}
 	}
 	rr.PublicKeyAlgorithm = uint8(i)
 
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	if l.token == "" || l.err {
-		return &ParseError{"", "bad HIP Hit", l}
+		return &ParseError{err: "bad HIP Hit", lex: l}
 	}
 	rr.Hit = l.token // This can not contain spaces, see RFC 5205 Section 6.
 	rr.HitLength = uint8(len(rr.Hit)) / 2
@@ -732,12 +755,12 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError {
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	if l.token == "" || l.err {
-		return &ParseError{"", "bad HIP PublicKey", l}
+		return &ParseError{err: "bad HIP PublicKey", lex: l}
 	}
 	rr.PublicKey = l.token // This cannot contain spaces
 	decodedPK, decodedPKerr := base64.StdEncoding.DecodeString(rr.PublicKey)
 	if decodedPKerr != nil {
-		return &ParseError{"", "bad HIP PublicKey", l}
+		return &ParseError{err: "bad HIP PublicKey", lex: l}
 	}
 	rr.PublicKeyLength = uint16(len(decodedPK))
 
@@ -749,13 +772,13 @@ func (rr *HIP) parse(c *zlexer, o string) *ParseError {
 		case zString:
 			name, nameOk := toAbsoluteName(l.token, o)
 			if l.err || !nameOk {
-				return &ParseError{"", "bad HIP RendezvousServers", l}
+				return &ParseError{err: "bad HIP RendezvousServers", lex: l}
 			}
 			xs = append(xs, name)
 		case zBlank:
 			// Ok
 		default:
-			return &ParseError{"", "bad HIP RendezvousServers", l}
+			return &ParseError{err: "bad HIP RendezvousServers", lex: l}
 		}
 		l, _ = c.Next()
 	}
@@ -769,7 +792,7 @@ func (rr *CERT) parse(c *zlexer, o string) *ParseError {
 	if v, ok := StringToCertType[l.token]; ok {
 		rr.Type = v
 	} else if i, err := strconv.ParseUint(l.token, 10, 16); err != nil {
-		return &ParseError{"", "bad CERT Type", l}
+		return &ParseError{err: "bad CERT Type", lex: l}
 	} else {
 		rr.Type = uint16(i)
 	}
@@ -777,7 +800,7 @@ func (rr *CERT) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next() // zString
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad CERT KeyTag", l}
+		return &ParseError{err: "bad CERT KeyTag", lex: l}
 	}
 	rr.KeyTag = uint16(i)
 	c.Next()        // zBlank
@@ -785,7 +808,7 @@ func (rr *CERT) parse(c *zlexer, o string) *ParseError {
 	if v, ok := StringToAlgorithm[l.token]; ok {
 		rr.Algorithm = v
 	} else if i, err := strconv.ParseUint(l.token, 10, 8); err != nil {
-		return &ParseError{"", "bad CERT Algorithm", l}
+		return &ParseError{err: "bad CERT Algorithm", lex: l}
 	} else {
 		rr.Algorithm = uint8(i)
 	}
@@ -811,7 +834,7 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError {
 	j, e := strconv.ParseUint(l.token, 10, 32)
 	if e != nil {
 		// Serial must be a number
-		return &ParseError{"", "bad CSYNC serial", l}
+		return &ParseError{err: "bad CSYNC serial", lex: l}
 	}
 	rr.Serial = uint32(j)
 
@@ -821,7 +844,7 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError {
 	j, e1 := strconv.ParseUint(l.token, 10, 16)
 	if e1 != nil {
 		// Serial must be a number
-		return &ParseError{"", "bad CSYNC flags", l}
+		return &ParseError{err: "bad CSYNC flags", lex: l}
 	}
 	rr.Flags = uint16(j)
 
@@ -839,12 +862,12 @@ func (rr *CSYNC) parse(c *zlexer, o string) *ParseError {
 			tokenUpper := strings.ToUpper(l.token)
 			if k, ok = StringToType[tokenUpper]; !ok {
 				if k, ok = typeToInt(l.token); !ok {
-					return &ParseError{"", "bad CSYNC TypeBitMap", l}
+					return &ParseError{err: "bad CSYNC TypeBitMap", lex: l}
 				}
 			}
 			rr.TypeBitMap = append(rr.TypeBitMap, k)
 		default:
-			return &ParseError{"", "bad CSYNC TypeBitMap", l}
+			return &ParseError{err: "bad CSYNC TypeBitMap", lex: l}
 		}
 		l, _ = c.Next()
 	}
@@ -855,7 +878,7 @@ func (rr *ZONEMD) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 32)
 	if e != nil || l.err {
-		return &ParseError{"", "bad ZONEMD Serial", l}
+		return &ParseError{err: "bad ZONEMD Serial", lex: l}
 	}
 	rr.Serial = uint32(i)
 
@@ -863,7 +886,7 @@ func (rr *ZONEMD) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad ZONEMD Scheme", l}
+		return &ParseError{err: "bad ZONEMD Scheme", lex: l}
 	}
 	rr.Scheme = uint8(i)
 
@@ -871,7 +894,7 @@ func (rr *ZONEMD) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, err := strconv.ParseUint(l.token, 10, 8)
 	if err != nil || l.err {
-		return &ParseError{"", "bad ZONEMD Hash Algorithm", l}
+		return &ParseError{err: "bad ZONEMD Hash Algorithm", lex: l}
 	}
 	rr.Hash = uint8(i)
 
@@ -892,11 +915,11 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 		if strings.HasPrefix(tokenUpper, "TYPE") {
 			t, ok = typeToInt(l.token)
 			if !ok {
-				return &ParseError{"", "bad RRSIG Typecovered", l}
+				return &ParseError{err: "bad RRSIG Typecovered", lex: l}
 			}
 			rr.TypeCovered = t
 		} else {
-			return &ParseError{"", "bad RRSIG Typecovered", l}
+			return &ParseError{err: "bad RRSIG Typecovered", lex: l}
 		}
 	} else {
 		rr.TypeCovered = t
@@ -905,14 +928,14 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if l.err {
-		return &ParseError{"", "bad RRSIG Algorithm", l}
+		return &ParseError{err: "bad RRSIG Algorithm", lex: l}
 	}
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	rr.Algorithm = uint8(i) // if 0 we'll check the mnemonic in the if
 	if e != nil {
 		v, ok := StringToAlgorithm[l.token]
 		if !ok {
-			return &ParseError{"", "bad RRSIG Algorithm", l}
+			return &ParseError{err: "bad RRSIG Algorithm", lex: l}
 		}
 		rr.Algorithm = v
 	}
@@ -921,7 +944,7 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad RRSIG Labels", l}
+		return &ParseError{err: "bad RRSIG Labels", lex: l}
 	}
 	rr.Labels = uint8(i)
 
@@ -929,7 +952,7 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e2 := strconv.ParseUint(l.token, 10, 32)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad RRSIG OrigTtl", l}
+		return &ParseError{err: "bad RRSIG OrigTtl", lex: l}
 	}
 	rr.OrigTtl = uint32(i)
 
@@ -940,7 +963,7 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 		if i, err := strconv.ParseUint(l.token, 10, 32); err == nil {
 			rr.Expiration = uint32(i)
 		} else {
-			return &ParseError{"", "bad RRSIG Expiration", l}
+			return &ParseError{err: "bad RRSIG Expiration", lex: l}
 		}
 	} else {
 		rr.Expiration = i
@@ -952,7 +975,7 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 		if i, err := strconv.ParseUint(l.token, 10, 32); err == nil {
 			rr.Inception = uint32(i)
 		} else {
-			return &ParseError{"", "bad RRSIG Inception", l}
+			return &ParseError{err: "bad RRSIG Inception", lex: l}
 		}
 	} else {
 		rr.Inception = i
@@ -962,7 +985,7 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e3 := strconv.ParseUint(l.token, 10, 16)
 	if e3 != nil || l.err {
-		return &ParseError{"", "bad RRSIG KeyTag", l}
+		return &ParseError{err: "bad RRSIG KeyTag", lex: l}
 	}
 	rr.KeyTag = uint16(i)
 
@@ -971,7 +994,7 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 	rr.SignerName = l.token
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad RRSIG SignerName", l}
+		return &ParseError{err: "bad RRSIG SignerName", lex: l}
 	}
 	rr.SignerName = name
 
@@ -984,11 +1007,13 @@ func (rr *RRSIG) parse(c *zlexer, o string) *ParseError {
 	return nil
 }
 
+func (rr *NXT) parse(c *zlexer, o string) *ParseError { return rr.NSEC.parse(c, o) }
+
 func (rr *NSEC) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad NSEC NextDomain", l}
+		return &ParseError{err: "bad NSEC NextDomain", lex: l}
 	}
 	rr.NextDomain = name
 
@@ -1006,12 +1031,12 @@ func (rr *NSEC) parse(c *zlexer, o string) *ParseError {
 			tokenUpper := strings.ToUpper(l.token)
 			if k, ok = StringToType[tokenUpper]; !ok {
 				if k, ok = typeToInt(l.token); !ok {
-					return &ParseError{"", "bad NSEC TypeBitMap", l}
+					return &ParseError{err: "bad NSEC TypeBitMap", lex: l}
 				}
 			}
 			rr.TypeBitMap = append(rr.TypeBitMap, k)
 		default:
-			return &ParseError{"", "bad NSEC TypeBitMap", l}
+			return &ParseError{err: "bad NSEC TypeBitMap", lex: l}
 		}
 		l, _ = c.Next()
 	}
@@ -1022,27 +1047,27 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad NSEC3 Hash", l}
+		return &ParseError{err: "bad NSEC3 Hash", lex: l}
 	}
 	rr.Hash = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad NSEC3 Flags", l}
+		return &ParseError{err: "bad NSEC3 Flags", lex: l}
 	}
 	rr.Flags = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e2 := strconv.ParseUint(l.token, 10, 16)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad NSEC3 Iterations", l}
+		return &ParseError{err: "bad NSEC3 Iterations", lex: l}
 	}
 	rr.Iterations = uint16(i)
 	c.Next()
 	l, _ = c.Next()
 	if l.token == "" || l.err {
-		return &ParseError{"", "bad NSEC3 Salt", l}
+		return &ParseError{err: "bad NSEC3 Salt", lex: l}
 	}
 	if l.token != "-" {
 		rr.SaltLength = uint8(len(l.token)) / 2
@@ -1052,7 +1077,7 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError {
 	c.Next()
 	l, _ = c.Next()
 	if l.token == "" || l.err {
-		return &ParseError{"", "bad NSEC3 NextDomain", l}
+		return &ParseError{err: "bad NSEC3 NextDomain", lex: l}
 	}
 	rr.HashLength = 20 // Fix for NSEC3 (sha1 160 bits)
 	rr.NextDomain = l.token
@@ -1071,12 +1096,12 @@ func (rr *NSEC3) parse(c *zlexer, o string) *ParseError {
 			tokenUpper := strings.ToUpper(l.token)
 			if k, ok = StringToType[tokenUpper]; !ok {
 				if k, ok = typeToInt(l.token); !ok {
-					return &ParseError{"", "bad NSEC3 TypeBitMap", l}
+					return &ParseError{err: "bad NSEC3 TypeBitMap", lex: l}
 				}
 			}
 			rr.TypeBitMap = append(rr.TypeBitMap, k)
 		default:
-			return &ParseError{"", "bad NSEC3 TypeBitMap", l}
+			return &ParseError{err: "bad NSEC3 TypeBitMap", lex: l}
 		}
 		l, _ = c.Next()
 	}
@@ -1087,21 +1112,21 @@ func (rr *NSEC3PARAM) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad NSEC3PARAM Hash", l}
+		return &ParseError{err: "bad NSEC3PARAM Hash", lex: l}
 	}
 	rr.Hash = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad NSEC3PARAM Flags", l}
+		return &ParseError{err: "bad NSEC3PARAM Flags", lex: l}
 	}
 	rr.Flags = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e2 := strconv.ParseUint(l.token, 10, 16)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad NSEC3PARAM Iterations", l}
+		return &ParseError{err: "bad NSEC3PARAM Iterations", lex: l}
 	}
 	rr.Iterations = uint16(i)
 	c.Next()
@@ -1116,7 +1141,7 @@ func (rr *NSEC3PARAM) parse(c *zlexer, o string) *ParseError {
 func (rr *EUI48) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	if len(l.token) != 17 || l.err {
-		return &ParseError{"", "bad EUI48 Address", l}
+		return &ParseError{err: "bad EUI48 Address", lex: l}
 	}
 	addr := make([]byte, 12)
 	dash := 0
@@ -1125,7 +1150,7 @@ func (rr *EUI48) parse(c *zlexer, o string) *ParseError {
 		addr[i+1] = l.token[i+1+dash]
 		dash++
 		if l.token[i+1+dash] != '-' {
-			return &ParseError{"", "bad EUI48 Address", l}
+			return &ParseError{err: "bad EUI48 Address", lex: l}
 		}
 	}
 	addr[10] = l.token[15]
@@ -1133,7 +1158,7 @@ func (rr *EUI48) parse(c *zlexer, o string) *ParseError {
 
 	i, e := strconv.ParseUint(string(addr), 16, 48)
 	if e != nil {
-		return &ParseError{"", "bad EUI48 Address", l}
+		return &ParseError{err: "bad EUI48 Address", lex: l}
 	}
 	rr.Address = i
 	return slurpRemainder(c)
@@ -1142,7 +1167,7 @@ func (rr *EUI48) parse(c *zlexer, o string) *ParseError {
 func (rr *EUI64) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	if len(l.token) != 23 || l.err {
-		return &ParseError{"", "bad EUI64 Address", l}
+		return &ParseError{err: "bad EUI64 Address", lex: l}
 	}
 	addr := make([]byte, 16)
 	dash := 0
@@ -1151,7 +1176,7 @@ func (rr *EUI64) parse(c *zlexer, o string) *ParseError {
 		addr[i+1] = l.token[i+1+dash]
 		dash++
 		if l.token[i+1+dash] != '-' {
-			return &ParseError{"", "bad EUI64 Address", l}
+			return &ParseError{err: "bad EUI64 Address", lex: l}
 		}
 	}
 	addr[14] = l.token[21]
@@ -1159,7 +1184,7 @@ func (rr *EUI64) parse(c *zlexer, o string) *ParseError {
 
 	i, e := strconv.ParseUint(string(addr), 16, 64)
 	if e != nil {
-		return &ParseError{"", "bad EUI68 Address", l}
+		return &ParseError{err: "bad EUI68 Address", lex: l}
 	}
 	rr.Address = i
 	return slurpRemainder(c)
@@ -1169,14 +1194,14 @@ func (rr *SSHFP) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad SSHFP Algorithm", l}
+		return &ParseError{err: "bad SSHFP Algorithm", lex: l}
 	}
 	rr.Algorithm = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad SSHFP Type", l}
+		return &ParseError{err: "bad SSHFP Type", lex: l}
 	}
 	rr.Type = uint8(i)
 	c.Next() // zBlank
@@ -1192,21 +1217,21 @@ func (rr *DNSKEY) parseDNSKEY(c *zlexer, o, typ string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad " + typ + " Flags", l}
+		return &ParseError{err: "bad " + typ + " Flags", lex: l}
 	}
 	rr.Flags = uint16(i)
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad " + typ + " Protocol", l}
+		return &ParseError{err: "bad " + typ + " Protocol", lex: l}
 	}
 	rr.Protocol = uint8(i)
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	i, e2 := strconv.ParseUint(l.token, 10, 8)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad " + typ + " Algorithm", l}
+		return &ParseError{err: "bad " + typ + " Algorithm", lex: l}
 	}
 	rr.Algorithm = uint8(i)
 	s, e3 := endingToString(c, "bad "+typ+" PublicKey")
@@ -1228,7 +1253,7 @@ func (rr *IPSECKEY) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	num, err := strconv.ParseUint(l.token, 10, 8)
 	if err != nil || l.err {
-		return &ParseError{"", "bad IPSECKEY value", l}
+		return &ParseError{err: "bad IPSECKEY value", lex: l}
 	}
 	rr.Precedence = uint8(num)
 	c.Next() // zBlank
@@ -1236,7 +1261,7 @@ func (rr *IPSECKEY) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	num, err = strconv.ParseUint(l.token, 10, 8)
 	if err != nil || l.err {
-		return &ParseError{"", "bad IPSECKEY value", l}
+		return &ParseError{err: "bad IPSECKEY value", lex: l}
 	}
 	rr.GatewayType = uint8(num)
 	c.Next() // zBlank
@@ -1244,19 +1269,19 @@ func (rr *IPSECKEY) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	num, err = strconv.ParseUint(l.token, 10, 8)
 	if err != nil || l.err {
-		return &ParseError{"", "bad IPSECKEY value", l}
+		return &ParseError{err: "bad IPSECKEY value", lex: l}
 	}
 	rr.Algorithm = uint8(num)
 	c.Next() // zBlank
 
 	l, _ = c.Next()
 	if l.err {
-		return &ParseError{"", "bad IPSECKEY gateway", l}
+		return &ParseError{err: "bad IPSECKEY gateway", lex: l}
 	}
 
 	rr.GatewayAddr, rr.GatewayHost, err = parseAddrHostUnion(l.token, o, rr.GatewayType)
 	if err != nil {
-		return &ParseError{"", "IPSECKEY " + err.Error(), l}
+		return &ParseError{wrappedErr: fmt.Errorf("IPSECKEY %w", err), lex: l}
 	}
 
 	c.Next() // zBlank
@@ -1273,14 +1298,14 @@ func (rr *AMTRELAY) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	num, err := strconv.ParseUint(l.token, 10, 8)
 	if err != nil || l.err {
-		return &ParseError{"", "bad AMTRELAY value", l}
+		return &ParseError{err: "bad AMTRELAY value", lex: l}
 	}
 	rr.Precedence = uint8(num)
 	c.Next() // zBlank
 
 	l, _ = c.Next()
 	if l.err || !(l.token == "0" || l.token == "1") {
-		return &ParseError{"", "bad discovery value", l}
+		return &ParseError{err: "bad discovery value", lex: l}
 	}
 	if l.token == "1" {
 		rr.GatewayType = 0x80
@@ -1291,19 +1316,19 @@ func (rr *AMTRELAY) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	num, err = strconv.ParseUint(l.token, 10, 8)
 	if err != nil || l.err {
-		return &ParseError{"", "bad AMTRELAY value", l}
+		return &ParseError{err: "bad AMTRELAY value", lex: l}
 	}
 	rr.GatewayType |= uint8(num)
 	c.Next() // zBlank
 
 	l, _ = c.Next()
 	if l.err {
-		return &ParseError{"", "bad AMTRELAY gateway", l}
+		return &ParseError{err: "bad AMTRELAY gateway", lex: l}
 	}
 
 	rr.GatewayAddr, rr.GatewayHost, err = parseAddrHostUnion(l.token, o, rr.GatewayType&0x7f)
 	if err != nil {
-		return &ParseError{"", "AMTRELAY " + err.Error(), l}
+		return &ParseError{wrappedErr: fmt.Errorf("AMTRELAY %w", err), lex: l}
 	}
 
 	return slurpRemainder(c)
@@ -1339,21 +1364,21 @@ func (rr *RKEY) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad RKEY Flags", l}
+		return &ParseError{err: "bad RKEY Flags", lex: l}
 	}
 	rr.Flags = uint16(i)
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad RKEY Protocol", l}
+		return &ParseError{err: "bad RKEY Protocol", lex: l}
 	}
 	rr.Protocol = uint8(i)
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	i, e2 := strconv.ParseUint(l.token, 10, 8)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad RKEY Algorithm", l}
+		return &ParseError{err: "bad RKEY Algorithm", lex: l}
 	}
 	rr.Algorithm = uint8(i)
 	s, e3 := endingToString(c, "bad RKEY PublicKey")
@@ -1386,21 +1411,21 @@ func (rr *GPOS) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	_, e := strconv.ParseFloat(l.token, 64)
 	if e != nil || l.err {
-		return &ParseError{"", "bad GPOS Longitude", l}
+		return &ParseError{err: "bad GPOS Longitude", lex: l}
 	}
 	rr.Longitude = l.token
 	c.Next() // zBlank
 	l, _ = c.Next()
 	_, e1 := strconv.ParseFloat(l.token, 64)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad GPOS Latitude", l}
+		return &ParseError{err: "bad GPOS Latitude", lex: l}
 	}
 	rr.Latitude = l.token
 	c.Next() // zBlank
 	l, _ = c.Next()
 	_, e2 := strconv.ParseFloat(l.token, 64)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad GPOS Altitude", l}
+		return &ParseError{err: "bad GPOS Altitude", lex: l}
 	}
 	rr.Altitude = l.token
 	return slurpRemainder(c)
@@ -1410,7 +1435,7 @@ func (rr *DS) parseDS(c *zlexer, o, typ string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad " + typ + " KeyTag", l}
+		return &ParseError{err: "bad " + typ + " KeyTag", lex: l}
 	}
 	rr.KeyTag = uint16(i)
 	c.Next() // zBlank
@@ -1419,7 +1444,7 @@ func (rr *DS) parseDS(c *zlexer, o, typ string) *ParseError {
 		tokenUpper := strings.ToUpper(l.token)
 		i, ok := StringToAlgorithm[tokenUpper]
 		if !ok || l.err {
-			return &ParseError{"", "bad " + typ + " Algorithm", l}
+			return &ParseError{err: "bad " + typ + " Algorithm", lex: l}
 		}
 		rr.Algorithm = i
 	} else {
@@ -1429,7 +1454,7 @@ func (rr *DS) parseDS(c *zlexer, o, typ string) *ParseError {
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad " + typ + " DigestType", l}
+		return &ParseError{err: "bad " + typ + " DigestType", lex: l}
 	}
 	rr.DigestType = uint8(i)
 	s, e2 := endingToString(c, "bad "+typ+" Digest")
@@ -1444,7 +1469,7 @@ func (rr *TA) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad TA KeyTag", l}
+		return &ParseError{err: "bad TA KeyTag", lex: l}
 	}
 	rr.KeyTag = uint16(i)
 	c.Next() // zBlank
@@ -1453,7 +1478,7 @@ func (rr *TA) parse(c *zlexer, o string) *ParseError {
 		tokenUpper := strings.ToUpper(l.token)
 		i, ok := StringToAlgorithm[tokenUpper]
 		if !ok || l.err {
-			return &ParseError{"", "bad TA Algorithm", l}
+			return &ParseError{err: "bad TA Algorithm", lex: l}
 		}
 		rr.Algorithm = i
 	} else {
@@ -1463,7 +1488,7 @@ func (rr *TA) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad TA DigestType", l}
+		return &ParseError{err: "bad TA DigestType", lex: l}
 	}
 	rr.DigestType = uint8(i)
 	s, e2 := endingToString(c, "bad TA Digest")
@@ -1478,21 +1503,21 @@ func (rr *TLSA) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad TLSA Usage", l}
+		return &ParseError{err: "bad TLSA Usage", lex: l}
 	}
 	rr.Usage = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad TLSA Selector", l}
+		return &ParseError{err: "bad TLSA Selector", lex: l}
 	}
 	rr.Selector = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e2 := strconv.ParseUint(l.token, 10, 8)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad TLSA MatchingType", l}
+		return &ParseError{err: "bad TLSA MatchingType", lex: l}
 	}
 	rr.MatchingType = uint8(i)
 	// So this needs be e2 (i.e. different than e), because...??t
@@ -1508,21 +1533,21 @@ func (rr *SMIMEA) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad SMIMEA Usage", l}
+		return &ParseError{err: "bad SMIMEA Usage", lex: l}
 	}
 	rr.Usage = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad SMIMEA Selector", l}
+		return &ParseError{err: "bad SMIMEA Selector", lex: l}
 	}
 	rr.Selector = uint8(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e2 := strconv.ParseUint(l.token, 10, 8)
 	if e2 != nil || l.err {
-		return &ParseError{"", "bad SMIMEA MatchingType", l}
+		return &ParseError{err: "bad SMIMEA MatchingType", lex: l}
 	}
 	rr.MatchingType = uint8(i)
 	// So this needs be e2 (i.e. different than e), because...??t
@@ -1537,14 +1562,14 @@ func (rr *SMIMEA) parse(c *zlexer, o string) *ParseError {
 func (rr *RFC3597) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	if l.token != "\\#" {
-		return &ParseError{"", "bad RFC3597 Rdata", l}
+		return &ParseError{err: "bad RFC3597 Rdata", lex: l}
 	}
 
 	c.Next() // zBlank
 	l, _ = c.Next()
 	rdlength, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad RFC3597 Rdata ", l}
+		return &ParseError{err: "bad RFC3597 Rdata ", lex: l}
 	}
 
 	s, e1 := endingToString(c, "bad RFC3597 Rdata")
@@ -1552,7 +1577,7 @@ func (rr *RFC3597) parse(c *zlexer, o string) *ParseError {
 		return e1
 	}
 	if int(rdlength)*2 != len(s) {
-		return &ParseError{"", "bad RFC3597 Rdata", l}
+		return &ParseError{err: "bad RFC3597 Rdata", lex: l}
 	}
 	rr.Rdata = s
 	return nil
@@ -1600,14 +1625,14 @@ func (rr *URI) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad URI Priority", l}
+		return &ParseError{err: "bad URI Priority", lex: l}
 	}
 	rr.Priority = uint16(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 16)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad URI Weight", l}
+		return &ParseError{err: "bad URI Weight", lex: l}
 	}
 	rr.Weight = uint16(i)
 
@@ -1617,7 +1642,7 @@ func (rr *URI) parse(c *zlexer, o string) *ParseError {
 		return e2
 	}
 	if len(s) != 1 {
-		return &ParseError{"", "bad URI Target", l}
+		return &ParseError{err: "bad URI Target", lex: l}
 	}
 	rr.Target = s[0]
 	return nil
@@ -1637,7 +1662,7 @@ func (rr *NID) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad NID Preference", l}
+		return &ParseError{err: "bad NID Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 	c.Next()        // zBlank
@@ -1654,14 +1679,14 @@ func (rr *L32) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad L32 Preference", l}
+		return &ParseError{err: "bad L32 Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	rr.Locator32 = net.ParseIP(l.token)
 	if rr.Locator32 == nil || l.err {
-		return &ParseError{"", "bad L32 Locator", l}
+		return &ParseError{err: "bad L32 Locator", lex: l}
 	}
 	return slurpRemainder(c)
 }
@@ -1670,7 +1695,7 @@ func (rr *LP) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad LP Preference", l}
+		return &ParseError{err: "bad LP Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 
@@ -1679,7 +1704,7 @@ func (rr *LP) parse(c *zlexer, o string) *ParseError {
 	rr.Fqdn = l.token
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{"", "bad LP Fqdn", l}
+		return &ParseError{err: "bad LP Fqdn", lex: l}
 	}
 	rr.Fqdn = name
 	return slurpRemainder(c)
@@ -1689,7 +1714,7 @@ func (rr *L64) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad L64 Preference", l}
+		return &ParseError{err: "bad L64 Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 	c.Next()        // zBlank
@@ -1706,7 +1731,7 @@ func (rr *UID) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 32)
 	if e != nil || l.err {
-		return &ParseError{"", "bad UID Uid", l}
+		return &ParseError{err: "bad UID Uid", lex: l}
 	}
 	rr.Uid = uint32(i)
 	return slurpRemainder(c)
@@ -1716,7 +1741,7 @@ func (rr *GID) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 32)
 	if e != nil || l.err {
-		return &ParseError{"", "bad GID Gid", l}
+		return &ParseError{err: "bad GID Gid", lex: l}
 	}
 	rr.Gid = uint32(i)
 	return slurpRemainder(c)
@@ -1738,7 +1763,7 @@ func (rr *PX) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{"", "bad PX Preference", l}
+		return &ParseError{err: "bad PX Preference", lex: l}
 	}
 	rr.Preference = uint16(i)
 
@@ -1747,7 +1772,7 @@ func (rr *PX) parse(c *zlexer, o string) *ParseError {
 	rr.Map822 = l.token
 	map822, map822Ok := toAbsoluteName(l.token, o)
 	if l.err || !map822Ok {
-		return &ParseError{"", "bad PX Map822", l}
+		return &ParseError{err: "bad PX Map822", lex: l}
 	}
 	rr.Map822 = map822
 
@@ -1756,7 +1781,7 @@ func (rr *PX) parse(c *zlexer, o string) *ParseError {
 	rr.Mapx400 = l.token
 	mapx400, mapx400Ok := toAbsoluteName(l.token, o)
 	if l.err || !mapx400Ok {
-		return &ParseError{"", "bad PX Mapx400", l}
+		return &ParseError{err: "bad PX Mapx400", lex: l}
 	}
 	rr.Mapx400 = mapx400
 	return slurpRemainder(c)
@@ -1766,14 +1791,14 @@ func (rr *CAA) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad CAA Flag", l}
+		return &ParseError{err: "bad CAA Flag", lex: l}
 	}
 	rr.Flag = uint8(i)
 
 	c.Next()        // zBlank
 	l, _ = c.Next() // zString
 	if l.value != zString {
-		return &ParseError{"", "bad CAA Tag", l}
+		return &ParseError{err: "bad CAA Tag", lex: l}
 	}
 	rr.Tag = l.token
 
@@ -1783,7 +1808,7 @@ func (rr *CAA) parse(c *zlexer, o string) *ParseError {
 		return e1
 	}
 	if len(s) != 1 {
-		return &ParseError{"", "bad CAA Value", l}
+		return &ParseError{err: "bad CAA Value", lex: l}
 	}
 	rr.Value = s[0]
 	return nil
@@ -1794,7 +1819,7 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError {
 
 	// Algorithm
 	if l.value != zString {
-		return &ParseError{"", "bad TKEY algorithm", l}
+		return &ParseError{err: "bad TKEY algorithm", lex: l}
 	}
 	rr.Algorithm = l.token
 	c.Next() // zBlank
@@ -1803,13 +1828,13 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 8)
 	if e != nil || l.err {
-		return &ParseError{"", "bad TKEY key length", l}
+		return &ParseError{err: "bad TKEY key length", lex: l}
 	}
 	rr.KeySize = uint16(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if l.value != zString {
-		return &ParseError{"", "bad TKEY key", l}
+		return &ParseError{err: "bad TKEY key", lex: l}
 	}
 	rr.Key = l.token
 	c.Next() // zBlank
@@ -1818,13 +1843,13 @@ func (rr *TKEY) parse(c *zlexer, o string) *ParseError {
 	l, _ = c.Next()
 	i, e1 := strconv.ParseUint(l.token, 10, 8)
 	if e1 != nil || l.err {
-		return &ParseError{"", "bad TKEY otherdata length", l}
+		return &ParseError{err: "bad TKEY otherdata length", lex: l}
 	}
 	rr.OtherLen = uint16(i)
 	c.Next() // zBlank
 	l, _ = c.Next()
 	if l.value != zString {
-		return &ParseError{"", "bad TKEY otherday", l}
+		return &ParseError{err: "bad TKEY otherday", lex: l}
 	}
 	rr.OtherData = l.token
 	return nil
@@ -1842,14 +1867,14 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
 			continue
 		}
 		if l.value != zString {
-			return &ParseError{"", "unexpected APL field", l}
+			return &ParseError{err: "unexpected APL field", lex: l}
 		}
 
 		// Expected format: [!]afi:address/prefix
 
 		colon := strings.IndexByte(l.token, ':')
 		if colon == -1 {
-			return &ParseError{"", "missing colon in APL field", l}
+			return &ParseError{err: "missing colon in APL field", lex: l}
 		}
 
 		family, cidr := l.token[:colon], l.token[colon+1:]
@@ -1862,7 +1887,7 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
 
 		afi, e := strconv.ParseUint(family, 10, 16)
 		if e != nil {
-			return &ParseError{"", "failed to parse APL family: " + e.Error(), l}
+			return &ParseError{wrappedErr: fmt.Errorf("failed to parse APL family: %w", e), lex: l}
 		}
 		var addrLen int
 		switch afi {
@@ -1871,19 +1896,19 @@ func (rr *APL) parse(c *zlexer, o string) *ParseError {
 		case 2:
 			addrLen = net.IPv6len
 		default:
-			return &ParseError{"", "unrecognized APL family", l}
+			return &ParseError{err: "unrecognized APL family", lex: l}
 		}
 
 		ip, subnet, e1 := net.ParseCIDR(cidr)
 		if e1 != nil {
-			return &ParseError{"", "failed to parse APL address: " + e1.Error(), l}
+			return &ParseError{wrappedErr: fmt.Errorf("failed to parse APL address: %w", e1), lex: l}
 		}
 		if !ip.Equal(subnet.IP) {
-			return &ParseError{"", "extra bits in APL address", l}
+			return &ParseError{err: "extra bits in APL address", lex: l}
 		}
 
 		if len(subnet.IP) != addrLen {
-			return &ParseError{"", "address mismatch with the APL family", l}
+			return &ParseError{err: "address mismatch with the APL family", lex: l}
 		}
 
 		prefixes = append(prefixes, APLPrefix{
diff --git a/vendor/github.com/miekg/dns/server.go b/vendor/github.com/miekg/dns/server.go
index 64e388546..0207d6da2 100644
--- a/vendor/github.com/miekg/dns/server.go
+++ b/vendor/github.com/miekg/dns/server.go
@@ -226,6 +226,10 @@ type Server struct {
 	// Whether to set the SO_REUSEPORT socket option, allowing multiple listeners to be bound to a single address.
 	// It is only supported on certain GOOSes and when using ListenAndServe.
 	ReusePort bool
+	// Whether to set the SO_REUSEADDR socket option, allowing multiple listeners to be bound to a single address.
+	// Crucially this allows binding when an existing server is listening on `0.0.0.0` or `::`.
+	// It is only supported on certain GOOSes and when using ListenAndServe.
+	ReuseAddr bool
 	// AcceptMsgFunc will check the incoming message and will reject it early in the process.
 	// By default DefaultMsgAcceptFunc will be used.
 	MsgAcceptFunc MsgAcceptFunc
@@ -304,7 +308,7 @@ func (srv *Server) ListenAndServe() error {
 
 	switch srv.Net {
 	case "tcp", "tcp4", "tcp6":
-		l, err := listenTCP(srv.Net, addr, srv.ReusePort)
+		l, err := listenTCP(srv.Net, addr, srv.ReusePort, srv.ReuseAddr)
 		if err != nil {
 			return err
 		}
@@ -317,7 +321,7 @@ func (srv *Server) ListenAndServe() error {
 			return errors.New("dns: neither Certificates nor GetCertificate set in Config")
 		}
 		network := strings.TrimSuffix(srv.Net, "-tls")
-		l, err := listenTCP(network, addr, srv.ReusePort)
+		l, err := listenTCP(network, addr, srv.ReusePort, srv.ReuseAddr)
 		if err != nil {
 			return err
 		}
@@ -327,7 +331,7 @@ func (srv *Server) ListenAndServe() error {
 		unlock()
 		return srv.serveTCP(l)
 	case "udp", "udp4", "udp6":
-		l, err := listenUDP(srv.Net, addr, srv.ReusePort)
+		l, err := listenUDP(srv.Net, addr, srv.ReusePort, srv.ReuseAddr)
 		if err != nil {
 			return err
 		}
diff --git a/vendor/github.com/miekg/dns/svcb.go b/vendor/github.com/miekg/dns/svcb.go
index 6d496d74d..c1a740b68 100644
--- a/vendor/github.com/miekg/dns/svcb.go
+++ b/vendor/github.com/miekg/dns/svcb.go
@@ -85,7 +85,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
 	l, _ := c.Next()
 	i, e := strconv.ParseUint(l.token, 10, 16)
 	if e != nil || l.err {
-		return &ParseError{l.token, "bad SVCB priority", l}
+		return &ParseError{file: l.token, err: "bad SVCB priority", lex: l}
 	}
 	rr.Priority = uint16(i)
 
@@ -95,7 +95,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
 
 	name, nameOk := toAbsoluteName(l.token, o)
 	if l.err || !nameOk {
-		return &ParseError{l.token, "bad SVCB Target", l}
+		return &ParseError{file: l.token, err: "bad SVCB Target", lex: l}
 	}
 	rr.Target = name
 
@@ -111,7 +111,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
 			if !canHaveNextKey {
 				// The key we can now read was probably meant to be
 				// a part of the last value.
-				return &ParseError{l.token, "bad SVCB value quotation", l}
+				return &ParseError{file: l.token, err: "bad SVCB value quotation", lex: l}
 			}
 
 			// In key=value pairs, value does not have to be quoted unless value
@@ -124,7 +124,7 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
 				// Key with no value and no equality sign
 				key = l.token
 			} else if idx == 0 {
-				return &ParseError{l.token, "bad SVCB key", l}
+				return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
 			} else {
 				key, value = l.token[:idx], l.token[idx+1:]
 
@@ -144,30 +144,30 @@ func (rr *SVCB) parse(c *zlexer, o string) *ParseError {
 							value = l.token
 							l, _ = c.Next()
 							if l.value != zQuote {
-								return &ParseError{l.token, "SVCB unterminated value", l}
+								return &ParseError{file: l.token, err: "SVCB unterminated value", lex: l}
 							}
 						case zQuote:
 							// There's nothing in double quotes.
 						default:
-							return &ParseError{l.token, "bad SVCB value", l}
+							return &ParseError{file: l.token, err: "bad SVCB value", lex: l}
 						}
 					}
 				}
 			}
 			kv := makeSVCBKeyValue(svcbStringToKey(key))
 			if kv == nil {
-				return &ParseError{l.token, "bad SVCB key", l}
+				return &ParseError{file: l.token, err: "bad SVCB key", lex: l}
 			}
 			if err := kv.parse(value); err != nil {
-				return &ParseError{l.token, err.Error(), l}
+				return &ParseError{file: l.token, wrappedErr: err, lex: l}
 			}
 			xs = append(xs, kv)
 		case zQuote:
-			return &ParseError{l.token, "SVCB key can't contain double quotes", l}
+			return &ParseError{file: l.token, err: "SVCB key can't contain double quotes", lex: l}
 		case zBlank:
 			canHaveNextKey = true
 		default:
-			return &ParseError{l.token, "bad SVCB values", l}
+			return &ParseError{file: l.token, err: "bad SVCB values", lex: l}
 		}
 		l, _ = c.Next()
 	}
@@ -314,10 +314,11 @@ func (s *SVCBMandatory) unpack(b []byte) error {
 }
 
 func (s *SVCBMandatory) parse(b string) error {
-	str := strings.Split(b, ",")
-	codes := make([]SVCBKey, 0, len(str))
-	for _, e := range str {
-		codes = append(codes, svcbStringToKey(e))
+	codes := make([]SVCBKey, 0, strings.Count(b, ",")+1)
+	for len(b) > 0 {
+		var key string
+		key, b, _ = strings.Cut(b, ",")
+		codes = append(codes, svcbStringToKey(key))
 	}
 	s.Code = codes
 	return nil
@@ -613,19 +614,24 @@ func (s *SVCBIPv4Hint) String() string {
 }
 
 func (s *SVCBIPv4Hint) parse(b string) error {
+	if b == "" {
+		return errors.New("dns: svcbipv4hint: empty hint")
+	}
 	if strings.Contains(b, ":") {
 		return errors.New("dns: svcbipv4hint: expected ipv4, got ipv6")
 	}
-	str := strings.Split(b, ",")
-	dst := make([]net.IP, len(str))
-	for i, e := range str {
+
+	hint := make([]net.IP, 0, strings.Count(b, ",")+1)
+	for len(b) > 0 {
+		var e string
+		e, b, _ = strings.Cut(b, ",")
 		ip := net.ParseIP(e).To4()
 		if ip == nil {
 			return errors.New("dns: svcbipv4hint: bad ip")
 		}
-		dst[i] = ip
+		hint = append(hint, ip)
 	}
-	s.Hint = dst
+	s.Hint = hint
 	return nil
 }
 
@@ -733,9 +739,14 @@ func (s *SVCBIPv6Hint) String() string {
 }
 
 func (s *SVCBIPv6Hint) parse(b string) error {
-	str := strings.Split(b, ",")
-	dst := make([]net.IP, len(str))
-	for i, e := range str {
+	if b == "" {
+		return errors.New("dns: svcbipv6hint: empty hint")
+	}
+
+	hint := make([]net.IP, 0, strings.Count(b, ",")+1)
+	for len(b) > 0 {
+		var e string
+		e, b, _ = strings.Cut(b, ",")
 		ip := net.ParseIP(e)
 		if ip == nil {
 			return errors.New("dns: svcbipv6hint: bad ip")
@@ -743,9 +754,9 @@ func (s *SVCBIPv6Hint) parse(b string) error {
 		if ip.To4() != nil {
 			return errors.New("dns: svcbipv6hint: expected ipv6, got ipv4-mapped-ipv6")
 		}
-		dst[i] = ip
+		hint = append(hint, ip)
 	}
-	s.Hint = dst
+	s.Hint = hint
 	return nil
 }
 
diff --git a/vendor/github.com/miekg/dns/types.go b/vendor/github.com/miekg/dns/types.go
index 03afeccda..8e3129cbd 100644
--- a/vendor/github.com/miekg/dns/types.go
+++ b/vendor/github.com/miekg/dns/types.go
@@ -135,8 +135,8 @@ const (
 	RcodeNXRrset        = 8  // NXRRSet   - RR Set that should exist does not [DNS Update]
 	RcodeNotAuth        = 9  // NotAuth   - Server Not Authoritative for zone [DNS Update]
 	RcodeNotZone        = 10 // NotZone   - Name not contained in zone        [DNS Update/TSIG]
-	RcodeBadSig         = 16 // BADSIG    - TSIG Signature Failure            [TSIG]
-	RcodeBadVers        = 16 // BADVERS   - Bad OPT Version                   [EDNS0]
+	RcodeBadSig         = 16 // BADSIG    - TSIG Signature Failure            [TSIG]  https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3
+	RcodeBadVers        = 16 // BADVERS   - Bad OPT Version                   [EDNS0] https://www.rfc-editor.org/rfc/rfc6895.html#section-2.3
 	RcodeBadKey         = 17 // BADKEY    - Key not recognized                [TSIG]
 	RcodeBadTime        = 18 // BADTIME   - Signature out of time window      [TSIG]
 	RcodeBadMode        = 19 // BADMODE   - Bad TKEY Mode                     [TKEY]
@@ -236,6 +236,9 @@ var CertTypeToString = map[uint16]string{
 	CertOID:     "OID",
 }
 
+// Prefix for IPv4 encoded as IPv6 address
+const ipv4InIPv6Prefix = "::ffff:"
+
 //go:generate go run types_generate.go
 
 // Question holds a DNS question. Usually there is just one. While the
@@ -399,6 +402,17 @@ func (rr *X25) String() string {
 	return rr.Hdr.String() + rr.PSDNAddress
 }
 
+// ISDN RR. See RFC 1183, Section 3.2.
+type ISDN struct {
+	Hdr        RR_Header
+	Address    string
+	SubAddress string
+}
+
+func (rr *ISDN) String() string {
+	return rr.Hdr.String() + sprintTxt([]string{rr.Address, rr.SubAddress})
+}
+
 // RT RR. See RFC 1183, Section 3.3.
 type RT struct {
 	Hdr        RR_Header
@@ -751,6 +765,11 @@ func (rr *AAAA) String() string {
 	if rr.AAAA == nil {
 		return rr.Hdr.String()
 	}
+
+	if rr.AAAA.To4() != nil {
+		return rr.Hdr.String() + ipv4InIPv6Prefix + rr.AAAA.String()
+	}
+
 	return rr.Hdr.String() + rr.AAAA.String()
 }
 
@@ -778,7 +797,7 @@ func (rr *GPOS) String() string {
 	return rr.Hdr.String() + rr.Longitude + " " + rr.Latitude + " " + rr.Altitude
 }
 
-// LOC RR. See RFC RFC 1876.
+// LOC RR. See RFC 1876.
 type LOC struct {
 	Hdr       RR_Header
 	Version   uint8
@@ -890,6 +909,11 @@ func (rr *RRSIG) String() string {
 	return s
 }
 
+// NXT RR. See RFC 2535.
+type NXT struct {
+	NSEC
+}
+
 // NSEC RR. See RFC 4034 and RFC 3755.
 type NSEC struct {
 	Hdr        RR_Header
@@ -974,7 +998,7 @@ func (rr *TALINK) String() string {
 		sprintName(rr.PreviousName) + " " + sprintName(rr.NextName)
 }
 
-// SSHFP RR. See RFC RFC 4255.
+// SSHFP RR. See RFC 4255.
 type SSHFP struct {
 	Hdr         RR_Header
 	Algorithm   uint8
@@ -988,7 +1012,7 @@ func (rr *SSHFP) String() string {
 		" " + strings.ToUpper(rr.FingerPrint)
 }
 
-// KEY RR. See RFC RFC 2535.
+// KEY RR. See RFC 2535.
 type KEY struct {
 	DNSKEY
 }
@@ -1298,7 +1322,7 @@ type NINFO struct {
 
 func (rr *NINFO) String() string { return rr.Hdr.String() + sprintTxt(rr.ZSData) }
 
-// NID RR. See RFC RFC 6742.
+// NID RR. See RFC 6742.
 type NID struct {
 	Hdr        RR_Header
 	Preference uint16
@@ -1517,7 +1541,7 @@ func (a *APLPrefix) str() string {
 	case net.IPv6len:
 		// add prefix for IPv4-mapped IPv6
 		if v4 := a.Network.IP.To4(); v4 != nil {
-			sb.WriteString("::ffff:")
+			sb.WriteString(ipv4InIPv6Prefix)
 		}
 		sb.WriteString(a.Network.IP.String())
 	}
diff --git a/vendor/github.com/miekg/dns/version.go b/vendor/github.com/miekg/dns/version.go
index 5891044a3..dc34e5902 100644
--- a/vendor/github.com/miekg/dns/version.go
+++ b/vendor/github.com/miekg/dns/version.go
@@ -3,7 +3,7 @@ package dns
 import "fmt"
 
 // Version is current version of this library.
-var Version = v{1, 1, 55}
+var Version = v{1, 1, 58}
 
 // v holds the version of this library.
 type v struct {
diff --git a/vendor/github.com/miekg/dns/xfr.go b/vendor/github.com/miekg/dns/xfr.go
index 0a831c880..05b3c5add 100644
--- a/vendor/github.com/miekg/dns/xfr.go
+++ b/vendor/github.com/miekg/dns/xfr.go
@@ -80,8 +80,13 @@ func (t *Transfer) In(q *Msg, a string) (env chan *Envelope, err error) {
 
 func (t *Transfer) inAxfr(q *Msg, c chan *Envelope) {
 	first := true
-	defer t.Close()
-	defer close(c)
+	defer func() {
+		// First close the connection, then the channel. This allows functions blocked on
+		// the channel to assume that the connection is closed and no further operations are
+		// pending when they resume.
+		t.Close()
+		close(c)
+	}()
 	timeout := dnsTimeout
 	if t.ReadTimeout != 0 {
 		timeout = t.ReadTimeout
@@ -131,8 +136,13 @@ func (t *Transfer) inIxfr(q *Msg, c chan *Envelope) {
 	axfr := true
 	n := 0
 	qser := q.Ns[0].(*SOA).Serial
-	defer t.Close()
-	defer close(c)
+	defer func() {
+		// First close the connection, then the channel. This allows functions blocked on
+		// the channel to assume that the connection is closed and no further operations are
+		// pending when they resume.
+		t.Close()
+		close(c)
+	}()
 	timeout := dnsTimeout
 	if t.ReadTimeout != 0 {
 		timeout = t.ReadTimeout
diff --git a/vendor/github.com/miekg/dns/zduplicate.go b/vendor/github.com/miekg/dns/zduplicate.go
index 450bbbc29..03029fb3e 100644
--- a/vendor/github.com/miekg/dns/zduplicate.go
+++ b/vendor/github.com/miekg/dns/zduplicate.go
@@ -481,6 +481,21 @@ func (r1 *IPSECKEY) isDuplicate(_r2 RR) bool {
 	return true
 }
 
+func (r1 *ISDN) isDuplicate(_r2 RR) bool {
+	r2, ok := _r2.(*ISDN)
+	if !ok {
+		return false
+	}
+	_ = r2
+	if r1.Address != r2.Address {
+		return false
+	}
+	if r1.SubAddress != r2.SubAddress {
+		return false
+	}
+	return true
+}
+
 func (r1 *KEY) isDuplicate(_r2 RR) bool {
 	r2, ok := _r2.(*KEY)
 	if !ok {
@@ -871,6 +886,26 @@ func (r1 *NULL) isDuplicate(_r2 RR) bool {
 	return true
 }
 
+func (r1 *NXT) isDuplicate(_r2 RR) bool {
+	r2, ok := _r2.(*NXT)
+	if !ok {
+		return false
+	}
+	_ = r2
+	if !isDuplicateName(r1.NextDomain, r2.NextDomain) {
+		return false
+	}
+	if len(r1.TypeBitMap) != len(r2.TypeBitMap) {
+		return false
+	}
+	for i := 0; i < len(r1.TypeBitMap); i++ {
+		if r1.TypeBitMap[i] != r2.TypeBitMap[i] {
+			return false
+		}
+	}
+	return true
+}
+
 func (r1 *OPENPGPKEY) isDuplicate(_r2 RR) bool {
 	r2, ok := _r2.(*OPENPGPKEY)
 	if !ok {
diff --git a/vendor/github.com/miekg/dns/zmsg.go b/vendor/github.com/miekg/dns/zmsg.go
index 3ea0eb423..39b3bc810 100644
--- a/vendor/github.com/miekg/dns/zmsg.go
+++ b/vendor/github.com/miekg/dns/zmsg.go
@@ -372,6 +372,18 @@ func (rr *IPSECKEY) pack(msg []byte, off int, compression compressionMap, compre
 	return off, nil
 }
 
+func (rr *ISDN) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
+	off, err = packString(rr.Address, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packString(rr.SubAddress, msg, off)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
+
 func (rr *KEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
 	off, err = packUint16(rr.Flags, msg, off)
 	if err != nil {
@@ -694,6 +706,18 @@ func (rr *NULL) pack(msg []byte, off int, compression compressionMap, compress b
 	return off, nil
 }
 
+func (rr *NXT) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
+	off, err = packDomainName(rr.NextDomain, msg, off, compression, false)
+	if err != nil {
+		return off, err
+	}
+	off, err = packDataNsec(rr.TypeBitMap, msg, off)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
+
 func (rr *OPENPGPKEY) pack(msg []byte, off int, compression compressionMap, compress bool) (off1 int, err error) {
 	off, err = packStringBase64(rr.PublicKey, msg, off)
 	if err != nil {
@@ -1746,6 +1770,24 @@ func (rr *IPSECKEY) unpack(msg []byte, off int) (off1 int, err error) {
 	return off, nil
 }
 
+func (rr *ISDN) unpack(msg []byte, off int) (off1 int, err error) {
+	rdStart := off
+	_ = rdStart
+
+	rr.Address, off, err = unpackString(msg, off)
+	if err != nil {
+		return off, err
+	}
+	if off == len(msg) {
+		return off, nil
+	}
+	rr.SubAddress, off, err = unpackString(msg, off)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
+
 func (rr *KEY) unpack(msg []byte, off int) (off1 int, err error) {
 	rdStart := off
 	_ = rdStart
@@ -2224,6 +2266,24 @@ func (rr *NULL) unpack(msg []byte, off int) (off1 int, err error) {
 	return off, nil
 }
 
+func (rr *NXT) unpack(msg []byte, off int) (off1 int, err error) {
+	rdStart := off
+	_ = rdStart
+
+	rr.NextDomain, off, err = UnpackDomainName(msg, off)
+	if err != nil {
+		return off, err
+	}
+	if off == len(msg) {
+		return off, nil
+	}
+	rr.TypeBitMap, off, err = unpackDataNsec(msg, off)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
+
 func (rr *OPENPGPKEY) unpack(msg []byte, off int) (off1 int, err error) {
 	rdStart := off
 	_ = rdStart
diff --git a/vendor/github.com/miekg/dns/ztypes.go b/vendor/github.com/miekg/dns/ztypes.go
index 1b6f43200..2c70fc44d 100644
--- a/vendor/github.com/miekg/dns/ztypes.go
+++ b/vendor/github.com/miekg/dns/ztypes.go
@@ -36,6 +36,7 @@ var TypeToRR = map[uint16]func() RR{
 	TypeHIP:        func() RR { return new(HIP) },
 	TypeHTTPS:      func() RR { return new(HTTPS) },
 	TypeIPSECKEY:   func() RR { return new(IPSECKEY) },
+	TypeISDN:       func() RR { return new(ISDN) },
 	TypeKEY:        func() RR { return new(KEY) },
 	TypeKX:         func() RR { return new(KX) },
 	TypeL32:        func() RR { return new(L32) },
@@ -59,6 +60,7 @@ var TypeToRR = map[uint16]func() RR{
 	TypeNSEC3:      func() RR { return new(NSEC3) },
 	TypeNSEC3PARAM: func() RR { return new(NSEC3PARAM) },
 	TypeNULL:       func() RR { return new(NULL) },
+	TypeNXT:        func() RR { return new(NXT) },
 	TypeOPENPGPKEY: func() RR { return new(OPENPGPKEY) },
 	TypeOPT:        func() RR { return new(OPT) },
 	TypePTR:        func() RR { return new(PTR) },
@@ -204,6 +206,7 @@ func (rr *HINFO) Header() *RR_Header      { return &rr.Hdr }
 func (rr *HIP) Header() *RR_Header        { return &rr.Hdr }
 func (rr *HTTPS) Header() *RR_Header      { return &rr.Hdr }
 func (rr *IPSECKEY) Header() *RR_Header   { return &rr.Hdr }
+func (rr *ISDN) Header() *RR_Header       { return &rr.Hdr }
 func (rr *KEY) Header() *RR_Header        { return &rr.Hdr }
 func (rr *KX) Header() *RR_Header         { return &rr.Hdr }
 func (rr *L32) Header() *RR_Header        { return &rr.Hdr }
@@ -227,6 +230,7 @@ func (rr *NSEC) Header() *RR_Header       { return &rr.Hdr }
 func (rr *NSEC3) Header() *RR_Header      { return &rr.Hdr }
 func (rr *NSEC3PARAM) Header() *RR_Header { return &rr.Hdr }
 func (rr *NULL) Header() *RR_Header       { return &rr.Hdr }
+func (rr *NXT) Header() *RR_Header        { return &rr.Hdr }
 func (rr *OPENPGPKEY) Header() *RR_Header { return &rr.Hdr }
 func (rr *OPT) Header() *RR_Header        { return &rr.Hdr }
 func (rr *PTR) Header() *RR_Header        { return &rr.Hdr }
@@ -437,6 +441,13 @@ func (rr *IPSECKEY) len(off int, compression map[string]struct{}) int {
 	return l
 }
 
+func (rr *ISDN) len(off int, compression map[string]struct{}) int {
+	l := rr.Hdr.len(off, compression)
+	l += len(rr.Address) + 1
+	l += len(rr.SubAddress) + 1
+	return l
+}
+
 func (rr *KX) len(off int, compression map[string]struct{}) int {
 	l := rr.Hdr.len(off, compression)
 	l += 2 // Preference
@@ -966,6 +977,10 @@ func (rr *IPSECKEY) copy() RR {
 	}
 }
 
+func (rr *ISDN) copy() RR {
+	return &ISDN{rr.Hdr, rr.Address, rr.SubAddress}
+}
+
 func (rr *KEY) copy() RR {
 	return &KEY{*rr.DNSKEY.copy().(*DNSKEY)}
 }
@@ -1092,6 +1107,10 @@ func (rr *NULL) copy() RR {
 	return &NULL{rr.Hdr, rr.Data}
 }
 
+func (rr *NXT) copy() RR {
+	return &NXT{*rr.NSEC.copy().(*NSEC)}
+}
+
 func (rr *OPENPGPKEY) copy() RR {
 	return &OPENPGPKEY{rr.Hdr, rr.PublicKey}
 }
diff --git a/vendor/golang.org/x/mod/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go
index a30a22bf2..9a2dfd33a 100644
--- a/vendor/golang.org/x/mod/semver/semver.go
+++ b/vendor/golang.org/x/mod/semver/semver.go
@@ -140,7 +140,7 @@ func Compare(v, w string) int {
 // Max canonicalizes its arguments and then returns the version string
 // that compares greater.
 //
-// Deprecated: use Compare instead. In most cases, returning a canonicalized
+// Deprecated: use [Compare] instead. In most cases, returning a canonicalized
 // version is not expected or desired.
 func Max(v, w string) string {
 	v = Canonical(v)
@@ -151,7 +151,7 @@ func Max(v, w string) string {
 	return w
 }
 
-// ByVersion implements sort.Interface for sorting semantic version strings.
+// ByVersion implements [sort.Interface] for sorting semantic version strings.
 type ByVersion []string
 
 func (vs ByVersion) Len() int      { return len(vs) }
@@ -164,7 +164,7 @@ func (vs ByVersion) Less(i, j int) bool {
 	return vs[i] < vs[j]
 }
 
-// Sort sorts a list of semantic version strings using ByVersion.
+// Sort sorts a list of semantic version strings using [ByVersion].
 func Sort(list []string) {
 	sort.Sort(ByVersion(list))
 }
diff --git a/vendor/golang.org/x/sync/errgroup/errgroup.go b/vendor/golang.org/x/sync/errgroup/errgroup.go
index b18efb743..948a3ee63 100644
--- a/vendor/golang.org/x/sync/errgroup/errgroup.go
+++ b/vendor/golang.org/x/sync/errgroup/errgroup.go
@@ -4,6 +4,9 @@
 
 // Package errgroup provides synchronization, error propagation, and Context
 // cancelation for groups of goroutines working on subtasks of a common task.
+//
+// [errgroup.Group] is related to [sync.WaitGroup] but adds handling of tasks
+// returning errors.
 package errgroup
 
 import (
diff --git a/vendor/golang.org/x/sync/errgroup/go120.go b/vendor/golang.org/x/sync/errgroup/go120.go
index 7d419d376..f93c740b6 100644
--- a/vendor/golang.org/x/sync/errgroup/go120.go
+++ b/vendor/golang.org/x/sync/errgroup/go120.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build go1.20
-// +build go1.20
 
 package errgroup
 
diff --git a/vendor/golang.org/x/sync/errgroup/pre_go120.go b/vendor/golang.org/x/sync/errgroup/pre_go120.go
index 1795c18ac..88ce33434 100644
--- a/vendor/golang.org/x/sync/errgroup/pre_go120.go
+++ b/vendor/golang.org/x/sync/errgroup/pre_go120.go
@@ -3,7 +3,6 @@
 // license that can be found in the LICENSE file.
 
 //go:build !go1.20
-// +build !go1.20
 
 package errgroup
 
diff --git a/vendor/golang.org/x/sys/execabs/execabs.go b/vendor/golang.org/x/sys/execabs/execabs.go
deleted file mode 100644
index 3bf40fdfe..000000000
--- a/vendor/golang.org/x/sys/execabs/execabs.go
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright 2020 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package execabs is a drop-in replacement for os/exec
-// that requires PATH lookups to find absolute paths.
-// That is, execabs.Command("cmd") runs the same PATH lookup
-// as exec.Command("cmd"), but if the result is a path
-// which is relative, the Run and Start methods will report
-// an error instead of running the executable.
-//
-// See https://blog.golang.org/path-security for more information
-// about when it may be necessary or appropriate to use this package.
-package execabs
-
-import (
-	"context"
-	"fmt"
-	"os/exec"
-	"path/filepath"
-	"reflect"
-	"unsafe"
-)
-
-// ErrNotFound is the error resulting if a path search failed to find an executable file.
-// It is an alias for exec.ErrNotFound.
-var ErrNotFound = exec.ErrNotFound
-
-// Cmd represents an external command being prepared or run.
-// It is an alias for exec.Cmd.
-type Cmd = exec.Cmd
-
-// Error is returned by LookPath when it fails to classify a file as an executable.
-// It is an alias for exec.Error.
-type Error = exec.Error
-
-// An ExitError reports an unsuccessful exit by a command.
-// It is an alias for exec.ExitError.
-type ExitError = exec.ExitError
-
-func relError(file, path string) error {
-	return fmt.Errorf("%s resolves to executable in current directory (.%c%s)", file, filepath.Separator, path)
-}
-
-// LookPath searches for an executable named file in the directories
-// named by the PATH environment variable. If file contains a slash,
-// it is tried directly and the PATH is not consulted. The result will be
-// an absolute path.
-//
-// LookPath differs from exec.LookPath in its handling of PATH lookups,
-// which are used for file names without slashes. If exec.LookPath's
-// PATH lookup would have returned an executable from the current directory,
-// LookPath instead returns an error.
-func LookPath(file string) (string, error) {
-	path, err := exec.LookPath(file)
-	if err != nil && !isGo119ErrDot(err) {
-		return "", err
-	}
-	if filepath.Base(file) == file && !filepath.IsAbs(path) {
-		return "", relError(file, path)
-	}
-	return path, nil
-}
-
-func fixCmd(name string, cmd *exec.Cmd) {
-	if filepath.Base(name) == name && !filepath.IsAbs(cmd.Path) && !isGo119ErrFieldSet(cmd) {
-		// exec.Command was called with a bare binary name and
-		// exec.LookPath returned a path which is not absolute.
-		// Set cmd.lookPathErr and clear cmd.Path so that it
-		// cannot be run.
-		lookPathErr := (*error)(unsafe.Pointer(reflect.ValueOf(cmd).Elem().FieldByName("lookPathErr").Addr().Pointer()))
-		if *lookPathErr == nil {
-			*lookPathErr = relError(name, cmd.Path)
-		}
-		cmd.Path = ""
-	}
-}
-
-// CommandContext is like Command but includes a context.
-//
-// The provided context is used to kill the process (by calling os.Process.Kill)
-// if the context becomes done before the command completes on its own.
-func CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd {
-	cmd := exec.CommandContext(ctx, name, arg...)
-	fixCmd(name, cmd)
-	return cmd
-
-}
-
-// Command returns the Cmd struct to execute the named program with the given arguments.
-// See exec.Command for most details.
-//
-// Command differs from exec.Command in its handling of PATH lookups,
-// which are used when the program name contains no slashes.
-// If exec.Command would have returned an exec.Cmd configured to run an
-// executable from the current directory, Command instead
-// returns an exec.Cmd that will return an error from Start or Run.
-func Command(name string, arg ...string) *exec.Cmd {
-	cmd := exec.Command(name, arg...)
-	fixCmd(name, cmd)
-	return cmd
-}
diff --git a/vendor/golang.org/x/sys/execabs/execabs_go118.go b/vendor/golang.org/x/sys/execabs/execabs_go118.go
deleted file mode 100644
index 5627d70e3..000000000
--- a/vendor/golang.org/x/sys/execabs/execabs_go118.go
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.19
-
-package execabs
-
-import "os/exec"
-
-func isGo119ErrDot(err error) bool {
-	return false
-}
-
-func isGo119ErrFieldSet(cmd *exec.Cmd) bool {
-	return false
-}
diff --git a/vendor/golang.org/x/sys/execabs/execabs_go119.go b/vendor/golang.org/x/sys/execabs/execabs_go119.go
deleted file mode 100644
index d60ab1b41..000000000
--- a/vendor/golang.org/x/sys/execabs/execabs_go119.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2022 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.19
-
-package execabs
-
-import (
-	"errors"
-	"os/exec"
-)
-
-func isGo119ErrDot(err error) bool {
-	return errors.Is(err, exec.ErrDot)
-}
-
-func isGo119ErrFieldSet(cmd *exec.Cmd) bool {
-	return cmd.Err != nil
-}
diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
index 165ede0f8..03543bd4b 100644
--- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
+++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go
@@ -128,15 +128,14 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package,
 	// (from "version"). Select appropriate importer.
 	if len(data) > 0 {
 		switch data[0] {
-		case 'i':
-			_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
-			return pkg, err
+		case 'v', 'c', 'd': // binary, till go1.10
+			return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
 
-		case 'v', 'c', 'd':
-			_, pkg, err := gcimporter.BImportData(fset, imports, data, path)
+		case 'i': // indexed, till go1.19
+			_, pkg, err := gcimporter.IImportData(fset, imports, data[1:], path)
 			return pkg, err
 
-		case 'u':
+		case 'u': // unified, from go1.20
 			_, pkg, err := gcimporter.UImportData(fset, imports, data[1:], path)
 			return pkg, err
 
diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go
index 18a002f82..333676b7c 100644
--- a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go
+++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go
@@ -8,42 +8,46 @@ package packagesdriver
 import (
 	"context"
 	"fmt"
-	"go/types"
 	"strings"
 
 	"golang.org/x/tools/internal/gocommand"
 )
 
-var debug = false
-
-func GetSizesGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (types.Sizes, error) {
+func GetSizesForArgsGolist(ctx context.Context, inv gocommand.Invocation, gocmdRunner *gocommand.Runner) (string, string, error) {
 	inv.Verb = "list"
 	inv.Args = []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}
 	stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv)
 	var goarch, compiler string
 	if rawErr != nil {
-		if rawErrMsg := rawErr.Error(); strings.Contains(rawErrMsg, "cannot find main module") || strings.Contains(rawErrMsg, "go.mod file not found") {
-			// User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc.
+		rawErrMsg := rawErr.Error()
+		if strings.Contains(rawErrMsg, "cannot find main module") ||
+			strings.Contains(rawErrMsg, "go.mod file not found") {
+			// User's running outside of a module.
+			// All bets are off. Get GOARCH and guess compiler is gc.
 			// TODO(matloob): Is this a problem in practice?
 			inv.Verb = "env"
 			inv.Args = []string{"GOARCH"}
 			envout, enverr := gocmdRunner.Run(ctx, inv)
 			if enverr != nil {
-				return nil, enverr
+				return "", "", enverr
 			}
 			goarch = strings.TrimSpace(envout.String())
 			compiler = "gc"
+		} else if friendlyErr != nil {
+			return "", "", friendlyErr
 		} else {
-			return nil, friendlyErr
+			// This should be unreachable, but be defensive
+			// in case RunRaw's error results are inconsistent.
+			return "", "", rawErr
 		}
 	} else {
 		fields := strings.Fields(stdout.String())
 		if len(fields) < 2 {
-			return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>",
+			return "", "", fmt.Errorf("could not parse GOARCH and Go compiler in format \"<GOARCH> <compiler>\":\nstdout: <<%s>>\nstderr: <<%s>>",
 				stdout.String(), stderr.String())
 		}
 		goarch = fields[0]
 		compiler = fields[1]
 	}
-	return types.SizesFor(compiler, goarch), nil
+	return compiler, goarch, nil
 }
diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go
index da4ab89fe..b2a0b7c6a 100644
--- a/vendor/golang.org/x/tools/go/packages/doc.go
+++ b/vendor/golang.org/x/tools/go/packages/doc.go
@@ -5,12 +5,32 @@
 /*
 Package packages loads Go packages for inspection and analysis.
 
-The Load function takes as input a list of patterns and return a list of Package
-structs describing individual packages matched by those patterns.
-The LoadMode controls the amount of detail in the loaded packages.
-
-Load passes most patterns directly to the underlying build tool,
-but all patterns with the prefix "query=", where query is a
+The [Load] function takes as input a list of patterns and returns a
+list of [Package] values describing individual packages matched by those
+patterns.
+A [Config] specifies configuration options, the most important of which is
+the [LoadMode], which controls the amount of detail in the loaded packages.
+
+Load passes most patterns directly to the underlying build tool.
+The default build tool is the go command.
+Its supported patterns are described at
+https://pkg.go.dev/cmd/go#hdr-Package_lists_and_patterns.
+
+Load may be used in Go projects that use alternative build systems, by
+installing an appropriate "driver" program for the build system and
+specifying its location in the GOPACKAGESDRIVER environment variable.
+For example,
+https://github.com/bazelbuild/rules_go/wiki/Editor-and-tool-integration
+explains how to use the driver for Bazel.
+The driver program is responsible for interpreting patterns in its
+preferred notation and reporting information about the packages that
+they identify.
+(See driverRequest and driverResponse types for the JSON
+schema used by the protocol.
+Though the protocol is supported, these types are currently unexported;
+see #64608 for a proposal to publish them.)
+
+Regardless of driver, all patterns with the prefix "query=", where query is a
 non-empty string of letters from [a-z], are reserved and may be
 interpreted as query operators.
 
@@ -35,7 +55,7 @@ The Package struct provides basic information about the package, including
   - Imports, a map from source import strings to the Packages they name;
   - Types, the type information for the package's exported symbols;
   - Syntax, the parsed syntax trees for the package's source code; and
-  - TypeInfo, the result of a complete type-check of the package syntax trees.
+  - TypesInfo, the result of a complete type-check of the package syntax trees.
 
 (See the documentation for type Package for the complete list of fields
 and more detailed descriptions.)
@@ -64,7 +84,7 @@ reported about the loaded packages. See the documentation for type LoadMode
 for details.
 
 Most tools should pass their command-line arguments (after any flags)
-uninterpreted to the loader, so that the loader can interpret them
+uninterpreted to [Load], so that it can interpret them
 according to the conventions of the underlying build system.
 See the Example function for typical usage.
 */
diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go
index 7242a0a7d..7db1d1293 100644
--- a/vendor/golang.org/x/tools/go/packages/external.go
+++ b/vendor/golang.org/x/tools/go/packages/external.go
@@ -12,8 +12,8 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
-	exec "golang.org/x/sys/execabs"
 	"os"
+	"os/exec"
 	"strings"
 )
 
diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go
index 6bb7168d2..cd375fbc3 100644
--- a/vendor/golang.org/x/tools/go/packages/golist.go
+++ b/vendor/golang.org/x/tools/go/packages/golist.go
@@ -9,10 +9,9 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"go/types"
-	"io/ioutil"
 	"log"
 	"os"
+	"os/exec"
 	"path"
 	"path/filepath"
 	"reflect"
@@ -22,7 +21,6 @@ import (
 	"sync"
 	"unicode"
 
-	exec "golang.org/x/sys/execabs"
 	"golang.org/x/tools/go/internal/packagesdriver"
 	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/packagesinternal"
@@ -153,10 +151,10 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
 	if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 {
 		sizeswg.Add(1)
 		go func() {
-			var sizes types.Sizes
-			sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner)
-			// types.SizesFor always returns nil or a *types.StdSizes.
-			response.dr.Sizes, _ = sizes.(*types.StdSizes)
+			compiler, arch, err := packagesdriver.GetSizesForArgsGolist(ctx, state.cfgInvocation(), cfg.gocmdRunner)
+			sizeserr = err
+			response.dr.Compiler = compiler
+			response.dr.Arch = arch
 			sizeswg.Done()
 		}()
 	}
@@ -210,62 +208,6 @@ extractQueries:
 		}
 	}
 
-	// Only use go/packages' overlay processing if we're using a Go version
-	// below 1.16. Otherwise, go list handles it.
-	if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 {
-		modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
-		if err != nil {
-			return nil, err
-		}
-
-		var containsCandidates []string
-		if len(containFiles) > 0 {
-			containsCandidates = append(containsCandidates, modifiedPkgs...)
-			containsCandidates = append(containsCandidates, needPkgs...)
-		}
-		if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
-			return nil, err
-		}
-		// Check candidate packages for containFiles.
-		if len(containFiles) > 0 {
-			for _, id := range containsCandidates {
-				pkg, ok := response.seenPackages[id]
-				if !ok {
-					response.addPackage(&Package{
-						ID: id,
-						Errors: []Error{{
-							Kind: ListError,
-							Msg:  fmt.Sprintf("package %s expected but not seen", id),
-						}},
-					})
-					continue
-				}
-				for _, f := range containFiles {
-					for _, g := range pkg.GoFiles {
-						if sameFile(f, g) {
-							response.addRoot(id)
-						}
-					}
-				}
-			}
-		}
-		// Add root for any package that matches a pattern. This applies only to
-		// packages that are modified by overlays, since they are not added as
-		// roots automatically.
-		for _, pattern := range restPatterns {
-			match := matchPattern(pattern)
-			for _, pkgID := range modifiedPkgs {
-				pkg, ok := response.seenPackages[pkgID]
-				if !ok {
-					continue
-				}
-				if match(pkg.PkgPath) {
-					response.addRoot(pkg.ID)
-				}
-			}
-		}
-	}
-
 	sizeswg.Wait()
 	if sizeserr != nil {
 		return nil, sizeserr
@@ -273,24 +215,6 @@ extractQueries:
 	return response.dr, nil
 }
 
-func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error {
-	if len(pkgs) == 0 {
-		return nil
-	}
-	dr, err := state.createDriverResponse(pkgs...)
-	if err != nil {
-		return err
-	}
-	for _, pkg := range dr.Packages {
-		response.addPackage(pkg)
-	}
-	_, needPkgs, err := state.processGolistOverlay(response)
-	if err != nil {
-		return err
-	}
-	return state.addNeededOverlayPackages(response, needPkgs)
-}
-
 func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error {
 	for _, query := range queries {
 		// TODO(matloob): Do only one query per directory.
@@ -625,7 +549,12 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse
 		}
 
 		if pkg.PkgPath == "unsafe" {
-			pkg.GoFiles = nil // ignore fake unsafe.go file
+			pkg.CompiledGoFiles = nil // ignore fake unsafe.go file (#59929)
+		} else if len(pkg.CompiledGoFiles) == 0 {
+			// Work around for pre-go.1.11 versions of go list.
+			// TODO(matloob): they should be handled by the fallback.
+			// Can we delete this?
+			pkg.CompiledGoFiles = pkg.GoFiles
 		}
 
 		// Assume go list emits only absolute paths for Dir.
@@ -663,16 +592,12 @@ func (state *golistState) createDriverResponse(words ...string) (*driverResponse
 			response.Roots = append(response.Roots, pkg.ID)
 		}
 
-		// Work around for pre-go.1.11 versions of go list.
-		// TODO(matloob): they should be handled by the fallback.
-		// Can we delete this?
-		if len(pkg.CompiledGoFiles) == 0 {
-			pkg.CompiledGoFiles = pkg.GoFiles
-		}
-
 		// Temporary work-around for golang/go#39986. Parse filenames out of
 		// error messages. This happens if there are unrecoverable syntax
 		// errors in the source, so we can't match on a specific error message.
+		//
+		// TODO(rfindley): remove this heuristic, in favor of considering
+		// InvalidGoFiles from the list driver.
 		if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) {
 			addFilenameFromPos := func(pos string) bool {
 				split := strings.Split(pos, ":")
@@ -891,6 +816,15 @@ func golistargs(cfg *Config, words []string, goVersion int) []string {
 		// probably because you'd just get the TestMain.
 		fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0 && !usesExportData(cfg)),
 	}
+
+	// golang/go#60456: with go1.21 and later, go list serves pgo variants, which
+	// can be costly to compute and may result in redundant processing for the
+	// caller. Disable these variants. If someone wants to add e.g. a NeedPGO
+	// mode flag, that should be a separate proposal.
+	if goVersion >= 21 {
+		fullargs = append(fullargs, "-pgo=off")
+	}
+
 	fullargs = append(fullargs, cfg.BuildFlags...)
 	fullargs = append(fullargs, "--")
 	fullargs = append(fullargs, words...)
@@ -1100,7 +1034,7 @@ func (state *golistState) writeOverlays() (filename string, cleanup func(), err
 	if len(state.cfg.Overlay) == 0 {
 		return "", func() {}, nil
 	}
-	dir, err := ioutil.TempDir("", "gopackages-*")
+	dir, err := os.MkdirTemp("", "gopackages-*")
 	if err != nil {
 		return "", nil, err
 	}
@@ -1119,7 +1053,7 @@ func (state *golistState) writeOverlays() (filename string, cleanup func(), err
 		// Create a unique filename for the overlaid files, to avoid
 		// creating nested directories.
 		noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "")
-		f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator))
+		f, err := os.CreateTemp(dir, fmt.Sprintf("*-%s", noSeparator))
 		if err != nil {
 			return "", func() {}, err
 		}
@@ -1137,7 +1071,7 @@ func (state *golistState) writeOverlays() (filename string, cleanup func(), err
 	}
 	// Write out the overlay file that contains the filepath mappings.
 	filename = filepath.Join(dir, "overlay.json")
-	if err := ioutil.WriteFile(filename, b, 0665); err != nil {
+	if err := os.WriteFile(filename, b, 0665); err != nil {
 		return "", func() {}, err
 	}
 	return filename, cleanup, nil
diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go
index 9576b472f..d823c474a 100644
--- a/vendor/golang.org/x/tools/go/packages/golist_overlay.go
+++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go
@@ -6,314 +6,11 @@ package packages
 
 import (
 	"encoding/json"
-	"fmt"
-	"go/parser"
-	"go/token"
-	"os"
 	"path/filepath"
-	"regexp"
-	"sort"
-	"strconv"
-	"strings"
 
 	"golang.org/x/tools/internal/gocommand"
 )
 
-// processGolistOverlay provides rudimentary support for adding
-// files that don't exist on disk to an overlay. The results can be
-// sometimes incorrect.
-// TODO(matloob): Handle unsupported cases, including the following:
-// - determining the correct package to add given a new import path
-func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) {
-	havePkgs := make(map[string]string) // importPath -> non-test package ID
-	needPkgsSet := make(map[string]bool)
-	modifiedPkgsSet := make(map[string]bool)
-
-	pkgOfDir := make(map[string][]*Package)
-	for _, pkg := range response.dr.Packages {
-		// This is an approximation of import path to id. This can be
-		// wrong for tests, vendored packages, and a number of other cases.
-		havePkgs[pkg.PkgPath] = pkg.ID
-		dir, err := commonDir(pkg.GoFiles)
-		if err != nil {
-			return nil, nil, err
-		}
-		if dir != "" {
-			pkgOfDir[dir] = append(pkgOfDir[dir], pkg)
-		}
-	}
-
-	// If no new imports are added, it is safe to avoid loading any needPkgs.
-	// Otherwise, it's hard to tell which package is actually being loaded
-	// (due to vendoring) and whether any modified package will show up
-	// in the transitive set of dependencies (because new imports are added,
-	// potentially modifying the transitive set of dependencies).
-	var overlayAddsImports bool
-
-	// If both a package and its test package are created by the overlay, we
-	// need the real package first. Process all non-test files before test
-	// files, and make the whole process deterministic while we're at it.
-	var overlayFiles []string
-	for opath := range state.cfg.Overlay {
-		overlayFiles = append(overlayFiles, opath)
-	}
-	sort.Slice(overlayFiles, func(i, j int) bool {
-		iTest := strings.HasSuffix(overlayFiles[i], "_test.go")
-		jTest := strings.HasSuffix(overlayFiles[j], "_test.go")
-		if iTest != jTest {
-			return !iTest // non-tests are before tests.
-		}
-		return overlayFiles[i] < overlayFiles[j]
-	})
-	for _, opath := range overlayFiles {
-		contents := state.cfg.Overlay[opath]
-		base := filepath.Base(opath)
-		dir := filepath.Dir(opath)
-		var pkg *Package           // if opath belongs to both a package and its test variant, this will be the test variant
-		var testVariantOf *Package // if opath is a test file, this is the package it is testing
-		var fileExists bool
-		isTestFile := strings.HasSuffix(opath, "_test.go")
-		pkgName, ok := extractPackageName(opath, contents)
-		if !ok {
-			// Don't bother adding a file that doesn't even have a parsable package statement
-			// to the overlay.
-			continue
-		}
-		// If all the overlay files belong to a different package, change the
-		// package name to that package.
-		maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir])
-	nextPackage:
-		for _, p := range response.dr.Packages {
-			if pkgName != p.Name && p.ID != "command-line-arguments" {
-				continue
-			}
-			for _, f := range p.GoFiles {
-				if !sameFile(filepath.Dir(f), dir) {
-					continue
-				}
-				// Make sure to capture information on the package's test variant, if needed.
-				if isTestFile && !hasTestFiles(p) {
-					// TODO(matloob): Are there packages other than the 'production' variant
-					// of a package that this can match? This shouldn't match the test main package
-					// because the file is generated in another directory.
-					testVariantOf = p
-					continue nextPackage
-				} else if !isTestFile && hasTestFiles(p) {
-					// We're examining a test variant, but the overlaid file is
-					// a non-test file. Because the overlay implementation
-					// (currently) only adds a file to one package, skip this
-					// package, so that we can add the file to the production
-					// variant of the package. (https://golang.org/issue/36857
-					// tracks handling overlays on both the production and test
-					// variant of a package).
-					continue nextPackage
-				}
-				if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath {
-					// We have already seen the production version of the
-					// for which p is a test variant.
-					if hasTestFiles(p) {
-						testVariantOf = pkg
-					}
-				}
-				pkg = p
-				if filepath.Base(f) == base {
-					fileExists = true
-				}
-			}
-		}
-		// The overlay could have included an entirely new package or an
-		// ad-hoc package. An ad-hoc package is one that we have manually
-		// constructed from inadequate `go list` results for a file= query.
-		// It will have the ID command-line-arguments.
-		if pkg == nil || pkg.ID == "command-line-arguments" {
-			// Try to find the module or gopath dir the file is contained in.
-			// Then for modules, add the module opath to the beginning.
-			pkgPath, ok, err := state.getPkgPath(dir)
-			if err != nil {
-				return nil, nil, err
-			}
-			if !ok {
-				break
-			}
-			var forTest string // only set for x tests
-			isXTest := strings.HasSuffix(pkgName, "_test")
-			if isXTest {
-				forTest = pkgPath
-				pkgPath += "_test"
-			}
-			id := pkgPath
-			if isTestFile {
-				if isXTest {
-					id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest)
-				} else {
-					id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath)
-				}
-			}
-			if pkg != nil {
-				// TODO(rstambler): We should change the package's path and ID
-				// here. The only issue is that this messes with the roots.
-			} else {
-				// Try to reclaim a package with the same ID, if it exists in the response.
-				for _, p := range response.dr.Packages {
-					if reclaimPackage(p, id, opath, contents) {
-						pkg = p
-						break
-					}
-				}
-				// Otherwise, create a new package.
-				if pkg == nil {
-					pkg = &Package{
-						PkgPath: pkgPath,
-						ID:      id,
-						Name:    pkgName,
-						Imports: make(map[string]*Package),
-					}
-					response.addPackage(pkg)
-					havePkgs[pkg.PkgPath] = id
-					// Add the production package's sources for a test variant.
-					if isTestFile && !isXTest && testVariantOf != nil {
-						pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...)
-						pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...)
-						// Add the package under test and its imports to the test variant.
-						pkg.forTest = testVariantOf.PkgPath
-						for k, v := range testVariantOf.Imports {
-							pkg.Imports[k] = &Package{ID: v.ID}
-						}
-					}
-					if isXTest {
-						pkg.forTest = forTest
-					}
-				}
-			}
-		}
-		if !fileExists {
-			pkg.GoFiles = append(pkg.GoFiles, opath)
-			// TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior
-			// if the file will be ignored due to its build tags.
-			pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath)
-			modifiedPkgsSet[pkg.ID] = true
-		}
-		imports, err := extractImports(opath, contents)
-		if err != nil {
-			// Let the parser or type checker report errors later.
-			continue
-		}
-		for _, imp := range imports {
-			// TODO(rstambler): If the package is an x test and the import has
-			// a test variant, make sure to replace it.
-			if _, found := pkg.Imports[imp]; found {
-				continue
-			}
-			overlayAddsImports = true
-			id, ok := havePkgs[imp]
-			if !ok {
-				var err error
-				id, err = state.resolveImport(dir, imp)
-				if err != nil {
-					return nil, nil, err
-				}
-			}
-			pkg.Imports[imp] = &Package{ID: id}
-			// Add dependencies to the non-test variant version of this package as well.
-			if testVariantOf != nil {
-				testVariantOf.Imports[imp] = &Package{ID: id}
-			}
-		}
-	}
-
-	// toPkgPath guesses the package path given the id.
-	toPkgPath := func(sourceDir, id string) (string, error) {
-		if i := strings.IndexByte(id, ' '); i >= 0 {
-			return state.resolveImport(sourceDir, id[:i])
-		}
-		return state.resolveImport(sourceDir, id)
-	}
-
-	// Now that new packages have been created, do another pass to determine
-	// the new set of missing packages.
-	for _, pkg := range response.dr.Packages {
-		for _, imp := range pkg.Imports {
-			if len(pkg.GoFiles) == 0 {
-				return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath)
-			}
-			pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID)
-			if err != nil {
-				return nil, nil, err
-			}
-			if _, ok := havePkgs[pkgPath]; !ok {
-				needPkgsSet[pkgPath] = true
-			}
-		}
-	}
-
-	if overlayAddsImports {
-		needPkgs = make([]string, 0, len(needPkgsSet))
-		for pkg := range needPkgsSet {
-			needPkgs = append(needPkgs, pkg)
-		}
-	}
-	modifiedPkgs = make([]string, 0, len(modifiedPkgsSet))
-	for pkg := range modifiedPkgsSet {
-		modifiedPkgs = append(modifiedPkgs, pkg)
-	}
-	return modifiedPkgs, needPkgs, err
-}
-
-// resolveImport finds the ID of a package given its import path.
-// In particular, it will find the right vendored copy when in GOPATH mode.
-func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) {
-	env, err := state.getEnv()
-	if err != nil {
-		return "", err
-	}
-	if env["GOMOD"] != "" {
-		return importPath, nil
-	}
-
-	searchDir := sourceDir
-	for {
-		vendorDir := filepath.Join(searchDir, "vendor")
-		exists, ok := state.vendorDirs[vendorDir]
-		if !ok {
-			info, err := os.Stat(vendorDir)
-			exists = err == nil && info.IsDir()
-			state.vendorDirs[vendorDir] = exists
-		}
-
-		if exists {
-			vendoredPath := filepath.Join(vendorDir, importPath)
-			if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() {
-				// We should probably check for .go files here, but shame on anyone who fools us.
-				path, ok, err := state.getPkgPath(vendoredPath)
-				if err != nil {
-					return "", err
-				}
-				if ok {
-					return path, nil
-				}
-			}
-		}
-
-		// We know we've hit the top of the filesystem when we Dir / and get /,
-		// or C:\ and get C:\, etc.
-		next := filepath.Dir(searchDir)
-		if next == searchDir {
-			break
-		}
-		searchDir = next
-	}
-	return importPath, nil
-}
-
-func hasTestFiles(p *Package) bool {
-	for _, f := range p.GoFiles {
-		if strings.HasSuffix(f, "_test.go") {
-			return true
-		}
-	}
-	return false
-}
-
 // determineRootDirs returns a mapping from absolute directories that could
 // contain code to their corresponding import path prefixes.
 func (state *golistState) determineRootDirs() (map[string]string, error) {
@@ -384,192 +81,3 @@ func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) {
 	}
 	return m, nil
 }
-
-func extractImports(filename string, contents []byte) ([]string, error) {
-	f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset?
-	if err != nil {
-		return nil, err
-	}
-	var res []string
-	for _, imp := range f.Imports {
-		quotedPath := imp.Path.Value
-		path, err := strconv.Unquote(quotedPath)
-		if err != nil {
-			return nil, err
-		}
-		res = append(res, path)
-	}
-	return res, nil
-}
-
-// reclaimPackage attempts to reuse a package that failed to load in an overlay.
-//
-// If the package has errors and has no Name, GoFiles, or Imports,
-// then it's possible that it doesn't yet exist on disk.
-func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool {
-	// TODO(rstambler): Check the message of the actual error?
-	// It differs between $GOPATH and module mode.
-	if pkg.ID != id {
-		return false
-	}
-	if len(pkg.Errors) != 1 {
-		return false
-	}
-	if pkg.Name != "" || pkg.ExportFile != "" {
-		return false
-	}
-	if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 {
-		return false
-	}
-	if len(pkg.Imports) > 0 {
-		return false
-	}
-	pkgName, ok := extractPackageName(filename, contents)
-	if !ok {
-		return false
-	}
-	pkg.Name = pkgName
-	pkg.Errors = nil
-	return true
-}
-
-func extractPackageName(filename string, contents []byte) (string, bool) {
-	// TODO(rstambler): Check the message of the actual error?
-	// It differs between $GOPATH and module mode.
-	f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset?
-	if err != nil {
-		return "", false
-	}
-	return f.Name.Name, true
-}
-
-// commonDir returns the directory that all files are in, "" if files is empty,
-// or an error if they aren't in the same directory.
-func commonDir(files []string) (string, error) {
-	seen := make(map[string]bool)
-	for _, f := range files {
-		seen[filepath.Dir(f)] = true
-	}
-	if len(seen) > 1 {
-		return "", fmt.Errorf("files (%v) are in more than one directory: %v", files, seen)
-	}
-	for k := range seen {
-		// seen has only one element; return it.
-		return k, nil
-	}
-	return "", nil // no files
-}
-
-// It is possible that the files in the disk directory dir have a different package
-// name from newName, which is deduced from the overlays. If they all have a different
-// package name, and they all have the same package name, then that name becomes
-// the package name.
-// It returns true if it changes the package name, false otherwise.
-func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) {
-	names := make(map[string]int)
-	for _, p := range pkgsOfDir {
-		names[p.Name]++
-	}
-	if len(names) != 1 {
-		// some files are in different packages
-		return
-	}
-	var oldName string
-	for k := range names {
-		oldName = k
-	}
-	if newName == oldName {
-		return
-	}
-	// We might have a case where all of the package names in the directory are
-	// the same, but the overlay file is for an x test, which belongs to its
-	// own package. If the x test does not yet exist on disk, we may not yet
-	// have its package name on disk, but we should not rename the packages.
-	//
-	// We use a heuristic to determine if this file belongs to an x test:
-	// The test file should have a package name whose package name has a _test
-	// suffix or looks like "newName_test".
-	maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test")
-	if isTestFile && maybeXTest {
-		return
-	}
-	for _, p := range pkgsOfDir {
-		p.Name = newName
-	}
-}
-
-// This function is copy-pasted from
-// https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360.
-// It should be deleted when we remove support for overlays from go/packages.
-//
-// NOTE: This does not handle any ./... or ./ style queries, as this function
-// doesn't know the working directory.
-//
-// matchPattern(pattern)(name) reports whether
-// name matches pattern. Pattern is a limited glob
-// pattern in which '...' means 'any string' and there
-// is no other special syntax.
-// Unfortunately, there are two special cases. Quoting "go help packages":
-//
-// First, /... at the end of the pattern can match an empty string,
-// so that net/... matches both net and packages in its subdirectories, like net/http.
-// Second, any slash-separated pattern element containing a wildcard never
-// participates in a match of the "vendor" element in the path of a vendored
-// package, so that ./... does not match packages in subdirectories of
-// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do.
-// Note, however, that a directory named vendor that itself contains code
-// is not a vendored package: cmd/vendor would be a command named vendor,
-// and the pattern cmd/... matches it.
-func matchPattern(pattern string) func(name string) bool {
-	// Convert pattern to regular expression.
-	// The strategy for the trailing /... is to nest it in an explicit ? expression.
-	// The strategy for the vendor exclusion is to change the unmatchable
-	// vendor strings to a disallowed code point (vendorChar) and to use
-	// "(anything but that codepoint)*" as the implementation of the ... wildcard.
-	// This is a bit complicated but the obvious alternative,
-	// namely a hand-written search like in most shell glob matchers,
-	// is too easy to make accidentally exponential.
-	// Using package regexp guarantees linear-time matching.
-
-	const vendorChar = "\x00"
-
-	if strings.Contains(pattern, vendorChar) {
-		return func(name string) bool { return false }
-	}
-
-	re := regexp.QuoteMeta(pattern)
-	re = replaceVendor(re, vendorChar)
-	switch {
-	case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`):
-		re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)`
-	case re == vendorChar+`/\.\.\.`:
-		re = `(/vendor|/` + vendorChar + `/\.\.\.)`
-	case strings.HasSuffix(re, `/\.\.\.`):
-		re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?`
-	}
-	re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`)
-
-	reg := regexp.MustCompile(`^` + re + `$`)
-
-	return func(name string) bool {
-		if strings.Contains(name, vendorChar) {
-			return false
-		}
-		return reg.MatchString(replaceVendor(name, vendorChar))
-	}
-}
-
-// replaceVendor returns the result of replacing
-// non-trailing vendor path elements in x with repl.
-func replaceVendor(x, repl string) string {
-	if !strings.Contains(x, "vendor") {
-		return x
-	}
-	elem := strings.Split(x, "/")
-	for i := 0; i < len(elem)-1; i++ {
-		if elem[i] == "vendor" {
-			elem[i] = repl
-		}
-	}
-	return strings.Join(elem, "/")
-}
diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go
index 0f1505b80..81e9e6a72 100644
--- a/vendor/golang.org/x/tools/go/packages/packages.go
+++ b/vendor/golang.org/x/tools/go/packages/packages.go
@@ -16,7 +16,6 @@ import (
 	"go/token"
 	"go/types"
 	"io"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -28,8 +27,8 @@ import (
 	"golang.org/x/tools/go/gcexportdata"
 	"golang.org/x/tools/internal/gocommand"
 	"golang.org/x/tools/internal/packagesinternal"
-	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal"
+	"golang.org/x/tools/internal/versions"
 )
 
 // A LoadMode controls the amount of detail to return when loading.
@@ -220,8 +219,10 @@ type driverResponse struct {
 	// lists of multiple drivers, go/packages will fall back to the next driver.
 	NotHandled bool
 
-	// Sizes, if not nil, is the types.Sizes to use when type checking.
-	Sizes *types.StdSizes
+	// Compiler and Arch are the arguments pass of types.SizesFor
+	// to get a types.Sizes to use when type checking.
+	Compiler string
+	Arch     string
 
 	// Roots is the set of package IDs that make up the root packages.
 	// We have to encode this separately because when we encode a single package
@@ -257,31 +258,52 @@ type driverResponse struct {
 // proceeding with further analysis. The PrintErrors function is
 // provided for convenient display of all errors.
 func Load(cfg *Config, patterns ...string) ([]*Package, error) {
-	l := newLoader(cfg)
-	response, err := defaultDriver(&l.Config, patterns...)
+	ld := newLoader(cfg)
+	response, external, err := defaultDriver(&ld.Config, patterns...)
 	if err != nil {
 		return nil, err
 	}
-	l.sizes = response.Sizes
-	return l.refine(response)
+
+	ld.sizes = types.SizesFor(response.Compiler, response.Arch)
+	if ld.sizes == nil && ld.Config.Mode&(NeedTypes|NeedTypesSizes|NeedTypesInfo) != 0 {
+		// Type size information is needed but unavailable.
+		if external {
+			// An external driver may fail to populate the Compiler/GOARCH fields,
+			// especially since they are relatively new (see #63700).
+			// Provide a sensible fallback in this case.
+			ld.sizes = types.SizesFor("gc", runtime.GOARCH)
+			if ld.sizes == nil { // gccgo-only arch
+				ld.sizes = types.SizesFor("gc", "amd64")
+			}
+		} else {
+			// Go list should never fail to deliver accurate size information.
+			// Reject the whole Load since the error is the same for every package.
+			return nil, fmt.Errorf("can't determine type sizes for compiler %q on GOARCH %q",
+				response.Compiler, response.Arch)
+		}
+	}
+
+	return ld.refine(response)
 }
 
 // defaultDriver is a driver that implements go/packages' fallback behavior.
 // It will try to request to an external driver, if one exists. If there's
 // no external driver, or the driver returns a response with NotHandled set,
 // defaultDriver will fall back to the go list driver.
-func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
-	driver := findExternalDriver(cfg)
-	if driver == nil {
-		driver = goListDriver
-	}
-	response, err := driver(cfg, patterns...)
-	if err != nil {
-		return response, err
-	} else if response.NotHandled {
-		return goListDriver(cfg, patterns...)
+// The boolean result indicates that an external driver handled the request.
+func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, bool, error) {
+	if driver := findExternalDriver(cfg); driver != nil {
+		response, err := driver(cfg, patterns...)
+		if err != nil {
+			return nil, false, err
+		} else if !response.NotHandled {
+			return response, true, nil
+		}
+		// (fall through)
 	}
-	return response, nil
+
+	response, err := goListDriver(cfg, patterns...)
+	return response, false, err
 }
 
 // A Package describes a loaded Go package.
@@ -308,6 +330,9 @@ type Package struct {
 	TypeErrors []types.Error
 
 	// GoFiles lists the absolute file paths of the package's Go source files.
+	// It may include files that should not be compiled, for example because
+	// they contain non-matching build tags, are documentary pseudo-files such as
+	// unsafe/unsafe.go or builtin/builtin.go, or are subject to cgo preprocessing.
 	GoFiles []string
 
 	// CompiledGoFiles lists the absolute file paths of the package's source
@@ -407,12 +432,6 @@ func init() {
 	packagesinternal.GetDepsErrors = func(p interface{}) []*packagesinternal.PackageError {
 		return p.(*Package).depsErrors
 	}
-	packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner {
-		return config.(*Config).gocmdRunner
-	}
-	packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {
-		config.(*Config).gocmdRunner = runner
-	}
 	packagesinternal.SetModFile = func(config interface{}, value string) {
 		config.(*Config).modFile = value
 	}
@@ -549,7 +568,7 @@ type loaderPackage struct {
 type loader struct {
 	pkgs map[string]*loaderPackage
 	Config
-	sizes        types.Sizes
+	sizes        types.Sizes // non-nil if needed by mode
 	parseCache   map[string]*parseValue
 	parseCacheMu sync.Mutex
 	exportMu     sync.Mutex // enforces mutual exclusion of exportdata operations
@@ -627,7 +646,7 @@ func newLoader(cfg *Config) *loader {
 	return ld
 }
 
-// refine connects the supplied packages into a graph and then adds type and
+// refine connects the supplied packages into a graph and then adds type
 // and syntax information as requested by the LoadMode.
 func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
 	roots := response.Roots
@@ -674,39 +693,38 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
 		}
 	}
 
-	// Materialize the import graph.
-
-	const (
-		white = 0 // new
-		grey  = 1 // in progress
-		black = 2 // complete
-	)
-
-	// visit traverses the import graph, depth-first,
-	// and materializes the graph as Packages.Imports.
-	//
-	// Valid imports are saved in the Packages.Import map.
-	// Invalid imports (cycles and missing nodes) are saved in the importErrors map.
-	// Thus, even in the presence of both kinds of errors, the Import graph remains a DAG.
-	//
-	// visit returns whether the package needs src or has a transitive
-	// dependency on a package that does. These are the only packages
-	// for which we load source code.
-	var stack []*loaderPackage
-	var visit func(lpkg *loaderPackage) bool
-	var srcPkgs []*loaderPackage
-	visit = func(lpkg *loaderPackage) bool {
-		switch lpkg.color {
-		case black:
-			return lpkg.needsrc
-		case grey:
-			panic("internal error: grey node")
-		}
-		lpkg.color = grey
-		stack = append(stack, lpkg) // push
-		stubs := lpkg.Imports       // the structure form has only stubs with the ID in the Imports
-		// If NeedImports isn't set, the imports fields will all be zeroed out.
-		if ld.Mode&NeedImports != 0 {
+	if ld.Mode&NeedImports != 0 {
+		// Materialize the import graph.
+
+		const (
+			white = 0 // new
+			grey  = 1 // in progress
+			black = 2 // complete
+		)
+
+		// visit traverses the import graph, depth-first,
+		// and materializes the graph as Packages.Imports.
+		//
+		// Valid imports are saved in the Packages.Import map.
+		// Invalid imports (cycles and missing nodes) are saved in the importErrors map.
+		// Thus, even in the presence of both kinds of errors,
+		// the Import graph remains a DAG.
+		//
+		// visit returns whether the package needs src or has a transitive
+		// dependency on a package that does. These are the only packages
+		// for which we load source code.
+		var stack []*loaderPackage
+		var visit func(lpkg *loaderPackage) bool
+		visit = func(lpkg *loaderPackage) bool {
+			switch lpkg.color {
+			case black:
+				return lpkg.needsrc
+			case grey:
+				panic("internal error: grey node")
+			}
+			lpkg.color = grey
+			stack = append(stack, lpkg) // push
+			stubs := lpkg.Imports       // the structure form has only stubs with the ID in the Imports
 			lpkg.Imports = make(map[string]*Package, len(stubs))
 			for importPath, ipkg := range stubs {
 				var importErr error
@@ -730,40 +748,39 @@ func (ld *loader) refine(response *driverResponse) ([]*Package, error) {
 				}
 				lpkg.Imports[importPath] = imp.Package
 			}
-		}
-		if lpkg.needsrc {
-			srcPkgs = append(srcPkgs, lpkg)
-		}
-		if ld.Mode&NeedTypesSizes != 0 {
-			lpkg.TypesSizes = ld.sizes
-		}
-		stack = stack[:len(stack)-1] // pop
-		lpkg.color = black
 
-		return lpkg.needsrc
-	}
+			// Complete type information is required for the
+			// immediate dependencies of each source package.
+			if lpkg.needsrc && ld.Mode&NeedTypes != 0 {
+				for _, ipkg := range lpkg.Imports {
+					ld.pkgs[ipkg.ID].needtypes = true
+				}
+			}
 
-	if ld.Mode&NeedImports == 0 {
-		// We do this to drop the stub import packages that we are not even going to try to resolve.
-		for _, lpkg := range initial {
-			lpkg.Imports = nil
+			// NeedTypeSizes causes TypeSizes to be set even
+			// on packages for which types aren't needed.
+			if ld.Mode&NeedTypesSizes != 0 {
+				lpkg.TypesSizes = ld.sizes
+			}
+			stack = stack[:len(stack)-1] // pop
+			lpkg.color = black
+
+			return lpkg.needsrc
 		}
-	} else {
+
 		// For each initial package, create its import DAG.
 		for _, lpkg := range initial {
 			visit(lpkg)
 		}
-	}
-	if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 {
-		for _, lpkg := range srcPkgs {
-			// Complete type information is required for the
-			// immediate dependencies of each source package.
-			for _, ipkg := range lpkg.Imports {
-				imp := ld.pkgs[ipkg.ID]
-				imp.needtypes = true
-			}
+
+	} else {
+		// !NeedImports: drop the stub (ID-only) import packages
+		// that we are not even going to try to resolve.
+		for _, lpkg := range initial {
+			lpkg.Imports = nil
 		}
 	}
+
 	// Load type data and syntax if needed, starting at
 	// the initial packages (roots of the import DAG).
 	if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 {
@@ -997,10 +1014,11 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
 		Defs:       make(map[*ast.Ident]types.Object),
 		Uses:       make(map[*ast.Ident]types.Object),
 		Implicits:  make(map[ast.Node]types.Object),
+		Instances:  make(map[*ast.Ident]types.Instance),
 		Scopes:     make(map[ast.Node]*types.Scope),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 	}
-	typeparams.InitInstanceInfo(lpkg.TypesInfo)
+	versions.InitFileVersions(lpkg.TypesInfo)
 	lpkg.TypesSizes = ld.sizes
 
 	importer := importerFunc(func(path string) (*types.Package, error) {
@@ -1038,7 +1056,10 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) {
 		IgnoreFuncBodies: ld.Mode&NeedDeps == 0 && !lpkg.initial,
 
 		Error: appendError,
-		Sizes: ld.sizes,
+		Sizes: ld.sizes, // may be nil
+	}
+	if lpkg.Module != nil && lpkg.Module.GoVersion != "" {
+		typesinternal.SetGoVersion(tc, "go"+lpkg.Module.GoVersion)
 	}
 	if (ld.Mode & typecheckCgo) != 0 {
 		if !typesinternal.SetUsesCgo(tc) {
@@ -1119,7 +1140,7 @@ func (ld *loader) parseFile(filename string) (*ast.File, error) {
 		var err error
 		if src == nil {
 			ioLimit <- true // wait
-			src, err = ioutil.ReadFile(filename)
+			src, err = os.ReadFile(filename)
 			<-ioLimit // signal
 		}
 		if err != nil {
diff --git a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
index be8f5a867..11d5c8c3a 100644
--- a/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
+++ b/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
@@ -26,13 +26,10 @@ package objectpath
 import (
 	"fmt"
 	"go/types"
-	"sort"
 	"strconv"
 	"strings"
 
 	"golang.org/x/tools/internal/typeparams"
-
-	_ "unsafe" // for go:linkname
 )
 
 // A Path is an opaque name that identifies a types.Object
@@ -113,6 +110,19 @@ const (
 	opObj    = 'O' // .Obj()		 (Named, TypeParam)
 )
 
+// For is equivalent to new(Encoder).For(obj).
+//
+// It may be more efficient to reuse a single Encoder across several calls.
+func For(obj types.Object) (Path, error) {
+	return new(Encoder).For(obj)
+}
+
+// An Encoder amortizes the cost of encoding the paths of multiple objects.
+// The zero value of an Encoder is ready to use.
+type Encoder struct {
+	scopeMemo map[*types.Scope][]types.Object // memoization of scopeObjects
+}
+
 // For returns the path to an object relative to its package,
 // or an error if the object is not accessible from the package's Scope.
 //
@@ -125,6 +135,17 @@ const (
 // These objects are sufficient to define the API of their package.
 // The objects described by a package's export data are drawn from this set.
 //
+// The set of objects accessible from a package's Scope depends on
+// whether the package was produced by type-checking syntax, or
+// reading export data; the latter may have a smaller Scope since
+// export data trims objects that are not reachable from an exported
+// declaration. For example, the For function will return a path for
+// an exported method of an unexported type that is not reachable
+// from any public declaration; this path will cause the Object
+// function to fail if called on a package loaded from export data.
+// TODO(adonovan): is this a bug or feature? Should this package
+// compute accessibility in the same way?
+//
 // For does not return a path for predeclared names, imported package
 // names, local names, and unexported package-level names (except
 // types).
@@ -145,24 +166,7 @@ const (
 //	.Type().Field(0)					(field Var X)
 //
 // where p is the package (*types.Package) to which X belongs.
-func For(obj types.Object) (Path, error) {
-	return newEncoderFor()(obj)
-}
-
-// An encoder amortizes the cost of encoding the paths of multiple objects.
-// Nonexported pending approval of proposal 58668.
-type encoder struct {
-	scopeNamesMemo   map[*types.Scope][]string      // memoization of Scope.Names()
-	namedMethodsMemo map[*types.Named][]*types.Func // memoization of namedMethods()
-}
-
-// Exposed to gopls via golang.org/x/tools/internal/typesinternal
-// pending approval of proposal 58668.
-//
-//go:linkname newEncoderFor
-func newEncoderFor() func(types.Object) (Path, error) { return new(encoder).For }
-
-func (enc *encoder) For(obj types.Object) (Path, error) {
+func (enc *Encoder) For(obj types.Object) (Path, error) {
 	pkg := obj.Pkg()
 
 	// This table lists the cases of interest.
@@ -219,7 +223,7 @@ func (enc *encoder) For(obj types.Object) (Path, error) {
 	//    Reject obviously non-viable cases.
 	switch obj := obj.(type) {
 	case *types.TypeName:
-		if _, ok := obj.Type().(*typeparams.TypeParam); !ok {
+		if _, ok := obj.Type().(*types.TypeParam); !ok {
 			// With the exception of type parameters, only package-level type names
 			// have a path.
 			return "", fmt.Errorf("no path for %v", obj)
@@ -260,15 +264,14 @@ func (enc *encoder) For(obj types.Object) (Path, error) {
 	// the best paths because non-types may
 	// refer to types, but not the reverse.
 	empty := make([]byte, 0, 48) // initial space
-	names := enc.scopeNames(scope)
-	for _, name := range names {
-		o := scope.Lookup(name)
+	objs := enc.scopeObjects(scope)
+	for _, o := range objs {
 		tname, ok := o.(*types.TypeName)
 		if !ok {
 			continue // handle non-types in second pass
 		}
 
-		path := append(empty, name...)
+		path := append(empty, o.Name()...)
 		path = append(path, opType)
 
 		T := o.Type()
@@ -280,7 +283,7 @@ func (enc *encoder) For(obj types.Object) (Path, error) {
 			}
 		} else {
 			if named, _ := T.(*types.Named); named != nil {
-				if r := findTypeParam(obj, typeparams.ForNamed(named), path, nil); r != nil {
+				if r := findTypeParam(obj, named.TypeParams(), path, nil); r != nil {
 					// generic named type
 					return Path(r), nil
 				}
@@ -294,9 +297,8 @@ func (enc *encoder) For(obj types.Object) (Path, error) {
 
 	// Then inspect everything else:
 	// non-types, and declared methods of defined types.
-	for _, name := range names {
-		o := scope.Lookup(name)
-		path := append(empty, name...)
+	for _, o := range objs {
+		path := append(empty, o.Name()...)
 		if _, ok := o.(*types.TypeName); !ok {
 			if o.Exported() {
 				// exported non-type (const, var, func)
@@ -310,10 +312,12 @@ func (enc *encoder) For(obj types.Object) (Path, error) {
 		// Inspect declared methods of defined types.
 		if T, ok := o.Type().(*types.Named); ok {
 			path = append(path, opType)
-			// Note that method index here is always with respect
-			// to canonical ordering of methods, regardless of how
-			// they appear in the underlying type.
-			for i, m := range enc.namedMethods(T) {
+			// The method index here is always with respect
+			// to the underlying go/types data structures,
+			// which ultimately derives from source order
+			// and must be preserved by export data.
+			for i := 0; i < T.NumMethods(); i++ {
+				m := T.Method(i)
 				path2 := appendOpArg(path, opMethod, i)
 				if m == obj {
 					return Path(path2), nil // found declared method
@@ -341,7 +345,7 @@ func appendOpArg(path []byte, op byte, arg int) []byte {
 // This function is just an optimization that avoids the general scope walking
 // approach. You are expected to fall back to the general approach if this
 // function fails.
-func (enc *encoder) concreteMethod(meth *types.Func) (Path, bool) {
+func (enc *Encoder) concreteMethod(meth *types.Func) (Path, bool) {
 	// Concrete methods can only be declared on package-scoped named types. For
 	// that reason we can skip the expensive walk over the package scope: the
 	// path will always be package -> named type -> method. We can trivially get
@@ -414,14 +418,24 @@ func (enc *encoder) concreteMethod(meth *types.Func) (Path, bool) {
 	path := make([]byte, 0, len(name)+8)
 	path = append(path, name...)
 	path = append(path, opType)
-	for i, m := range enc.namedMethods(named) {
-		if m == meth {
+
+	// Method indices are w.r.t. the go/types data structures,
+	// ultimately deriving from source order,
+	// which is preserved by export data.
+	for i := 0; i < named.NumMethods(); i++ {
+		if named.Method(i) == meth {
 			path = appendOpArg(path, opMethod, i)
 			return Path(path), true
 		}
 	}
 
-	panic(fmt.Sprintf("couldn't find method %s on type %s", meth, named))
+	// Due to golang/go#59944, go/types fails to associate the receiver with
+	// certain methods on cgo types.
+	//
+	// TODO(rfindley): replace this panic once golang/go#59944 is fixed in all Go
+	// versions gopls supports.
+	return "", false
+	// panic(fmt.Sprintf("couldn't find method %s on type %s; methods: %#v", meth, named, enc.namedMethods(named)))
 }
 
 // find finds obj within type T, returning the path to it, or nil if not found.
@@ -448,7 +462,7 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]
 		}
 		return find(obj, T.Elem(), append(path, opElem), seen)
 	case *types.Signature:
-		if r := findTypeParam(obj, typeparams.ForSignature(T), path, seen); r != nil {
+		if r := findTypeParam(obj, T.TypeParams(), path, seen); r != nil {
 			return r
 		}
 		if r := find(obj, T.Params(), append(path, opParams), seen); r != nil {
@@ -491,7 +505,7 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]
 			}
 		}
 		return nil
-	case *typeparams.TypeParam:
+	case *types.TypeParam:
 		name := T.Obj()
 		if name == obj {
 			return append(path, opObj)
@@ -511,7 +525,7 @@ func find(obj types.Object, T types.Type, path []byte, seen map[*types.TypeName]
 	panic(T)
 }
 
-func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte {
+func findTypeParam(obj types.Object, list *types.TypeParamList, path []byte, seen map[*types.TypeName]bool) []byte {
 	for i := 0; i < list.Len(); i++ {
 		tparam := list.At(i)
 		path2 := appendOpArg(path, opTypeParam, i)
@@ -524,11 +538,11 @@ func findTypeParam(obj types.Object, list *typeparams.TypeParamList, path []byte
 
 // Object returns the object denoted by path p within the package pkg.
 func Object(pkg *types.Package, p Path) (types.Object, error) {
-	if p == "" {
+	pathstr := string(p)
+	if pathstr == "" {
 		return nil, fmt.Errorf("empty path")
 	}
 
-	pathstr := string(p)
 	var pkgobj, suffix string
 	if dot := strings.IndexByte(pathstr, opType); dot < 0 {
 		pkgobj = pathstr
@@ -548,7 +562,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
 	}
 	// abstraction of *types.{Named,Signature}
 	type hasTypeParams interface {
-		TypeParams() *typeparams.TypeParamList
+		TypeParams() *types.TypeParamList
 	}
 	// abstraction of *types.{Named,TypeParam}
 	type hasObj interface {
@@ -650,7 +664,7 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
 			t = tparams.At(index)
 
 		case opConstraint:
-			tparam, ok := t.(*typeparams.TypeParam)
+			tparam, ok := t.(*types.TypeParam)
 			if !ok {
 				return nil, fmt.Errorf("cannot apply %q to %s (got %T, want type parameter)", code, t, t)
 			}
@@ -687,11 +701,10 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
 				obj = t.Method(index) // Id-ordered
 
 			case *types.Named:
-				methods := namedMethods(t) // (unmemoized)
-				if index >= len(methods) {
-					return nil, fmt.Errorf("method index %d out of range [0-%d)", index, len(methods))
+				if index >= t.NumMethods() {
+					return nil, fmt.Errorf("method index %d out of range [0-%d)", index, t.NumMethods())
 				}
-				obj = methods[index] // Id-ordered
+				obj = t.Method(index)
 
 			default:
 				return nil, fmt.Errorf("cannot apply %q to %s (got %T, want interface or named)", code, t, t)
@@ -718,45 +731,22 @@ func Object(pkg *types.Package, p Path) (types.Object, error) {
 	return obj, nil // success
 }
 
-// namedMethods returns the methods of a Named type in ascending Id order.
-func namedMethods(named *types.Named) []*types.Func {
-	methods := make([]*types.Func, named.NumMethods())
-	for i := range methods {
-		methods[i] = named.Method(i)
-	}
-	sort.Slice(methods, func(i, j int) bool {
-		return methods[i].Id() < methods[j].Id()
-	})
-	return methods
-}
-
-// scopeNames is a memoization of scope.Names. Callers must not modify the result.
-func (enc *encoder) scopeNames(scope *types.Scope) []string {
-	m := enc.scopeNamesMemo
+// scopeObjects is a memoization of scope objects.
+// Callers must not modify the result.
+func (enc *Encoder) scopeObjects(scope *types.Scope) []types.Object {
+	m := enc.scopeMemo
 	if m == nil {
-		m = make(map[*types.Scope][]string)
-		enc.scopeNamesMemo = m
+		m = make(map[*types.Scope][]types.Object)
+		enc.scopeMemo = m
 	}
-	names, ok := m[scope]
+	objs, ok := m[scope]
 	if !ok {
-		names = scope.Names() // allocates and sorts
-		m[scope] = names
-	}
-	return names
-}
-
-// namedMethods is a memoization of the namedMethods function. Callers must not modify the result.
-func (enc *encoder) namedMethods(named *types.Named) []*types.Func {
-	m := enc.namedMethodsMemo
-	if m == nil {
-		m = make(map[*types.Named][]*types.Func)
-		enc.namedMethodsMemo = m
-	}
-	methods, ok := m[named]
-	if !ok {
-		methods = namedMethods(named) // allocates and sorts
-		m[named] = methods
+		names := scope.Names() // allocates and sorts
+		objs = make([]types.Object, len(names))
+		for i, name := range names {
+			objs[i] = scope.Lookup(name)
+		}
+		m[scope] = objs
 	}
-	return methods
-
+	return objs
 }
diff --git a/vendor/golang.org/x/tools/internal/event/keys/util.go b/vendor/golang.org/x/tools/internal/event/keys/util.go
new file mode 100644
index 000000000..c0e8e731c
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/event/keys/util.go
@@ -0,0 +1,21 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package keys
+
+import (
+	"sort"
+	"strings"
+)
+
+// Join returns a canonical join of the keys in S:
+// a sorted comma-separated string list.
+func Join[S ~[]T, T ~string](s S) string {
+	strs := make([]string, 0, len(s))
+	for _, v := range s {
+		strs = append(strs, string(v))
+	}
+	sort.Strings(strs)
+	return strings.Join(strs, ",")
+}
diff --git a/vendor/golang.org/x/tools/internal/event/tag/tag.go b/vendor/golang.org/x/tools/internal/event/tag/tag.go
new file mode 100644
index 000000000..581b26c20
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/event/tag/tag.go
@@ -0,0 +1,59 @@
+// Copyright 2019 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package tag provides the labels used for telemetry throughout gopls.
+package tag
+
+import (
+	"golang.org/x/tools/internal/event/keys"
+)
+
+var (
+	// create the label keys we use
+	Method        = keys.NewString("method", "")
+	StatusCode    = keys.NewString("status.code", "")
+	StatusMessage = keys.NewString("status.message", "")
+	RPCID         = keys.NewString("id", "")
+	RPCDirection  = keys.NewString("direction", "")
+	File          = keys.NewString("file", "")
+	Directory     = keys.New("directory", "")
+	URI           = keys.New("URI", "")
+	Package       = keys.NewString("package", "") // sorted comma-separated list of Package IDs
+	PackagePath   = keys.NewString("package_path", "")
+	Query         = keys.New("query", "")
+	Snapshot      = keys.NewUInt64("snapshot", "")
+	Operation     = keys.NewString("operation", "")
+
+	Position     = keys.New("position", "")
+	Category     = keys.NewString("category", "")
+	PackageCount = keys.NewInt("packages", "")
+	Files        = keys.New("files", "")
+	Port         = keys.NewInt("port", "")
+	Type         = keys.New("type", "")
+	HoverKind    = keys.NewString("hoverkind", "")
+
+	NewServer = keys.NewString("new_server", "A new server was added")
+	EndServer = keys.NewString("end_server", "A server was shut down")
+
+	ServerID     = keys.NewString("server", "The server ID an event is related to")
+	Logfile      = keys.NewString("logfile", "")
+	DebugAddress = keys.NewString("debug_address", "")
+	GoplsPath    = keys.NewString("gopls_path", "")
+	ClientID     = keys.NewString("client_id", "")
+
+	Level = keys.NewInt("level", "The logging level")
+)
+
+var (
+	// create the stats we measure
+	Started       = keys.NewInt64("started", "Count of started RPCs.")
+	ReceivedBytes = keys.NewInt64("received_bytes", "Bytes received.")            //, unit.Bytes)
+	SentBytes     = keys.NewInt64("sent_bytes", "Bytes sent.")                    //, unit.Bytes)
+	Latency       = keys.NewFloat64("latency_ms", "Elapsed time in milliseconds") //, unit.Milliseconds)
+)
+
+const (
+	Inbound  = "in"
+	Outbound = "out"
+)
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/bexport.go b/vendor/golang.org/x/tools/internal/gcimporter/bexport.go
deleted file mode 100644
index 30582ed6d..000000000
--- a/vendor/golang.org/x/tools/internal/gcimporter/bexport.go
+++ /dev/null
@@ -1,852 +0,0 @@
-// Copyright 2016 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Binary package export.
-// This file was derived from $GOROOT/src/cmd/compile/internal/gc/bexport.go;
-// see that file for specification of the format.
-
-package gcimporter
-
-import (
-	"bytes"
-	"encoding/binary"
-	"fmt"
-	"go/constant"
-	"go/token"
-	"go/types"
-	"math"
-	"math/big"
-	"sort"
-	"strings"
-)
-
-// If debugFormat is set, each integer and string value is preceded by a marker
-// and position information in the encoding. This mechanism permits an importer
-// to recognize immediately when it is out of sync. The importer recognizes this
-// mode automatically (i.e., it can import export data produced with debugging
-// support even if debugFormat is not set at the time of import). This mode will
-// lead to massively larger export data (by a factor of 2 to 3) and should only
-// be enabled during development and debugging.
-//
-// NOTE: This flag is the first flag to enable if importing dies because of
-// (suspected) format errors, and whenever a change is made to the format.
-const debugFormat = false // default: false
-
-// Current export format version. Increase with each format change.
-//
-// Note: The latest binary (non-indexed) export format is at version 6.
-// This exporter is still at level 4, but it doesn't matter since
-// the binary importer can handle older versions just fine.
-//
-//	6: package height (CL 105038) -- NOT IMPLEMENTED HERE
-//	5: improved position encoding efficiency (issue 20080, CL 41619) -- NOT IMPLEMENTED HERE
-//	4: type name objects support type aliases, uses aliasTag
-//	3: Go1.8 encoding (same as version 2, aliasTag defined but never used)
-//	2: removed unused bool in ODCL export (compiler only)
-//	1: header format change (more regular), export package for _ struct fields
-//	0: Go1.7 encoding
-const exportVersion = 4
-
-// trackAllTypes enables cycle tracking for all types, not just named
-// types. The existing compiler invariants assume that unnamed types
-// that are not completely set up are not used, or else there are spurious
-// errors.
-// If disabled, only named types are tracked, possibly leading to slightly
-// less efficient encoding in rare cases. It also prevents the export of
-// some corner-case type declarations (but those are not handled correctly
-// with with the textual export format either).
-// TODO(gri) enable and remove once issues caused by it are fixed
-const trackAllTypes = false
-
-type exporter struct {
-	fset *token.FileSet
-	out  bytes.Buffer
-
-	// object -> index maps, indexed in order of serialization
-	strIndex map[string]int
-	pkgIndex map[*types.Package]int
-	typIndex map[types.Type]int
-
-	// position encoding
-	posInfoFormat bool
-	prevFile      string
-	prevLine      int
-
-	// debugging support
-	written int // bytes written
-	indent  int // for trace
-}
-
-// internalError represents an error generated inside this package.
-type internalError string
-
-func (e internalError) Error() string { return "gcimporter: " + string(e) }
-
-func internalErrorf(format string, args ...interface{}) error {
-	return internalError(fmt.Sprintf(format, args...))
-}
-
-// BExportData returns binary export data for pkg.
-// If no file set is provided, position info will be missing.
-func BExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) {
-	if !debug {
-		defer func() {
-			if e := recover(); e != nil {
-				if ierr, ok := e.(internalError); ok {
-					err = ierr
-					return
-				}
-				// Not an internal error; panic again.
-				panic(e)
-			}
-		}()
-	}
-
-	p := exporter{
-		fset:          fset,
-		strIndex:      map[string]int{"": 0}, // empty string is mapped to 0
-		pkgIndex:      make(map[*types.Package]int),
-		typIndex:      make(map[types.Type]int),
-		posInfoFormat: true, // TODO(gri) might become a flag, eventually
-	}
-
-	// write version info
-	// The version string must start with "version %d" where %d is the version
-	// number. Additional debugging information may follow after a blank; that
-	// text is ignored by the importer.
-	p.rawStringln(fmt.Sprintf("version %d", exportVersion))
-	var debug string
-	if debugFormat {
-		debug = "debug"
-	}
-	p.rawStringln(debug) // cannot use p.bool since it's affected by debugFormat; also want to see this clearly
-	p.bool(trackAllTypes)
-	p.bool(p.posInfoFormat)
-
-	// --- generic export data ---
-
-	// populate type map with predeclared "known" types
-	for index, typ := range predeclared() {
-		p.typIndex[typ] = index
-	}
-	if len(p.typIndex) != len(predeclared()) {
-		return nil, internalError("duplicate entries in type map?")
-	}
-
-	// write package data
-	p.pkg(pkg, true)
-	if trace {
-		p.tracef("\n")
-	}
-
-	// write objects
-	objcount := 0
-	scope := pkg.Scope()
-	for _, name := range scope.Names() {
-		if !token.IsExported(name) {
-			continue
-		}
-		if trace {
-			p.tracef("\n")
-		}
-		p.obj(scope.Lookup(name))
-		objcount++
-	}
-
-	// indicate end of list
-	if trace {
-		p.tracef("\n")
-	}
-	p.tag(endTag)
-
-	// for self-verification only (redundant)
-	p.int(objcount)
-
-	if trace {
-		p.tracef("\n")
-	}
-
-	// --- end of export data ---
-
-	return p.out.Bytes(), nil
-}
-
-func (p *exporter) pkg(pkg *types.Package, emptypath bool) {
-	if pkg == nil {
-		panic(internalError("unexpected nil pkg"))
-	}
-
-	// if we saw the package before, write its index (>= 0)
-	if i, ok := p.pkgIndex[pkg]; ok {
-		p.index('P', i)
-		return
-	}
-
-	// otherwise, remember the package, write the package tag (< 0) and package data
-	if trace {
-		p.tracef("P%d = { ", len(p.pkgIndex))
-		defer p.tracef("} ")
-	}
-	p.pkgIndex[pkg] = len(p.pkgIndex)
-
-	p.tag(packageTag)
-	p.string(pkg.Name())
-	if emptypath {
-		p.string("")
-	} else {
-		p.string(pkg.Path())
-	}
-}
-
-func (p *exporter) obj(obj types.Object) {
-	switch obj := obj.(type) {
-	case *types.Const:
-		p.tag(constTag)
-		p.pos(obj)
-		p.qualifiedName(obj)
-		p.typ(obj.Type())
-		p.value(obj.Val())
-
-	case *types.TypeName:
-		if obj.IsAlias() {
-			p.tag(aliasTag)
-			p.pos(obj)
-			p.qualifiedName(obj)
-		} else {
-			p.tag(typeTag)
-		}
-		p.typ(obj.Type())
-
-	case *types.Var:
-		p.tag(varTag)
-		p.pos(obj)
-		p.qualifiedName(obj)
-		p.typ(obj.Type())
-
-	case *types.Func:
-		p.tag(funcTag)
-		p.pos(obj)
-		p.qualifiedName(obj)
-		sig := obj.Type().(*types.Signature)
-		p.paramList(sig.Params(), sig.Variadic())
-		p.paramList(sig.Results(), false)
-
-	default:
-		panic(internalErrorf("unexpected object %v (%T)", obj, obj))
-	}
-}
-
-func (p *exporter) pos(obj types.Object) {
-	if !p.posInfoFormat {
-		return
-	}
-
-	file, line := p.fileLine(obj)
-	if file == p.prevFile {
-		// common case: write line delta
-		// delta == 0 means different file or no line change
-		delta := line - p.prevLine
-		p.int(delta)
-		if delta == 0 {
-			p.int(-1) // -1 means no file change
-		}
-	} else {
-		// different file
-		p.int(0)
-		// Encode filename as length of common prefix with previous
-		// filename, followed by (possibly empty) suffix. Filenames
-		// frequently share path prefixes, so this can save a lot
-		// of space and make export data size less dependent on file
-		// path length. The suffix is unlikely to be empty because
-		// file names tend to end in ".go".
-		n := commonPrefixLen(p.prevFile, file)
-		p.int(n)           // n >= 0
-		p.string(file[n:]) // write suffix only
-		p.prevFile = file
-		p.int(line)
-	}
-	p.prevLine = line
-}
-
-func (p *exporter) fileLine(obj types.Object) (file string, line int) {
-	if p.fset != nil {
-		pos := p.fset.Position(obj.Pos())
-		file = pos.Filename
-		line = pos.Line
-	}
-	return
-}
-
-func commonPrefixLen(a, b string) int {
-	if len(a) > len(b) {
-		a, b = b, a
-	}
-	// len(a) <= len(b)
-	i := 0
-	for i < len(a) && a[i] == b[i] {
-		i++
-	}
-	return i
-}
-
-func (p *exporter) qualifiedName(obj types.Object) {
-	p.string(obj.Name())
-	p.pkg(obj.Pkg(), false)
-}
-
-func (p *exporter) typ(t types.Type) {
-	if t == nil {
-		panic(internalError("nil type"))
-	}
-
-	// Possible optimization: Anonymous pointer types *T where
-	// T is a named type are common. We could canonicalize all
-	// such types *T to a single type PT = *T. This would lead
-	// to at most one *T entry in typIndex, and all future *T's
-	// would be encoded as the respective index directly. Would
-	// save 1 byte (pointerTag) per *T and reduce the typIndex
-	// size (at the cost of a canonicalization map). We can do
-	// this later, without encoding format change.
-
-	// if we saw the type before, write its index (>= 0)
-	if i, ok := p.typIndex[t]; ok {
-		p.index('T', i)
-		return
-	}
-
-	// otherwise, remember the type, write the type tag (< 0) and type data
-	if trackAllTypes {
-		if trace {
-			p.tracef("T%d = {>\n", len(p.typIndex))
-			defer p.tracef("<\n} ")
-		}
-		p.typIndex[t] = len(p.typIndex)
-	}
-
-	switch t := t.(type) {
-	case *types.Named:
-		if !trackAllTypes {
-			// if we don't track all types, track named types now
-			p.typIndex[t] = len(p.typIndex)
-		}
-
-		p.tag(namedTag)
-		p.pos(t.Obj())
-		p.qualifiedName(t.Obj())
-		p.typ(t.Underlying())
-		if !types.IsInterface(t) {
-			p.assocMethods(t)
-		}
-
-	case *types.Array:
-		p.tag(arrayTag)
-		p.int64(t.Len())
-		p.typ(t.Elem())
-
-	case *types.Slice:
-		p.tag(sliceTag)
-		p.typ(t.Elem())
-
-	case *dddSlice:
-		p.tag(dddTag)
-		p.typ(t.elem)
-
-	case *types.Struct:
-		p.tag(structTag)
-		p.fieldList(t)
-
-	case *types.Pointer:
-		p.tag(pointerTag)
-		p.typ(t.Elem())
-
-	case *types.Signature:
-		p.tag(signatureTag)
-		p.paramList(t.Params(), t.Variadic())
-		p.paramList(t.Results(), false)
-
-	case *types.Interface:
-		p.tag(interfaceTag)
-		p.iface(t)
-
-	case *types.Map:
-		p.tag(mapTag)
-		p.typ(t.Key())
-		p.typ(t.Elem())
-
-	case *types.Chan:
-		p.tag(chanTag)
-		p.int(int(3 - t.Dir())) // hack
-		p.typ(t.Elem())
-
-	default:
-		panic(internalErrorf("unexpected type %T: %s", t, t))
-	}
-}
-
-func (p *exporter) assocMethods(named *types.Named) {
-	// Sort methods (for determinism).
-	var methods []*types.Func
-	for i := 0; i < named.NumMethods(); i++ {
-		methods = append(methods, named.Method(i))
-	}
-	sort.Sort(methodsByName(methods))
-
-	p.int(len(methods))
-
-	if trace && methods != nil {
-		p.tracef("associated methods {>\n")
-	}
-
-	for i, m := range methods {
-		if trace && i > 0 {
-			p.tracef("\n")
-		}
-
-		p.pos(m)
-		name := m.Name()
-		p.string(name)
-		if !exported(name) {
-			p.pkg(m.Pkg(), false)
-		}
-
-		sig := m.Type().(*types.Signature)
-		p.paramList(types.NewTuple(sig.Recv()), false)
-		p.paramList(sig.Params(), sig.Variadic())
-		p.paramList(sig.Results(), false)
-		p.int(0) // dummy value for go:nointerface pragma - ignored by importer
-	}
-
-	if trace && methods != nil {
-		p.tracef("<\n} ")
-	}
-}
-
-type methodsByName []*types.Func
-
-func (x methodsByName) Len() int           { return len(x) }
-func (x methodsByName) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }
-func (x methodsByName) Less(i, j int) bool { return x[i].Name() < x[j].Name() }
-
-func (p *exporter) fieldList(t *types.Struct) {
-	if trace && t.NumFields() > 0 {
-		p.tracef("fields {>\n")
-		defer p.tracef("<\n} ")
-	}
-
-	p.int(t.NumFields())
-	for i := 0; i < t.NumFields(); i++ {
-		if trace && i > 0 {
-			p.tracef("\n")
-		}
-		p.field(t.Field(i))
-		p.string(t.Tag(i))
-	}
-}
-
-func (p *exporter) field(f *types.Var) {
-	if !f.IsField() {
-		panic(internalError("field expected"))
-	}
-
-	p.pos(f)
-	p.fieldName(f)
-	p.typ(f.Type())
-}
-
-func (p *exporter) iface(t *types.Interface) {
-	// TODO(gri): enable importer to load embedded interfaces,
-	// then emit Embeddeds and ExplicitMethods separately here.
-	p.int(0)
-
-	n := t.NumMethods()
-	if trace && n > 0 {
-		p.tracef("methods {>\n")
-		defer p.tracef("<\n} ")
-	}
-	p.int(n)
-	for i := 0; i < n; i++ {
-		if trace && i > 0 {
-			p.tracef("\n")
-		}
-		p.method(t.Method(i))
-	}
-}
-
-func (p *exporter) method(m *types.Func) {
-	sig := m.Type().(*types.Signature)
-	if sig.Recv() == nil {
-		panic(internalError("method expected"))
-	}
-
-	p.pos(m)
-	p.string(m.Name())
-	if m.Name() != "_" && !token.IsExported(m.Name()) {
-		p.pkg(m.Pkg(), false)
-	}
-
-	// interface method; no need to encode receiver.
-	p.paramList(sig.Params(), sig.Variadic())
-	p.paramList(sig.Results(), false)
-}
-
-func (p *exporter) fieldName(f *types.Var) {
-	name := f.Name()
-
-	if f.Anonymous() {
-		// anonymous field - we distinguish between 3 cases:
-		// 1) field name matches base type name and is exported
-		// 2) field name matches base type name and is not exported
-		// 3) field name doesn't match base type name (alias name)
-		bname := basetypeName(f.Type())
-		if name == bname {
-			if token.IsExported(name) {
-				name = "" // 1) we don't need to know the field name or package
-			} else {
-				name = "?" // 2) use unexported name "?" to force package export
-			}
-		} else {
-			// 3) indicate alias and export name as is
-			// (this requires an extra "@" but this is a rare case)
-			p.string("@")
-		}
-	}
-
-	p.string(name)
-	if name != "" && !token.IsExported(name) {
-		p.pkg(f.Pkg(), false)
-	}
-}
-
-func basetypeName(typ types.Type) string {
-	switch typ := deref(typ).(type) {
-	case *types.Basic:
-		return typ.Name()
-	case *types.Named:
-		return typ.Obj().Name()
-	default:
-		return "" // unnamed type
-	}
-}
-
-func (p *exporter) paramList(params *types.Tuple, variadic bool) {
-	// use negative length to indicate unnamed parameters
-	// (look at the first parameter only since either all
-	// names are present or all are absent)
-	n := params.Len()
-	if n > 0 && params.At(0).Name() == "" {
-		n = -n
-	}
-	p.int(n)
-	for i := 0; i < params.Len(); i++ {
-		q := params.At(i)
-		t := q.Type()
-		if variadic && i == params.Len()-1 {
-			t = &dddSlice{t.(*types.Slice).Elem()}
-		}
-		p.typ(t)
-		if n > 0 {
-			name := q.Name()
-			p.string(name)
-			if name != "_" {
-				p.pkg(q.Pkg(), false)
-			}
-		}
-		p.string("") // no compiler-specific info
-	}
-}
-
-func (p *exporter) value(x constant.Value) {
-	if trace {
-		p.tracef("= ")
-	}
-
-	switch x.Kind() {
-	case constant.Bool:
-		tag := falseTag
-		if constant.BoolVal(x) {
-			tag = trueTag
-		}
-		p.tag(tag)
-
-	case constant.Int:
-		if v, exact := constant.Int64Val(x); exact {
-			// common case: x fits into an int64 - use compact encoding
-			p.tag(int64Tag)
-			p.int64(v)
-			return
-		}
-		// uncommon case: large x - use float encoding
-		// (powers of 2 will be encoded efficiently with exponent)
-		p.tag(floatTag)
-		p.float(constant.ToFloat(x))
-
-	case constant.Float:
-		p.tag(floatTag)
-		p.float(x)
-
-	case constant.Complex:
-		p.tag(complexTag)
-		p.float(constant.Real(x))
-		p.float(constant.Imag(x))
-
-	case constant.String:
-		p.tag(stringTag)
-		p.string(constant.StringVal(x))
-
-	case constant.Unknown:
-		// package contains type errors
-		p.tag(unknownTag)
-
-	default:
-		panic(internalErrorf("unexpected value %v (%T)", x, x))
-	}
-}
-
-func (p *exporter) float(x constant.Value) {
-	if x.Kind() != constant.Float {
-		panic(internalErrorf("unexpected constant %v, want float", x))
-	}
-	// extract sign (there is no -0)
-	sign := constant.Sign(x)
-	if sign == 0 {
-		// x == 0
-		p.int(0)
-		return
-	}
-	// x != 0
-
-	var f big.Float
-	if v, exact := constant.Float64Val(x); exact {
-		// float64
-		f.SetFloat64(v)
-	} else if num, denom := constant.Num(x), constant.Denom(x); num.Kind() == constant.Int {
-		// TODO(gri): add big.Rat accessor to constant.Value.
-		r := valueToRat(num)
-		f.SetRat(r.Quo(r, valueToRat(denom)))
-	} else {
-		// Value too large to represent as a fraction => inaccessible.
-		// TODO(gri): add big.Float accessor to constant.Value.
-		f.SetFloat64(math.MaxFloat64) // FIXME
-	}
-
-	// extract exponent such that 0.5 <= m < 1.0
-	var m big.Float
-	exp := f.MantExp(&m)
-
-	// extract mantissa as *big.Int
-	// - set exponent large enough so mant satisfies mant.IsInt()
-	// - get *big.Int from mant
-	m.SetMantExp(&m, int(m.MinPrec()))
-	mant, acc := m.Int(nil)
-	if acc != big.Exact {
-		panic(internalError("internal error"))
-	}
-
-	p.int(sign)
-	p.int(exp)
-	p.string(string(mant.Bytes()))
-}
-
-func valueToRat(x constant.Value) *big.Rat {
-	// Convert little-endian to big-endian.
-	// I can't believe this is necessary.
-	bytes := constant.Bytes(x)
-	for i := 0; i < len(bytes)/2; i++ {
-		bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
-	}
-	return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
-}
-
-func (p *exporter) bool(b bool) bool {
-	if trace {
-		p.tracef("[")
-		defer p.tracef("= %v] ", b)
-	}
-
-	x := 0
-	if b {
-		x = 1
-	}
-	p.int(x)
-	return b
-}
-
-// ----------------------------------------------------------------------------
-// Low-level encoders
-
-func (p *exporter) index(marker byte, index int) {
-	if index < 0 {
-		panic(internalError("invalid index < 0"))
-	}
-	if debugFormat {
-		p.marker('t')
-	}
-	if trace {
-		p.tracef("%c%d ", marker, index)
-	}
-	p.rawInt64(int64(index))
-}
-
-func (p *exporter) tag(tag int) {
-	if tag >= 0 {
-		panic(internalError("invalid tag >= 0"))
-	}
-	if debugFormat {
-		p.marker('t')
-	}
-	if trace {
-		p.tracef("%s ", tagString[-tag])
-	}
-	p.rawInt64(int64(tag))
-}
-
-func (p *exporter) int(x int) {
-	p.int64(int64(x))
-}
-
-func (p *exporter) int64(x int64) {
-	if debugFormat {
-		p.marker('i')
-	}
-	if trace {
-		p.tracef("%d ", x)
-	}
-	p.rawInt64(x)
-}
-
-func (p *exporter) string(s string) {
-	if debugFormat {
-		p.marker('s')
-	}
-	if trace {
-		p.tracef("%q ", s)
-	}
-	// if we saw the string before, write its index (>= 0)
-	// (the empty string is mapped to 0)
-	if i, ok := p.strIndex[s]; ok {
-		p.rawInt64(int64(i))
-		return
-	}
-	// otherwise, remember string and write its negative length and bytes
-	p.strIndex[s] = len(p.strIndex)
-	p.rawInt64(-int64(len(s)))
-	for i := 0; i < len(s); i++ {
-		p.rawByte(s[i])
-	}
-}
-
-// marker emits a marker byte and position information which makes
-// it easy for a reader to detect if it is "out of sync". Used for
-// debugFormat format only.
-func (p *exporter) marker(m byte) {
-	p.rawByte(m)
-	// Enable this for help tracking down the location
-	// of an incorrect marker when running in debugFormat.
-	if false && trace {
-		p.tracef("#%d ", p.written)
-	}
-	p.rawInt64(int64(p.written))
-}
-
-// rawInt64 should only be used by low-level encoders.
-func (p *exporter) rawInt64(x int64) {
-	var tmp [binary.MaxVarintLen64]byte
-	n := binary.PutVarint(tmp[:], x)
-	for i := 0; i < n; i++ {
-		p.rawByte(tmp[i])
-	}
-}
-
-// rawStringln should only be used to emit the initial version string.
-func (p *exporter) rawStringln(s string) {
-	for i := 0; i < len(s); i++ {
-		p.rawByte(s[i])
-	}
-	p.rawByte('\n')
-}
-
-// rawByte is the bottleneck interface to write to p.out.
-// rawByte escapes b as follows (any encoding does that
-// hides '$'):
-//
-//	'$'  => '|' 'S'
-//	'|'  => '|' '|'
-//
-// Necessary so other tools can find the end of the
-// export data by searching for "$$".
-// rawByte should only be used by low-level encoders.
-func (p *exporter) rawByte(b byte) {
-	switch b {
-	case '$':
-		// write '$' as '|' 'S'
-		b = 'S'
-		fallthrough
-	case '|':
-		// write '|' as '|' '|'
-		p.out.WriteByte('|')
-		p.written++
-	}
-	p.out.WriteByte(b)
-	p.written++
-}
-
-// tracef is like fmt.Printf but it rewrites the format string
-// to take care of indentation.
-func (p *exporter) tracef(format string, args ...interface{}) {
-	if strings.ContainsAny(format, "<>\n") {
-		var buf bytes.Buffer
-		for i := 0; i < len(format); i++ {
-			// no need to deal with runes
-			ch := format[i]
-			switch ch {
-			case '>':
-				p.indent++
-				continue
-			case '<':
-				p.indent--
-				continue
-			}
-			buf.WriteByte(ch)
-			if ch == '\n' {
-				for j := p.indent; j > 0; j-- {
-					buf.WriteString(".  ")
-				}
-			}
-		}
-		format = buf.String()
-	}
-	fmt.Printf(format, args...)
-}
-
-// Debugging support.
-// (tagString is only used when tracing is enabled)
-var tagString = [...]string{
-	// Packages
-	-packageTag: "package",
-
-	// Types
-	-namedTag:     "named type",
-	-arrayTag:     "array",
-	-sliceTag:     "slice",
-	-dddTag:       "ddd",
-	-structTag:    "struct",
-	-pointerTag:   "pointer",
-	-signatureTag: "signature",
-	-interfaceTag: "interface",
-	-mapTag:       "map",
-	-chanTag:      "chan",
-
-	// Values
-	-falseTag:    "false",
-	-trueTag:     "true",
-	-int64Tag:    "int64",
-	-floatTag:    "float",
-	-fractionTag: "fraction",
-	-complexTag:  "complex",
-	-stringTag:   "string",
-	-unknownTag:  "unknown",
-
-	// Type aliases
-	-aliasTag: "alias",
-}
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/bimport.go b/vendor/golang.org/x/tools/internal/gcimporter/bimport.go
index b85de0147..d98b0db2a 100644
--- a/vendor/golang.org/x/tools/internal/gcimporter/bimport.go
+++ b/vendor/golang.org/x/tools/internal/gcimporter/bimport.go
@@ -2,340 +2,24 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// This file is a copy of $GOROOT/src/go/internal/gcimporter/bimport.go.
+// This file contains the remaining vestiges of
+// $GOROOT/src/go/internal/gcimporter/bimport.go.
 
 package gcimporter
 
 import (
-	"encoding/binary"
 	"fmt"
-	"go/constant"
 	"go/token"
 	"go/types"
-	"sort"
-	"strconv"
-	"strings"
 	"sync"
-	"unicode"
-	"unicode/utf8"
 )
 
-type importer struct {
-	imports    map[string]*types.Package
-	data       []byte
-	importpath string
-	buf        []byte // for reading strings
-	version    int    // export format version
-
-	// object lists
-	strList       []string           // in order of appearance
-	pathList      []string           // in order of appearance
-	pkgList       []*types.Package   // in order of appearance
-	typList       []types.Type       // in order of appearance
-	interfaceList []*types.Interface // for delayed completion only
-	trackAllTypes bool
-
-	// position encoding
-	posInfoFormat bool
-	prevFile      string
-	prevLine      int
-	fake          fakeFileSet
-
-	// debugging support
-	debugFormat bool
-	read        int // bytes read
-}
-
-// BImportData imports a package from the serialized package data
-// and returns the number of bytes consumed and a reference to the package.
-// If the export data version is not recognized or the format is otherwise
-// compromised, an error is returned.
-func BImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
-	// catch panics and return them as errors
-	const currentVersion = 6
-	version := -1 // unknown version
-	defer func() {
-		if e := recover(); e != nil {
-			// Return a (possibly nil or incomplete) package unchanged (see #16088).
-			if version > currentVersion {
-				err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
-			} else {
-				err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
-			}
-		}
-	}()
-
-	p := importer{
-		imports:    imports,
-		data:       data,
-		importpath: path,
-		version:    version,
-		strList:    []string{""}, // empty string is mapped to 0
-		pathList:   []string{""}, // empty string is mapped to 0
-		fake: fakeFileSet{
-			fset:  fset,
-			files: make(map[string]*fileInfo),
-		},
-	}
-	defer p.fake.setLines() // set lines for files in fset
-
-	// read version info
-	var versionstr string
-	if b := p.rawByte(); b == 'c' || b == 'd' {
-		// Go1.7 encoding; first byte encodes low-level
-		// encoding format (compact vs debug).
-		// For backward-compatibility only (avoid problems with
-		// old installed packages). Newly compiled packages use
-		// the extensible format string.
-		// TODO(gri) Remove this support eventually; after Go1.8.
-		if b == 'd' {
-			p.debugFormat = true
-		}
-		p.trackAllTypes = p.rawByte() == 'a'
-		p.posInfoFormat = p.int() != 0
-		versionstr = p.string()
-		if versionstr == "v1" {
-			version = 0
-		}
-	} else {
-		// Go1.8 extensible encoding
-		// read version string and extract version number (ignore anything after the version number)
-		versionstr = p.rawStringln(b)
-		if s := strings.SplitN(versionstr, " ", 3); len(s) >= 2 && s[0] == "version" {
-			if v, err := strconv.Atoi(s[1]); err == nil && v > 0 {
-				version = v
-			}
-		}
-	}
-	p.version = version
-
-	// read version specific flags - extend as necessary
-	switch p.version {
-	// case currentVersion:
-	// 	...
-	//	fallthrough
-	case currentVersion, 5, 4, 3, 2, 1:
-		p.debugFormat = p.rawStringln(p.rawByte()) == "debug"
-		p.trackAllTypes = p.int() != 0
-		p.posInfoFormat = p.int() != 0
-	case 0:
-		// Go1.7 encoding format - nothing to do here
-	default:
-		errorf("unknown bexport format version %d (%q)", p.version, versionstr)
-	}
-
-	// --- generic export data ---
-
-	// populate typList with predeclared "known" types
-	p.typList = append(p.typList, predeclared()...)
-
-	// read package data
-	pkg = p.pkg()
-
-	// read objects of phase 1 only (see cmd/compile/internal/gc/bexport.go)
-	objcount := 0
-	for {
-		tag := p.tagOrIndex()
-		if tag == endTag {
-			break
-		}
-		p.obj(tag)
-		objcount++
-	}
-
-	// self-verification
-	if count := p.int(); count != objcount {
-		errorf("got %d objects; want %d", objcount, count)
-	}
-
-	// ignore compiler-specific import data
-
-	// complete interfaces
-	// TODO(gri) re-investigate if we still need to do this in a delayed fashion
-	for _, typ := range p.interfaceList {
-		typ.Complete()
-	}
-
-	// record all referenced packages as imports
-	list := append(([]*types.Package)(nil), p.pkgList[1:]...)
-	sort.Sort(byPath(list))
-	pkg.SetImports(list)
-
-	// package was imported completely and without errors
-	pkg.MarkComplete()
-
-	return p.read, pkg, nil
-}
-
 func errorf(format string, args ...interface{}) {
 	panic(fmt.Sprintf(format, args...))
 }
 
-func (p *importer) pkg() *types.Package {
-	// if the package was seen before, i is its index (>= 0)
-	i := p.tagOrIndex()
-	if i >= 0 {
-		return p.pkgList[i]
-	}
-
-	// otherwise, i is the package tag (< 0)
-	if i != packageTag {
-		errorf("unexpected package tag %d version %d", i, p.version)
-	}
-
-	// read package data
-	name := p.string()
-	var path string
-	if p.version >= 5 {
-		path = p.path()
-	} else {
-		path = p.string()
-	}
-	if p.version >= 6 {
-		p.int() // package height; unused by go/types
-	}
-
-	// we should never see an empty package name
-	if name == "" {
-		errorf("empty package name in import")
-	}
-
-	// an empty path denotes the package we are currently importing;
-	// it must be the first package we see
-	if (path == "") != (len(p.pkgList) == 0) {
-		errorf("package path %q for pkg index %d", path, len(p.pkgList))
-	}
-
-	// if the package was imported before, use that one; otherwise create a new one
-	if path == "" {
-		path = p.importpath
-	}
-	pkg := p.imports[path]
-	if pkg == nil {
-		pkg = types.NewPackage(path, name)
-		p.imports[path] = pkg
-	} else if pkg.Name() != name {
-		errorf("conflicting names %s and %s for package %q", pkg.Name(), name, path)
-	}
-	p.pkgList = append(p.pkgList, pkg)
-
-	return pkg
-}
-
-// objTag returns the tag value for each object kind.
-func objTag(obj types.Object) int {
-	switch obj.(type) {
-	case *types.Const:
-		return constTag
-	case *types.TypeName:
-		return typeTag
-	case *types.Var:
-		return varTag
-	case *types.Func:
-		return funcTag
-	default:
-		errorf("unexpected object: %v (%T)", obj, obj) // panics
-		panic("unreachable")
-	}
-}
-
-func sameObj(a, b types.Object) bool {
-	// Because unnamed types are not canonicalized, we cannot simply compare types for
-	// (pointer) identity.
-	// Ideally we'd check equality of constant values as well, but this is good enough.
-	return objTag(a) == objTag(b) && types.Identical(a.Type(), b.Type())
-}
-
-func (p *importer) declare(obj types.Object) {
-	pkg := obj.Pkg()
-	if alt := pkg.Scope().Insert(obj); alt != nil {
-		// This can only trigger if we import a (non-type) object a second time.
-		// Excluding type aliases, this cannot happen because 1) we only import a package
-		// once; and b) we ignore compiler-specific export data which may contain
-		// functions whose inlined function bodies refer to other functions that
-		// were already imported.
-		// However, type aliases require reexporting the original type, so we need
-		// to allow it (see also the comment in cmd/compile/internal/gc/bimport.go,
-		// method importer.obj, switch case importing functions).
-		// TODO(gri) review/update this comment once the gc compiler handles type aliases.
-		if !sameObj(obj, alt) {
-			errorf("inconsistent import:\n\t%v\npreviously imported as:\n\t%v\n", obj, alt)
-		}
-	}
-}
-
-func (p *importer) obj(tag int) {
-	switch tag {
-	case constTag:
-		pos := p.pos()
-		pkg, name := p.qualifiedName()
-		typ := p.typ(nil, nil)
-		val := p.value()
-		p.declare(types.NewConst(pos, pkg, name, typ, val))
-
-	case aliasTag:
-		// TODO(gri) verify type alias hookup is correct
-		pos := p.pos()
-		pkg, name := p.qualifiedName()
-		typ := p.typ(nil, nil)
-		p.declare(types.NewTypeName(pos, pkg, name, typ))
-
-	case typeTag:
-		p.typ(nil, nil)
-
-	case varTag:
-		pos := p.pos()
-		pkg, name := p.qualifiedName()
-		typ := p.typ(nil, nil)
-		p.declare(types.NewVar(pos, pkg, name, typ))
-
-	case funcTag:
-		pos := p.pos()
-		pkg, name := p.qualifiedName()
-		params, isddd := p.paramList()
-		result, _ := p.paramList()
-		sig := types.NewSignature(nil, params, result, isddd)
-		p.declare(types.NewFunc(pos, pkg, name, sig))
-
-	default:
-		errorf("unexpected object tag %d", tag)
-	}
-}
-
 const deltaNewFile = -64 // see cmd/compile/internal/gc/bexport.go
 
-func (p *importer) pos() token.Pos {
-	if !p.posInfoFormat {
-		return token.NoPos
-	}
-
-	file := p.prevFile
-	line := p.prevLine
-	delta := p.int()
-	line += delta
-	if p.version >= 5 {
-		if delta == deltaNewFile {
-			if n := p.int(); n >= 0 {
-				// file changed
-				file = p.path()
-				line = n
-			}
-		}
-	} else {
-		if delta == 0 {
-			if n := p.int(); n >= 0 {
-				// file changed
-				file = p.prevFile[:n] + p.string()
-				line = p.int()
-			}
-		}
-	}
-	p.prevFile = file
-	p.prevLine = line
-
-	return p.fake.pos(file, line, 0)
-}
-
 // Synthesize a token.Pos
 type fakeFileSet struct {
 	fset  *token.FileSet
@@ -389,205 +73,6 @@ var (
 	fakeLinesOnce sync.Once
 )
 
-func (p *importer) qualifiedName() (pkg *types.Package, name string) {
-	name = p.string()
-	pkg = p.pkg()
-	return
-}
-
-func (p *importer) record(t types.Type) {
-	p.typList = append(p.typList, t)
-}
-
-// A dddSlice is a types.Type representing ...T parameters.
-// It only appears for parameter types and does not escape
-// the importer.
-type dddSlice struct {
-	elem types.Type
-}
-
-func (t *dddSlice) Underlying() types.Type { return t }
-func (t *dddSlice) String() string         { return "..." + t.elem.String() }
-
-// parent is the package which declared the type; parent == nil means
-// the package currently imported. The parent package is needed for
-// exported struct fields and interface methods which don't contain
-// explicit package information in the export data.
-//
-// A non-nil tname is used as the "owner" of the result type; i.e.,
-// the result type is the underlying type of tname. tname is used
-// to give interface methods a named receiver type where possible.
-func (p *importer) typ(parent *types.Package, tname *types.Named) types.Type {
-	// if the type was seen before, i is its index (>= 0)
-	i := p.tagOrIndex()
-	if i >= 0 {
-		return p.typList[i]
-	}
-
-	// otherwise, i is the type tag (< 0)
-	switch i {
-	case namedTag:
-		// read type object
-		pos := p.pos()
-		parent, name := p.qualifiedName()
-		scope := parent.Scope()
-		obj := scope.Lookup(name)
-
-		// if the object doesn't exist yet, create and insert it
-		if obj == nil {
-			obj = types.NewTypeName(pos, parent, name, nil)
-			scope.Insert(obj)
-		}
-
-		if _, ok := obj.(*types.TypeName); !ok {
-			errorf("pkg = %s, name = %s => %s", parent, name, obj)
-		}
-
-		// associate new named type with obj if it doesn't exist yet
-		t0 := types.NewNamed(obj.(*types.TypeName), nil, nil)
-
-		// but record the existing type, if any
-		tname := obj.Type().(*types.Named) // tname is either t0 or the existing type
-		p.record(tname)
-
-		// read underlying type
-		t0.SetUnderlying(p.typ(parent, t0))
-
-		// interfaces don't have associated methods
-		if types.IsInterface(t0) {
-			return tname
-		}
-
-		// read associated methods
-		for i := p.int(); i > 0; i-- {
-			// TODO(gri) replace this with something closer to fieldName
-			pos := p.pos()
-			name := p.string()
-			if !exported(name) {
-				p.pkg()
-			}
-
-			recv, _ := p.paramList() // TODO(gri) do we need a full param list for the receiver?
-			params, isddd := p.paramList()
-			result, _ := p.paramList()
-			p.int() // go:nointerface pragma - discarded
-
-			sig := types.NewSignature(recv.At(0), params, result, isddd)
-			t0.AddMethod(types.NewFunc(pos, parent, name, sig))
-		}
-
-		return tname
-
-	case arrayTag:
-		t := new(types.Array)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		n := p.int64()
-		*t = *types.NewArray(p.typ(parent, nil), n)
-		return t
-
-	case sliceTag:
-		t := new(types.Slice)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		*t = *types.NewSlice(p.typ(parent, nil))
-		return t
-
-	case dddTag:
-		t := new(dddSlice)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		t.elem = p.typ(parent, nil)
-		return t
-
-	case structTag:
-		t := new(types.Struct)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		*t = *types.NewStruct(p.fieldList(parent))
-		return t
-
-	case pointerTag:
-		t := new(types.Pointer)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		*t = *types.NewPointer(p.typ(parent, nil))
-		return t
-
-	case signatureTag:
-		t := new(types.Signature)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		params, isddd := p.paramList()
-		result, _ := p.paramList()
-		*t = *types.NewSignature(nil, params, result, isddd)
-		return t
-
-	case interfaceTag:
-		// Create a dummy entry in the type list. This is safe because we
-		// cannot expect the interface type to appear in a cycle, as any
-		// such cycle must contain a named type which would have been
-		// first defined earlier.
-		// TODO(gri) Is this still true now that we have type aliases?
-		// See issue #23225.
-		n := len(p.typList)
-		if p.trackAllTypes {
-			p.record(nil)
-		}
-
-		var embeddeds []types.Type
-		for n := p.int(); n > 0; n-- {
-			p.pos()
-			embeddeds = append(embeddeds, p.typ(parent, nil))
-		}
-
-		t := newInterface(p.methodList(parent, tname), embeddeds)
-		p.interfaceList = append(p.interfaceList, t)
-		if p.trackAllTypes {
-			p.typList[n] = t
-		}
-		return t
-
-	case mapTag:
-		t := new(types.Map)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		key := p.typ(parent, nil)
-		val := p.typ(parent, nil)
-		*t = *types.NewMap(key, val)
-		return t
-
-	case chanTag:
-		t := new(types.Chan)
-		if p.trackAllTypes {
-			p.record(t)
-		}
-
-		dir := chanDir(p.int())
-		val := p.typ(parent, nil)
-		*t = *types.NewChan(dir, val)
-		return t
-
-	default:
-		errorf("unexpected type tag %d", i) // panics
-		panic("unreachable")
-	}
-}
-
 func chanDir(d int) types.ChanDir {
 	// tag values must match the constants in cmd/compile/internal/gc/go.go
 	switch d {
@@ -603,394 +88,6 @@ func chanDir(d int) types.ChanDir {
 	}
 }
 
-func (p *importer) fieldList(parent *types.Package) (fields []*types.Var, tags []string) {
-	if n := p.int(); n > 0 {
-		fields = make([]*types.Var, n)
-		tags = make([]string, n)
-		for i := range fields {
-			fields[i], tags[i] = p.field(parent)
-		}
-	}
-	return
-}
-
-func (p *importer) field(parent *types.Package) (*types.Var, string) {
-	pos := p.pos()
-	pkg, name, alias := p.fieldName(parent)
-	typ := p.typ(parent, nil)
-	tag := p.string()
-
-	anonymous := false
-	if name == "" {
-		// anonymous field - typ must be T or *T and T must be a type name
-		switch typ := deref(typ).(type) {
-		case *types.Basic: // basic types are named types
-			pkg = nil // // objects defined in Universe scope have no package
-			name = typ.Name()
-		case *types.Named:
-			name = typ.Obj().Name()
-		default:
-			errorf("named base type expected")
-		}
-		anonymous = true
-	} else if alias {
-		// anonymous field: we have an explicit name because it's an alias
-		anonymous = true
-	}
-
-	return types.NewField(pos, pkg, name, typ, anonymous), tag
-}
-
-func (p *importer) methodList(parent *types.Package, baseType *types.Named) (methods []*types.Func) {
-	if n := p.int(); n > 0 {
-		methods = make([]*types.Func, n)
-		for i := range methods {
-			methods[i] = p.method(parent, baseType)
-		}
-	}
-	return
-}
-
-func (p *importer) method(parent *types.Package, baseType *types.Named) *types.Func {
-	pos := p.pos()
-	pkg, name, _ := p.fieldName(parent)
-	// If we don't have a baseType, use a nil receiver.
-	// A receiver using the actual interface type (which
-	// we don't know yet) will be filled in when we call
-	// types.Interface.Complete.
-	var recv *types.Var
-	if baseType != nil {
-		recv = types.NewVar(token.NoPos, parent, "", baseType)
-	}
-	params, isddd := p.paramList()
-	result, _ := p.paramList()
-	sig := types.NewSignature(recv, params, result, isddd)
-	return types.NewFunc(pos, pkg, name, sig)
-}
-
-func (p *importer) fieldName(parent *types.Package) (pkg *types.Package, name string, alias bool) {
-	name = p.string()
-	pkg = parent
-	if pkg == nil {
-		// use the imported package instead
-		pkg = p.pkgList[0]
-	}
-	if p.version == 0 && name == "_" {
-		// version 0 didn't export a package for _ fields
-		return
-	}
-	switch name {
-	case "":
-		// 1) field name matches base type name and is exported: nothing to do
-	case "?":
-		// 2) field name matches base type name and is not exported: need package
-		name = ""
-		pkg = p.pkg()
-	case "@":
-		// 3) field name doesn't match type name (alias)
-		name = p.string()
-		alias = true
-		fallthrough
-	default:
-		if !exported(name) {
-			pkg = p.pkg()
-		}
-	}
-	return
-}
-
-func (p *importer) paramList() (*types.Tuple, bool) {
-	n := p.int()
-	if n == 0 {
-		return nil, false
-	}
-	// negative length indicates unnamed parameters
-	named := true
-	if n < 0 {
-		n = -n
-		named = false
-	}
-	// n > 0
-	params := make([]*types.Var, n)
-	isddd := false
-	for i := range params {
-		params[i], isddd = p.param(named)
-	}
-	return types.NewTuple(params...), isddd
-}
-
-func (p *importer) param(named bool) (*types.Var, bool) {
-	t := p.typ(nil, nil)
-	td, isddd := t.(*dddSlice)
-	if isddd {
-		t = types.NewSlice(td.elem)
-	}
-
-	var pkg *types.Package
-	var name string
-	if named {
-		name = p.string()
-		if name == "" {
-			errorf("expected named parameter")
-		}
-		if name != "_" {
-			pkg = p.pkg()
-		}
-		if i := strings.Index(name, "·"); i > 0 {
-			name = name[:i] // cut off gc-specific parameter numbering
-		}
-	}
-
-	// read and discard compiler-specific info
-	p.string()
-
-	return types.NewVar(token.NoPos, pkg, name, t), isddd
-}
-
-func exported(name string) bool {
-	ch, _ := utf8.DecodeRuneInString(name)
-	return unicode.IsUpper(ch)
-}
-
-func (p *importer) value() constant.Value {
-	switch tag := p.tagOrIndex(); tag {
-	case falseTag:
-		return constant.MakeBool(false)
-	case trueTag:
-		return constant.MakeBool(true)
-	case int64Tag:
-		return constant.MakeInt64(p.int64())
-	case floatTag:
-		return p.float()
-	case complexTag:
-		re := p.float()
-		im := p.float()
-		return constant.BinaryOp(re, token.ADD, constant.MakeImag(im))
-	case stringTag:
-		return constant.MakeString(p.string())
-	case unknownTag:
-		return constant.MakeUnknown()
-	default:
-		errorf("unexpected value tag %d", tag) // panics
-		panic("unreachable")
-	}
-}
-
-func (p *importer) float() constant.Value {
-	sign := p.int()
-	if sign == 0 {
-		return constant.MakeInt64(0)
-	}
-
-	exp := p.int()
-	mant := []byte(p.string()) // big endian
-
-	// remove leading 0's if any
-	for len(mant) > 0 && mant[0] == 0 {
-		mant = mant[1:]
-	}
-
-	// convert to little endian
-	// TODO(gri) go/constant should have a more direct conversion function
-	//           (e.g., once it supports a big.Float based implementation)
-	for i, j := 0, len(mant)-1; i < j; i, j = i+1, j-1 {
-		mant[i], mant[j] = mant[j], mant[i]
-	}
-
-	// adjust exponent (constant.MakeFromBytes creates an integer value,
-	// but mant represents the mantissa bits such that 0.5 <= mant < 1.0)
-	exp -= len(mant) << 3
-	if len(mant) > 0 {
-		for msd := mant[len(mant)-1]; msd&0x80 == 0; msd <<= 1 {
-			exp++
-		}
-	}
-
-	x := constant.MakeFromBytes(mant)
-	switch {
-	case exp < 0:
-		d := constant.Shift(constant.MakeInt64(1), token.SHL, uint(-exp))
-		x = constant.BinaryOp(x, token.QUO, d)
-	case exp > 0:
-		x = constant.Shift(x, token.SHL, uint(exp))
-	}
-
-	if sign < 0 {
-		x = constant.UnaryOp(token.SUB, x, 0)
-	}
-	return x
-}
-
-// ----------------------------------------------------------------------------
-// Low-level decoders
-
-func (p *importer) tagOrIndex() int {
-	if p.debugFormat {
-		p.marker('t')
-	}
-
-	return int(p.rawInt64())
-}
-
-func (p *importer) int() int {
-	x := p.int64()
-	if int64(int(x)) != x {
-		errorf("exported integer too large")
-	}
-	return int(x)
-}
-
-func (p *importer) int64() int64 {
-	if p.debugFormat {
-		p.marker('i')
-	}
-
-	return p.rawInt64()
-}
-
-func (p *importer) path() string {
-	if p.debugFormat {
-		p.marker('p')
-	}
-	// if the path was seen before, i is its index (>= 0)
-	// (the empty string is at index 0)
-	i := p.rawInt64()
-	if i >= 0 {
-		return p.pathList[i]
-	}
-	// otherwise, i is the negative path length (< 0)
-	a := make([]string, -i)
-	for n := range a {
-		a[n] = p.string()
-	}
-	s := strings.Join(a, "/")
-	p.pathList = append(p.pathList, s)
-	return s
-}
-
-func (p *importer) string() string {
-	if p.debugFormat {
-		p.marker('s')
-	}
-	// if the string was seen before, i is its index (>= 0)
-	// (the empty string is at index 0)
-	i := p.rawInt64()
-	if i >= 0 {
-		return p.strList[i]
-	}
-	// otherwise, i is the negative string length (< 0)
-	if n := int(-i); n <= cap(p.buf) {
-		p.buf = p.buf[:n]
-	} else {
-		p.buf = make([]byte, n)
-	}
-	for i := range p.buf {
-		p.buf[i] = p.rawByte()
-	}
-	s := string(p.buf)
-	p.strList = append(p.strList, s)
-	return s
-}
-
-func (p *importer) marker(want byte) {
-	if got := p.rawByte(); got != want {
-		errorf("incorrect marker: got %c; want %c (pos = %d)", got, want, p.read)
-	}
-
-	pos := p.read
-	if n := int(p.rawInt64()); n != pos {
-		errorf("incorrect position: got %d; want %d", n, pos)
-	}
-}
-
-// rawInt64 should only be used by low-level decoders.
-func (p *importer) rawInt64() int64 {
-	i, err := binary.ReadVarint(p)
-	if err != nil {
-		errorf("read error: %v", err)
-	}
-	return i
-}
-
-// rawStringln should only be used to read the initial version string.
-func (p *importer) rawStringln(b byte) string {
-	p.buf = p.buf[:0]
-	for b != '\n' {
-		p.buf = append(p.buf, b)
-		b = p.rawByte()
-	}
-	return string(p.buf)
-}
-
-// needed for binary.ReadVarint in rawInt64
-func (p *importer) ReadByte() (byte, error) {
-	return p.rawByte(), nil
-}
-
-// byte is the bottleneck interface for reading p.data.
-// It unescapes '|' 'S' to '$' and '|' '|' to '|'.
-// rawByte should only be used by low-level decoders.
-func (p *importer) rawByte() byte {
-	b := p.data[0]
-	r := 1
-	if b == '|' {
-		b = p.data[1]
-		r = 2
-		switch b {
-		case 'S':
-			b = '$'
-		case '|':
-			// nothing to do
-		default:
-			errorf("unexpected escape sequence in export data")
-		}
-	}
-	p.data = p.data[r:]
-	p.read += r
-	return b
-
-}
-
-// ----------------------------------------------------------------------------
-// Export format
-
-// Tags. Must be < 0.
-const (
-	// Objects
-	packageTag = -(iota + 1)
-	constTag
-	typeTag
-	varTag
-	funcTag
-	endTag
-
-	// Types
-	namedTag
-	arrayTag
-	sliceTag
-	dddTag
-	structTag
-	pointerTag
-	signatureTag
-	interfaceTag
-	mapTag
-	chanTag
-
-	// Values
-	falseTag
-	trueTag
-	int64Tag
-	floatTag
-	fractionTag // not used by gc
-	complexTag
-	stringTag
-	nilTag     // only used by gc (appears in exported inlined function bodies)
-	unknownTag // not used by gc (only appears in packages with errors)
-
-	// Type aliases
-	aliasTag
-)
-
 var predeclOnce sync.Once
 var predecl []types.Type // initialized lazily
 
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go
index a973dece9..2d078ccb1 100644
--- a/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go
+++ b/vendor/golang.org/x/tools/internal/gcimporter/gcimporter.go
@@ -29,7 +29,6 @@ import (
 	"go/token"
 	"go/types"
 	"io"
-	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -221,7 +220,7 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func
 	switch hdr {
 	case "$$B\n":
 		var data []byte
-		data, err = ioutil.ReadAll(buf)
+		data, err = io.ReadAll(buf)
 		if err != nil {
 			break
 		}
@@ -230,20 +229,17 @@ func Import(packages map[string]*types.Package, path, srcDir string, lookup func
 		// Or, define a new standard go/types/gcexportdata package.
 		fset := token.NewFileSet()
 
-		// The indexed export format starts with an 'i'; the older
-		// binary export format starts with a 'c', 'd', or 'v'
-		// (from "version"). Select appropriate importer.
+		// Select appropriate importer.
 		if len(data) > 0 {
 			switch data[0] {
-			case 'i':
-				_, pkg, err := IImportData(fset, packages, data[1:], id)
-				return pkg, err
+			case 'v', 'c', 'd': // binary, till go1.10
+				return nil, fmt.Errorf("binary (%c) import format is no longer supported", data[0])
 
-			case 'v', 'c', 'd':
-				_, pkg, err := BImportData(fset, packages, data, id)
+			case 'i': // indexed, till go1.19
+				_, pkg, err := IImportData(fset, packages, data[1:], id)
 				return pkg, err
 
-			case 'u':
+			case 'u': // unified, from go1.20
 				_, pkg, err := UImportData(fset, packages, data[1:size], id)
 				return pkg, err
 
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go
index a0dc0b5e2..2ee8c7016 100644
--- a/vendor/golang.org/x/tools/internal/gcimporter/iexport.go
+++ b/vendor/golang.org/x/tools/internal/gcimporter/iexport.go
@@ -22,17 +22,22 @@ import (
 	"strconv"
 	"strings"
 
+	"golang.org/x/tools/go/types/objectpath"
 	"golang.org/x/tools/internal/tokeninternal"
-	"golang.org/x/tools/internal/typeparams"
 )
 
 // IExportShallow encodes "shallow" export data for the specified package.
 //
-// No promises are made about the encoding other than that it can be
-// decoded by the same version of IIExportShallow. If you plan to save
-// export data in the file system, be sure to include a cryptographic
-// digest of the executable in the key to avoid version skew.
-func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
+// No promises are made about the encoding other than that it can be decoded by
+// the same version of IIExportShallow. If you plan to save export data in the
+// file system, be sure to include a cryptographic digest of the executable in
+// the key to avoid version skew.
+//
+// If the provided reportf func is non-nil, it will be used for reporting bugs
+// encountered during export.
+// TODO(rfindley): remove reportf when we are confident enough in the new
+// objectpath encoding.
+func IExportShallow(fset *token.FileSet, pkg *types.Package, reportf ReportFunc) ([]byte, error) {
 	// In principle this operation can only fail if out.Write fails,
 	// but that's impossible for bytes.Buffer---and as a matter of
 	// fact iexportCommon doesn't even check for I/O errors.
@@ -47,19 +52,27 @@ func IExportShallow(fset *token.FileSet, pkg *types.Package) ([]byte, error) {
 // IImportShallow decodes "shallow" types.Package data encoded by
 // IExportShallow in the same executable. This function cannot import data from
 // cmd/compile or gcexportdata.Write.
-func IImportShallow(fset *token.FileSet, getPackage GetPackageFunc, data []byte, path string, insert InsertType) (*types.Package, error) {
+//
+// The importer calls getPackages to obtain package symbols for all
+// packages mentioned in the export data, including the one being
+// decoded.
+//
+// If the provided reportf func is non-nil, it will be used for reporting bugs
+// encountered during import.
+// TODO(rfindley): remove reportf when we are confident enough in the new
+// objectpath encoding.
+func IImportShallow(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, path string, reportf ReportFunc) (*types.Package, error) {
 	const bundle = false
-	pkgs, err := iimportCommon(fset, getPackage, data, bundle, path, insert)
+	const shallow = true
+	pkgs, err := iimportCommon(fset, getPackages, data, bundle, path, shallow, reportf)
 	if err != nil {
 		return nil, err
 	}
 	return pkgs[0], nil
 }
 
-// InsertType is the type of a function that creates a types.TypeName
-// object for a named type and inserts it into the scope of the
-// specified Package.
-type InsertType = func(pkg *types.Package, name string)
+// ReportFunc is the type of a function used to report formatted bugs.
+type ReportFunc = func(string, ...interface{})
 
 // Current bundled export format version. Increase with each format change.
 // 0: initial implementation
@@ -313,8 +326,9 @@ type iexporter struct {
 	out     *bytes.Buffer
 	version int
 
-	shallow  bool           // don't put types from other packages in the index
-	localpkg *types.Package // (nil in bundle mode)
+	shallow    bool                // don't put types from other packages in the index
+	objEncoder *objectpath.Encoder // encodes objects from other packages in shallow mode; lazily allocated
+	localpkg   *types.Package      // (nil in bundle mode)
 
 	// allPkgs tracks all packages that have been referenced by
 	// the export data, so we can ensure to include them in the
@@ -354,6 +368,17 @@ func (p *iexporter) trace(format string, args ...interface{}) {
 	fmt.Printf(strings.Repeat("..", p.indent)+format+"\n", args...)
 }
 
+// objectpathEncoder returns the lazily allocated objectpath.Encoder to use
+// when encoding objects in other packages during shallow export.
+//
+// Using a shared Encoder amortizes some of cost of objectpath search.
+func (p *iexporter) objectpathEncoder() *objectpath.Encoder {
+	if p.objEncoder == nil {
+		p.objEncoder = new(objectpath.Encoder)
+	}
+	return p.objEncoder
+}
+
 // stringOff returns the offset of s within the string section.
 // If not already present, it's added to the end.
 func (p *iexporter) stringOff(s string) uint64 {
@@ -413,7 +438,6 @@ type exportWriter struct {
 	p *iexporter
 
 	data       intWriter
-	currPkg    *types.Package
 	prevFile   string
 	prevLine   int64
 	prevColumn int64
@@ -436,7 +460,6 @@ func (p *iexporter) doDecl(obj types.Object) {
 		}()
 	}
 	w := p.newWriter()
-	w.setPkg(obj.Pkg(), false)
 
 	switch obj := obj.(type) {
 	case *types.Var:
@@ -457,7 +480,7 @@ func (p *iexporter) doDecl(obj types.Object) {
 		}
 
 		// Function.
-		if typeparams.ForSignature(sig).Len() == 0 {
+		if sig.TypeParams().Len() == 0 {
 			w.tag('F')
 		} else {
 			w.tag('G')
@@ -470,7 +493,7 @@ func (p *iexporter) doDecl(obj types.Object) {
 		//
 		// While importing the type parameters, tparamList computes and records
 		// their export name, so that it can be later used when writing the index.
-		if tparams := typeparams.ForSignature(sig); tparams.Len() > 0 {
+		if tparams := sig.TypeParams(); tparams.Len() > 0 {
 			w.tparamList(obj.Name(), tparams, obj.Pkg())
 		}
 		w.signature(sig)
@@ -483,14 +506,14 @@ func (p *iexporter) doDecl(obj types.Object) {
 	case *types.TypeName:
 		t := obj.Type()
 
-		if tparam, ok := t.(*typeparams.TypeParam); ok {
+		if tparam, ok := t.(*types.TypeParam); ok {
 			w.tag('P')
 			w.pos(obj.Pos())
 			constraint := tparam.Constraint()
 			if p.version >= iexportVersionGo1_18 {
 				implicit := false
 				if iface, _ := constraint.(*types.Interface); iface != nil {
-					implicit = typeparams.IsImplicit(iface)
+					implicit = iface.IsImplicit()
 				}
 				w.bool(implicit)
 			}
@@ -511,17 +534,17 @@ func (p *iexporter) doDecl(obj types.Object) {
 			panic(internalErrorf("%s is not a defined type", t))
 		}
 
-		if typeparams.ForNamed(named).Len() == 0 {
+		if named.TypeParams().Len() == 0 {
 			w.tag('T')
 		} else {
 			w.tag('U')
 		}
 		w.pos(obj.Pos())
 
-		if typeparams.ForNamed(named).Len() > 0 {
+		if named.TypeParams().Len() > 0 {
 			// While importing the type parameters, tparamList computes and records
 			// their export name, so that it can be later used when writing the index.
-			w.tparamList(obj.Name(), typeparams.ForNamed(named), obj.Pkg())
+			w.tparamList(obj.Name(), named.TypeParams(), obj.Pkg())
 		}
 
 		underlying := obj.Type().Underlying()
@@ -541,7 +564,7 @@ func (p *iexporter) doDecl(obj types.Object) {
 
 			// Receiver type parameters are type arguments of the receiver type, so
 			// their name must be qualified before exporting recv.
-			if rparams := typeparams.RecvTypeParams(sig); rparams.Len() > 0 {
+			if rparams := sig.RecvTypeParams(); rparams.Len() > 0 {
 				prefix := obj.Name() + "." + m.Name()
 				for i := 0; i < rparams.Len(); i++ {
 					rparam := rparams.At(i)
@@ -673,6 +696,9 @@ func (w *exportWriter) qualifiedType(obj *types.TypeName) {
 	w.pkg(obj.Pkg())
 }
 
+// TODO(rfindley): what does 'pkg' even mean here? It would be better to pass
+// it in explicitly into signatures and structs that may use it for
+// constructing fields.
 func (w *exportWriter) typ(t types.Type, pkg *types.Package) {
 	w.data.uint64(w.p.typOff(t, pkg))
 }
@@ -713,19 +739,19 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
 	}
 	switch t := t.(type) {
 	case *types.Named:
-		if targs := typeparams.NamedTypeArgs(t); targs.Len() > 0 {
+		if targs := t.TypeArgs(); targs.Len() > 0 {
 			w.startType(instanceType)
 			// TODO(rfindley): investigate if this position is correct, and if it
 			// matters.
 			w.pos(t.Obj().Pos())
 			w.typeList(targs, pkg)
-			w.typ(typeparams.NamedTypeOrigin(t), pkg)
+			w.typ(t.Origin(), pkg)
 			return
 		}
 		w.startType(definedType)
 		w.qualifiedType(t.Obj())
 
-	case *typeparams.TypeParam:
+	case *types.TypeParam:
 		w.startType(typeParamType)
 		w.qualifiedType(t.Obj())
 
@@ -764,30 +790,53 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
 
 	case *types.Signature:
 		w.startType(signatureType)
-		w.setPkg(pkg, true)
+		w.pkg(pkg)
 		w.signature(t)
 
 	case *types.Struct:
 		w.startType(structType)
 		n := t.NumFields()
+		// Even for struct{} we must emit some qualifying package, because that's
+		// what the compiler does, and thus that's what the importer expects.
+		fieldPkg := pkg
 		if n > 0 {
-			w.setPkg(t.Field(0).Pkg(), true) // qualifying package for field objects
-		} else {
-			w.setPkg(pkg, true)
+			fieldPkg = t.Field(0).Pkg()
+		}
+		if fieldPkg == nil {
+			// TODO(rfindley): improve this very hacky logic.
+			//
+			// The importer expects a package to be set for all struct types, even
+			// those with no fields. A better encoding might be to set NumFields
+			// before pkg. setPkg panics with a nil package, which may be possible
+			// to reach with invalid packages (and perhaps valid packages, too?), so
+			// (arbitrarily) set the localpkg if available.
+			//
+			// Alternatively, we may be able to simply guarantee that pkg != nil, by
+			// reconsidering the encoding of constant values.
+			if w.p.shallow {
+				fieldPkg = w.p.localpkg
+			} else {
+				panic(internalErrorf("no package to set for empty struct"))
+			}
 		}
+		w.pkg(fieldPkg)
 		w.uint64(uint64(n))
+
 		for i := 0; i < n; i++ {
 			f := t.Field(i)
+			if w.p.shallow {
+				w.objectPath(f)
+			}
 			w.pos(f.Pos())
 			w.string(f.Name()) // unexported fields implicitly qualified by prior setPkg
-			w.typ(f.Type(), pkg)
+			w.typ(f.Type(), fieldPkg)
 			w.bool(f.Anonymous())
 			w.string(t.Tag(i)) // note (or tag)
 		}
 
 	case *types.Interface:
 		w.startType(interfaceType)
-		w.setPkg(pkg, true)
+		w.pkg(pkg)
 
 		n := t.NumEmbeddeds()
 		w.uint64(uint64(n))
@@ -802,17 +851,23 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
 			w.typ(ft, tPkg)
 		}
 
+		// See comment for struct fields. In shallow mode we change the encoding
+		// for interface methods that are promoted from other packages.
+
 		n = t.NumExplicitMethods()
 		w.uint64(uint64(n))
 		for i := 0; i < n; i++ {
 			m := t.ExplicitMethod(i)
+			if w.p.shallow {
+				w.objectPath(m)
+			}
 			w.pos(m.Pos())
 			w.string(m.Name())
 			sig, _ := m.Type().(*types.Signature)
 			w.signature(sig)
 		}
 
-	case *typeparams.Union:
+	case *types.Union:
 		w.startType(unionType)
 		nt := t.Len()
 		w.uint64(uint64(nt))
@@ -827,12 +882,61 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) {
 	}
 }
 
-func (w *exportWriter) setPkg(pkg *types.Package, write bool) {
-	if write {
-		w.pkg(pkg)
+// objectPath writes the package and objectPath to use to look up obj in a
+// different package, when encoding in "shallow" mode.
+//
+// When doing a shallow import, the importer creates only the local package,
+// and requests package symbols for dependencies from the client.
+// However, certain types defined in the local package may hold objects defined
+// (perhaps deeply) within another package.
+//
+// For example, consider the following:
+//
+//	package a
+//	func F() chan * map[string] struct { X int }
+//
+//	package b
+//	import "a"
+//	var B = a.F()
+//
+// In this example, the type of b.B holds fields defined in package a.
+// In order to have the correct canonical objects for the field defined in the
+// type of B, they are encoded as objectPaths and later looked up in the
+// importer. The same problem applies to interface methods.
+func (w *exportWriter) objectPath(obj types.Object) {
+	if obj.Pkg() == nil || obj.Pkg() == w.p.localpkg {
+		// obj.Pkg() may be nil for the builtin error.Error.
+		// In this case, or if obj is declared in the local package, no need to
+		// encode.
+		w.string("")
+		return
 	}
-
-	w.currPkg = pkg
+	objectPath, err := w.p.objectpathEncoder().For(obj)
+	if err != nil {
+		// Fall back to the empty string, which will cause the importer to create a
+		// new object, which matches earlier behavior. Creating a new object is
+		// sufficient for many purposes (such as type checking), but causes certain
+		// references algorithms to fail (golang/go#60819). However, we didn't
+		// notice this problem during months of gopls@v0.12.0 testing.
+		//
+		// TODO(golang/go#61674): this workaround is insufficient, as in the case
+		// where the field forwarded from an instantiated type that may not appear
+		// in the export data of the original package:
+		//
+		//  // package a
+		//  type A[P any] struct{ F P }
+		//
+		//  // package b
+		//  type B a.A[int]
+		//
+		// We need to update references algorithms not to depend on this
+		// de-duplication, at which point we may want to simply remove the
+		// workaround here.
+		w.string("")
+		return
+	}
+	w.string(string(objectPath))
+	w.pkg(obj.Pkg())
 }
 
 func (w *exportWriter) signature(sig *types.Signature) {
@@ -843,14 +947,14 @@ func (w *exportWriter) signature(sig *types.Signature) {
 	}
 }
 
-func (w *exportWriter) typeList(ts *typeparams.TypeList, pkg *types.Package) {
+func (w *exportWriter) typeList(ts *types.TypeList, pkg *types.Package) {
 	w.uint64(uint64(ts.Len()))
 	for i := 0; i < ts.Len(); i++ {
 		w.typ(ts.At(i), pkg)
 	}
 }
 
-func (w *exportWriter) tparamList(prefix string, list *typeparams.TypeParamList, pkg *types.Package) {
+func (w *exportWriter) tparamList(prefix string, list *types.TypeParamList, pkg *types.Package) {
 	ll := uint64(list.Len())
 	w.uint64(ll)
 	for i := 0; i < list.Len(); i++ {
@@ -868,7 +972,7 @@ const blankMarker = "$"
 // differs from its actual object name: it is prefixed with a qualifier, and
 // blank type parameter names are disambiguated by their index in the type
 // parameter list.
-func tparamExportName(prefix string, tparam *typeparams.TypeParam) string {
+func tparamExportName(prefix string, tparam *types.TypeParam) string {
 	assert(prefix != "")
 	name := tparam.Obj().Name()
 	if name == "_" {
@@ -913,6 +1017,17 @@ func (w *exportWriter) value(typ types.Type, v constant.Value) {
 		w.int64(int64(v.Kind()))
 	}
 
+	if v.Kind() == constant.Unknown {
+		// golang/go#60605: treat unknown constant values as if they have invalid type
+		//
+		// This loses some fidelity over the package type-checked from source, but that
+		// is acceptable.
+		//
+		// TODO(rfindley): we should switch on the recorded constant kind rather
+		// than the constant type
+		return
+	}
+
 	switch b := typ.Underlying().(*types.Basic); b.Info() & types.IsConstType {
 	case types.IsBoolean:
 		w.bool(constant.BoolVal(v))
@@ -969,6 +1084,16 @@ func constantToFloat(x constant.Value) *big.Float {
 	return &f
 }
 
+func valueToRat(x constant.Value) *big.Rat {
+	// Convert little-endian to big-endian.
+	// I can't believe this is necessary.
+	bytes := constant.Bytes(x)
+	for i := 0; i < len(bytes)/2; i++ {
+		bytes[i], bytes[len(bytes)-1-i] = bytes[len(bytes)-1-i], bytes[i]
+	}
+	return new(big.Rat).SetInt(new(big.Int).SetBytes(bytes))
+}
+
 // mpint exports a multi-precision integer.
 //
 // For unsigned types, small values are written out as a single
@@ -1178,3 +1303,19 @@ func (q *objQueue) popHead() types.Object {
 	q.head++
 	return obj
 }
+
+// internalError represents an error generated inside this package.
+type internalError string
+
+func (e internalError) Error() string { return "gcimporter: " + string(e) }
+
+// TODO(adonovan): make this call panic, so that it's symmetric with errorf.
+// Otherwise it's easy to forget to do anything with the error.
+//
+// TODO(adonovan): also, consider switching the names "errorf" and
+// "internalErrorf" as the former is used for bugs, whose cause is
+// internal inconsistency, whereas the latter is used for ordinary
+// situations like bad input, whose cause is external.
+func internalErrorf(format string, args ...interface{}) error {
+	return internalError(fmt.Sprintf(format, args...))
+}
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go
index be6dace15..9bde15e3b 100644
--- a/vendor/golang.org/x/tools/internal/gcimporter/iimport.go
+++ b/vendor/golang.org/x/tools/internal/gcimporter/iimport.go
@@ -21,7 +21,7 @@ import (
 	"sort"
 	"strings"
 
-	"golang.org/x/tools/internal/typeparams"
+	"golang.org/x/tools/go/types/objectpath"
 )
 
 type intReader struct {
@@ -85,7 +85,7 @@ const (
 // If the export data version is not recognized or the format is otherwise
 // compromised, an error is returned.
 func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (int, *types.Package, error) {
-	pkgs, err := iimportCommon(fset, GetPackageFromMap(imports), data, false, path, nil)
+	pkgs, err := iimportCommon(fset, GetPackagesFromMap(imports), data, false, path, false, nil)
 	if err != nil {
 		return 0, nil, err
 	}
@@ -94,33 +94,49 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []
 
 // IImportBundle imports a set of packages from the serialized package bundle.
 func IImportBundle(fset *token.FileSet, imports map[string]*types.Package, data []byte) ([]*types.Package, error) {
-	return iimportCommon(fset, GetPackageFromMap(imports), data, true, "", nil)
+	return iimportCommon(fset, GetPackagesFromMap(imports), data, true, "", false, nil)
 }
 
-// A GetPackageFunc is a function that gets the package with the given path
-// from the importer state, creating it (with the specified name) if necessary.
-// It is an abstraction of the map historically used to memoize package creation.
+// A GetPackagesFunc function obtains the non-nil symbols for a set of
+// packages, creating and recursively importing them as needed. An
+// implementation should store each package symbol is in the Pkg
+// field of the items array.
 //
-// Two calls with the same path must return the same package.
-//
-// If the given getPackage func returns nil, the import will fail.
-type GetPackageFunc = func(path, name string) *types.Package
+// Any error causes importing to fail. This can be used to quickly read
+// the import manifest of an export data file without fully decoding it.
+type GetPackagesFunc = func(items []GetPackagesItem) error
+
+// A GetPackagesItem is a request from the importer for the package
+// symbol of the specified name and path.
+type GetPackagesItem struct {
+	Name, Path string
+	Pkg        *types.Package // to be filled in by GetPackagesFunc call
+
+	// private importer state
+	pathOffset uint64
+	nameIndex  map[string]uint64
+}
 
-// GetPackageFromMap returns a GetPackageFunc that retrieves packages from the
-// given map of package path -> package.
+// GetPackagesFromMap returns a GetPackagesFunc that retrieves
+// packages from the given map of package path to package.
 //
-// The resulting func may mutate m: if a requested package is not found, a new
-// package will be inserted into m.
-func GetPackageFromMap(m map[string]*types.Package) GetPackageFunc {
-	return func(path, name string) *types.Package {
-		if _, ok := m[path]; !ok {
-			m[path] = types.NewPackage(path, name)
+// The returned function may mutate m: each requested package that is not
+// found is created with types.NewPackage and inserted into m.
+func GetPackagesFromMap(m map[string]*types.Package) GetPackagesFunc {
+	return func(items []GetPackagesItem) error {
+		for i, item := range items {
+			pkg, ok := m[item.Path]
+			if !ok {
+				pkg = types.NewPackage(item.Path, item.Name)
+				m[item.Path] = pkg
+			}
+			items[i].Pkg = pkg
 		}
-		return m[path]
+		return nil
 	}
 }
 
-func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte, bundle bool, path string, insert InsertType) (pkgs []*types.Package, err error) {
+func iimportCommon(fset *token.FileSet, getPackages GetPackagesFunc, data []byte, bundle bool, path string, shallow bool, reportf ReportFunc) (pkgs []*types.Package, err error) {
 	const currentVersion = iexportVersionCurrent
 	version := int64(-1)
 	if !debug {
@@ -131,7 +147,7 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 				} else if version > currentVersion {
 					err = fmt.Errorf("cannot import %q (%v), export data is newer version - update tool", path, e)
 				} else {
-					err = fmt.Errorf("cannot import %q (%v), possibly version skew - reinstall package", path, e)
+					err = fmt.Errorf("internal error while importing %q (%v); please report an issue", path, e)
 				}
 			}
 		}()
@@ -140,11 +156,8 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 	r := &intReader{bytes.NewReader(data), path}
 
 	if bundle {
-		bundleVersion := r.uint64()
-		switch bundleVersion {
-		case bundleVersion:
-		default:
-			errorf("unknown bundle format version %d", bundleVersion)
+		if v := r.uint64(); v != bundleVersion {
+			errorf("unknown bundle format version %d", v)
 		}
 	}
 
@@ -162,7 +175,7 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 	sLen := int64(r.uint64())
 	var fLen int64
 	var fileOffset []uint64
-	if insert != nil {
+	if shallow {
 		// Shallow mode uses a different position encoding.
 		fLen = int64(r.uint64())
 		fileOffset = make([]uint64, r.uint64())
@@ -181,7 +194,8 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 	p := iimporter{
 		version: int(version),
 		ipath:   path,
-		insert:  insert,
+		shallow: shallow,
+		reportf: reportf,
 
 		stringData:  stringData,
 		stringCache: make(map[uint64]string),
@@ -208,8 +222,9 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 		p.typCache[uint64(i)] = pt
 	}
 
-	pkgList := make([]*types.Package, r.uint64())
-	for i := range pkgList {
+	// Gather the relevant packages from the manifest.
+	items := make([]GetPackagesItem, r.uint64())
+	for i := range items {
 		pkgPathOff := r.uint64()
 		pkgPath := p.stringAt(pkgPathOff)
 		pkgName := p.stringAt(r.uint64())
@@ -218,29 +233,42 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 		if pkgPath == "" {
 			pkgPath = path
 		}
-		pkg := getPackage(pkgPath, pkgName)
-		if pkg == nil {
-			errorf("internal error: getPackage returned nil package for %s", pkgPath)
-		} else if pkg.Name() != pkgName {
-			errorf("conflicting names %s and %s for package %q", pkg.Name(), pkgName, path)
-		}
-		if i == 0 && !bundle {
-			p.localpkg = pkg
-		}
-
-		p.pkgCache[pkgPathOff] = pkg
+		items[i].Name = pkgName
+		items[i].Path = pkgPath
+		items[i].pathOffset = pkgPathOff
 
 		// Read index for package.
 		nameIndex := make(map[string]uint64)
 		nSyms := r.uint64()
-		// In shallow mode we don't expect an index for other packages.
-		assert(nSyms == 0 || p.localpkg == pkg || p.insert == nil)
+		// In shallow mode, only the current package (i=0) has an index.
+		assert(!(shallow && i > 0 && nSyms != 0))
 		for ; nSyms > 0; nSyms-- {
 			name := p.stringAt(r.uint64())
 			nameIndex[name] = r.uint64()
 		}
 
-		p.pkgIndex[pkg] = nameIndex
+		items[i].nameIndex = nameIndex
+	}
+
+	// Request packages all at once from the client,
+	// enabling a parallel implementation.
+	if err := getPackages(items); err != nil {
+		return nil, err // don't wrap this error
+	}
+
+	// Check the results and complete the index.
+	pkgList := make([]*types.Package, len(items))
+	for i, item := range items {
+		pkg := item.Pkg
+		if pkg == nil {
+			errorf("internal error: getPackages returned nil package for %q", item.Path)
+		} else if pkg.Path() != item.Path {
+			errorf("internal error: getPackages returned wrong path %q, want %q", pkg.Path(), item.Path)
+		} else if pkg.Name() != item.Name {
+			errorf("internal error: getPackages returned wrong name %s for package %q, want %s", pkg.Name(), item.Path, item.Name)
+		}
+		p.pkgCache[item.pathOffset] = pkg
+		p.pkgIndex[pkg] = item.nameIndex
 		pkgList[i] = pkg
 	}
 
@@ -292,18 +320,25 @@ func iimportCommon(fset *token.FileSet, getPackage GetPackageFunc, data []byte,
 	// Therefore, we defer calling SetConstraint there, and call it here instead
 	// after all types are complete.
 	for _, d := range p.later {
-		typeparams.SetTypeParamConstraint(d.t, d.constraint)
+		d.t.SetConstraint(d.constraint)
 	}
 
 	for _, typ := range p.interfaceList {
 		typ.Complete()
 	}
 
+	// Workaround for golang/go#61561. See the doc for instanceList for details.
+	for _, typ := range p.instanceList {
+		if iface, _ := typ.Underlying().(*types.Interface); iface != nil {
+			iface.Complete()
+		}
+	}
+
 	return pkgs, nil
 }
 
 type setConstraintArgs struct {
-	t          *typeparams.TypeParam
+	t          *types.TypeParam
 	constraint types.Type
 }
 
@@ -311,8 +346,8 @@ type iimporter struct {
 	version int
 	ipath   string
 
-	localpkg *types.Package
-	insert   func(pkg *types.Package, name string) // "shallow" mode only
+	shallow bool
+	reportf ReportFunc // if non-nil, used to report bugs
 
 	stringData  []byte
 	stringCache map[uint64]string
@@ -329,6 +364,12 @@ type iimporter struct {
 	fake          fakeFileSet
 	interfaceList []*types.Interface
 
+	// Workaround for the go/types bug golang/go#61561: instances produced during
+	// instantiation may contain incomplete interfaces. Here we only complete the
+	// underlying type of the instance, which is the most common case but doesn't
+	// handle parameterized interface literals defined deeper in the type.
+	instanceList []types.Type // instances for later completion (see golang/go#61561)
+
 	// Arguments for calls to SetConstraint that are deferred due to recursive types
 	later []setConstraintArgs
 
@@ -360,13 +401,9 @@ func (p *iimporter) doDecl(pkg *types.Package, name string) {
 
 	off, ok := p.pkgIndex[pkg][name]
 	if !ok {
-		// In "shallow" mode, call back to the application to
-		// find the object and insert it into the package scope.
-		if p.insert != nil {
-			assert(pkg != p.localpkg)
-			p.insert(pkg, name) // "can't fail"
-			return
-		}
+		// In deep mode, the index should be complete. In shallow
+		// mode, we should have already recursively loaded necessary
+		// dependencies so the above Lookup succeeds.
 		errorf("%v.%v not in index", pkg, name)
 	}
 
@@ -511,7 +548,7 @@ func (r *importReader) obj(name string) {
 		r.declare(types.NewConst(pos, r.currPkg, name, typ, val))
 
 	case 'F', 'G':
-		var tparams []*typeparams.TypeParam
+		var tparams []*types.TypeParam
 		if tag == 'G' {
 			tparams = r.tparamList()
 		}
@@ -528,7 +565,7 @@ func (r *importReader) obj(name string) {
 		r.declare(obj)
 		if tag == 'U' {
 			tparams := r.tparamList()
-			typeparams.SetForNamed(named, tparams)
+			named.SetTypeParams(tparams)
 		}
 
 		underlying := r.p.typAt(r.uint64(), named).Underlying()
@@ -545,12 +582,12 @@ func (r *importReader) obj(name string) {
 				// typeparams being used in the method sig/body).
 				base := baseType(recv.Type())
 				assert(base != nil)
-				targs := typeparams.NamedTypeArgs(base)
-				var rparams []*typeparams.TypeParam
+				targs := base.TypeArgs()
+				var rparams []*types.TypeParam
 				if targs.Len() > 0 {
-					rparams = make([]*typeparams.TypeParam, targs.Len())
+					rparams = make([]*types.TypeParam, targs.Len())
 					for i := range rparams {
-						rparams[i] = targs.At(i).(*typeparams.TypeParam)
+						rparams[i] = targs.At(i).(*types.TypeParam)
 					}
 				}
 				msig := r.signature(recv, rparams, nil)
@@ -568,7 +605,7 @@ func (r *importReader) obj(name string) {
 		}
 		name0 := tparamName(name)
 		tn := types.NewTypeName(pos, r.currPkg, name0, nil)
-		t := typeparams.NewTypeParam(tn, nil)
+		t := types.NewTypeParam(tn, nil)
 
 		// To handle recursive references to the typeparam within its
 		// bound, save the partial type in tparamIndex before reading the bounds.
@@ -584,7 +621,7 @@ func (r *importReader) obj(name string) {
 			if iface == nil {
 				errorf("non-interface constraint marked implicit")
 			}
-			typeparams.MarkImplicit(iface)
+			iface.MarkImplicit()
 		}
 		// The constraint type may not be complete, if we
 		// are in the middle of a type recursion involving type
@@ -733,7 +770,8 @@ func (r *importReader) qualifiedIdent() (*types.Package, string) {
 }
 
 func (r *importReader) pos() token.Pos {
-	if r.p.insert != nil { // shallow mode
+	if r.p.shallow {
+		// precise offsets are encoded only in shallow mode
 		return r.posv2()
 	}
 	if r.p.version >= iexportVersionPosCol {
@@ -834,13 +872,28 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
 		fields := make([]*types.Var, r.uint64())
 		tags := make([]string, len(fields))
 		for i := range fields {
+			var field *types.Var
+			if r.p.shallow {
+				field, _ = r.objectPathObject().(*types.Var)
+			}
+
 			fpos := r.pos()
 			fname := r.ident()
 			ftyp := r.typ()
 			emb := r.bool()
 			tag := r.string()
 
-			fields[i] = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
+			// Either this is not a shallow import, the field is local, or the
+			// encoded objectPath failed to produce an object (a bug).
+			//
+			// Even in this last, buggy case, fall back on creating a new field. As
+			// discussed in iexport.go, this is not correct, but mostly works and is
+			// preferable to failing (for now at least).
+			if field == nil {
+				field = types.NewField(fpos, r.currPkg, fname, ftyp, emb)
+			}
+
+			fields[i] = field
 			tags[i] = tag
 		}
 		return types.NewStruct(fields, tags)
@@ -856,6 +909,11 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
 
 		methods := make([]*types.Func, r.uint64())
 		for i := range methods {
+			var method *types.Func
+			if r.p.shallow {
+				method, _ = r.objectPathObject().(*types.Func)
+			}
+
 			mpos := r.pos()
 			mname := r.ident()
 
@@ -865,9 +923,12 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
 			if base != nil {
 				recv = types.NewVar(token.NoPos, r.currPkg, "", base)
 			}
-
 			msig := r.signature(recv, nil, nil)
-			methods[i] = types.NewFunc(mpos, r.currPkg, mname, msig)
+
+			if method == nil {
+				method = types.NewFunc(mpos, r.currPkg, mname, msig)
+			}
+			methods[i] = method
 		}
 
 		typ := newInterface(methods, embeddeds)
@@ -904,18 +965,21 @@ func (r *importReader) doType(base *types.Named) (res types.Type) {
 		// The imported instantiated type doesn't include any methods, so
 		// we must always use the methods of the base (orig) type.
 		// TODO provide a non-nil *Environment
-		t, _ := typeparams.Instantiate(nil, baseType, targs, false)
+		t, _ := types.Instantiate(nil, baseType, targs, false)
+
+		// Workaround for golang/go#61561. See the doc for instanceList for details.
+		r.p.instanceList = append(r.p.instanceList, t)
 		return t
 
 	case unionType:
 		if r.p.version < iexportVersionGenerics {
 			errorf("unexpected instantiation type")
 		}
-		terms := make([]*typeparams.Term, r.uint64())
+		terms := make([]*types.Term, r.uint64())
 		for i := range terms {
-			terms[i] = typeparams.NewTerm(r.bool(), r.typ())
+			terms[i] = types.NewTerm(r.bool(), r.typ())
 		}
-		return typeparams.NewUnion(terms)
+		return types.NewUnion(terms)
 	}
 }
 
@@ -923,23 +987,43 @@ func (r *importReader) kind() itag {
 	return itag(r.uint64())
 }
 
-func (r *importReader) signature(recv *types.Var, rparams []*typeparams.TypeParam, tparams []*typeparams.TypeParam) *types.Signature {
+// objectPathObject is the inverse of exportWriter.objectPath.
+//
+// In shallow mode, certain fields and methods may need to be looked up in an
+// imported package. See the doc for exportWriter.objectPath for a full
+// explanation.
+func (r *importReader) objectPathObject() types.Object {
+	objPath := objectpath.Path(r.string())
+	if objPath == "" {
+		return nil
+	}
+	pkg := r.pkg()
+	obj, err := objectpath.Object(pkg, objPath)
+	if err != nil {
+		if r.p.reportf != nil {
+			r.p.reportf("failed to find object for objectPath %q: %v", objPath, err)
+		}
+	}
+	return obj
+}
+
+func (r *importReader) signature(recv *types.Var, rparams []*types.TypeParam, tparams []*types.TypeParam) *types.Signature {
 	params := r.paramList()
 	results := r.paramList()
 	variadic := params.Len() > 0 && r.bool()
-	return typeparams.NewSignatureType(recv, rparams, tparams, params, results, variadic)
+	return types.NewSignatureType(recv, rparams, tparams, params, results, variadic)
 }
 
-func (r *importReader) tparamList() []*typeparams.TypeParam {
+func (r *importReader) tparamList() []*types.TypeParam {
 	n := r.uint64()
 	if n == 0 {
 		return nil
 	}
-	xs := make([]*typeparams.TypeParam, n)
+	xs := make([]*types.TypeParam, n)
 	for i := range xs {
 		// Note: the standard library importer is tolerant of nil types here,
 		// though would panic in SetTypeParams.
-		xs[i] = r.typ().(*typeparams.TypeParam)
+		xs[i] = r.typ().(*types.TypeParam)
 	}
 	return xs
 }
diff --git a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go
index 34fc783f8..b977435f6 100644
--- a/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go
+++ b/vendor/golang.org/x/tools/internal/gcimporter/ureader_yes.go
@@ -10,6 +10,7 @@
 package gcimporter
 
 import (
+	"fmt"
 	"go/token"
 	"go/types"
 	"sort"
@@ -63,6 +64,14 @@ type typeInfo struct {
 }
 
 func UImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) {
+	if !debug {
+		defer func() {
+			if x := recover(); x != nil {
+				err = fmt.Errorf("internal error in importing %q (%v); please report an issue", path, x)
+			}
+		}()
+	}
+
 	s := string(data)
 	s = s[:strings.LastIndex(s, "\n$$\n")]
 	input := pkgbits.NewPkgDecoder(path, s)
diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go
index d50551693..55312522d 100644
--- a/vendor/golang.org/x/tools/internal/gocommand/invoke.go
+++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go
@@ -8,10 +8,13 @@ package gocommand
 import (
 	"bytes"
 	"context"
+	"errors"
 	"fmt"
 	"io"
 	"log"
 	"os"
+	"os/exec"
+	"reflect"
 	"regexp"
 	"runtime"
 	"strconv"
@@ -19,9 +22,10 @@ import (
 	"sync"
 	"time"
 
-	exec "golang.org/x/sys/execabs"
-
 	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/event/keys"
+	"golang.org/x/tools/internal/event/label"
+	"golang.org/x/tools/internal/event/tag"
 )
 
 // An Runner will run go command invocations and serialize
@@ -51,9 +55,19 @@ func (runner *Runner) initialize() {
 // 1.14: go: updating go.mod: existing contents have changed since last read
 var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)
 
+// verb is an event label for the go command verb.
+var verb = keys.NewString("verb", "go command verb")
+
+func invLabels(inv Invocation) []label.Label {
+	return []label.Label{verb.Of(inv.Verb), tag.Directory.Of(inv.WorkingDir)}
+}
+
 // Run is a convenience wrapper around RunRaw.
 // It returns only stdout and a "friendly" error.
 func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
+	ctx, done := event.Start(ctx, "gocommand.Runner.Run", invLabels(inv)...)
+	defer done()
+
 	stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
 	return stdout, friendly
 }
@@ -61,13 +75,19 @@ func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, e
 // RunPiped runs the invocation serially, always waiting for any concurrent
 // invocations to complete first.
 func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
+	ctx, done := event.Start(ctx, "gocommand.Runner.RunPiped", invLabels(inv)...)
+	defer done()
+
 	_, err := runner.runPiped(ctx, inv, stdout, stderr)
 	return err
 }
 
 // RunRaw runs the invocation, serializing requests only if they fight over
 // go.mod changes.
+// Postcondition: both error results have same nilness.
 func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
+	ctx, done := event.Start(ctx, "gocommand.Runner.RunRaw", invLabels(inv)...)
+	defer done()
 	// Make sure the runner is always initialized.
 	runner.initialize()
 
@@ -75,23 +95,24 @@ func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer
 	stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
 
 	// If we encounter a load concurrency error, we need to retry serially.
-	if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
-		return stdout, stderr, friendlyErr, err
+	if friendlyErr != nil && modConcurrencyError.MatchString(friendlyErr.Error()) {
+		event.Error(ctx, "Load concurrency error, will retry serially", err)
+
+		// Run serially by calling runPiped.
+		stdout.Reset()
+		stderr.Reset()
+		friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
 	}
-	event.Error(ctx, "Load concurrency error, will retry serially", err)
 
-	// Run serially by calling runPiped.
-	stdout.Reset()
-	stderr.Reset()
-	friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
 	return stdout, stderr, friendlyErr, err
 }
 
+// Postcondition: both error results have same nilness.
 func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
 	// Wait for 1 worker to become available.
 	select {
 	case <-ctx.Done():
-		return nil, nil, nil, ctx.Err()
+		return nil, nil, ctx.Err(), ctx.Err()
 	case runner.inFlight <- struct{}{}:
 		defer func() { <-runner.inFlight }()
 	}
@@ -101,6 +122,7 @@ func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes
 	return stdout, stderr, friendlyErr, err
 }
 
+// Postcondition: both error results have same nilness.
 func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
 	// Make sure the runner is always initialized.
 	runner.initialize()
@@ -109,7 +131,7 @@ func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stde
 	// runPiped commands.
 	select {
 	case <-ctx.Done():
-		return nil, ctx.Err()
+		return ctx.Err(), ctx.Err()
 	case runner.serialized <- struct{}{}:
 		defer func() { <-runner.serialized }()
 	}
@@ -119,7 +141,7 @@ func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stde
 	for i := 0; i < maxInFlight; i++ {
 		select {
 		case <-ctx.Done():
-			return nil, ctx.Err()
+			return ctx.Err(), ctx.Err()
 		case runner.inFlight <- struct{}{}:
 			// Make sure we always "return" any workers we took.
 			defer func() { <-runner.inFlight }()
@@ -152,6 +174,7 @@ type Invocation struct {
 	Logf       func(format string, args ...interface{})
 }
 
+// Postcondition: both error results have same nilness.
 func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
 	rawError = i.run(ctx, stdout, stderr)
 	if rawError != nil {
@@ -215,6 +238,18 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
 	cmd := exec.Command("go", goArgs...)
 	cmd.Stdout = stdout
 	cmd.Stderr = stderr
+
+	// cmd.WaitDelay was added only in go1.20 (see #50436).
+	if waitDelay := reflect.ValueOf(cmd).Elem().FieldByName("WaitDelay"); waitDelay.IsValid() {
+		// https://go.dev/issue/59541: don't wait forever copying stderr
+		// after the command has exited.
+		// After CL 484741 we copy stdout manually, so we we'll stop reading that as
+		// soon as ctx is done. However, we also don't want to wait around forever
+		// for stderr. Give a much-longer-than-reasonable delay and then assume that
+		// something has wedged in the kernel or runtime.
+		waitDelay.Set(reflect.ValueOf(30 * time.Second))
+	}
+
 	// On darwin the cwd gets resolved to the real path, which breaks anything that
 	// expects the working directory to keep the original path, including the
 	// go command when dealing with modules.
@@ -229,6 +264,7 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
 		cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir)
 		cmd.Dir = i.WorkingDir
 	}
+
 	defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now())
 
 	return runCmdContext(ctx, cmd)
@@ -242,10 +278,85 @@ var DebugHangingGoCommands = false
 
 // runCmdContext is like exec.CommandContext except it sends os.Interrupt
 // before os.Kill.
-func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
-	if err := cmd.Start(); err != nil {
+func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) {
+	// If cmd.Stdout is not an *os.File, the exec package will create a pipe and
+	// copy it to the Writer in a goroutine until the process has finished and
+	// either the pipe reaches EOF or command's WaitDelay expires.
+	//
+	// However, the output from 'go list' can be quite large, and we don't want to
+	// keep reading (and allocating buffers) if we've already decided we don't
+	// care about the output. We don't want to wait for the process to finish, and
+	// we don't wait to wait for the WaitDelay to expire either.
+	//
+	// Instead, if cmd.Stdout requires a copying goroutine we explicitly replace
+	// it with a pipe (which is an *os.File), which we can close in order to stop
+	// copying output as soon as we realize we don't care about it.
+	var stdoutW *os.File
+	if cmd.Stdout != nil {
+		if _, ok := cmd.Stdout.(*os.File); !ok {
+			var stdoutR *os.File
+			stdoutR, stdoutW, err = os.Pipe()
+			if err != nil {
+				return err
+			}
+			prevStdout := cmd.Stdout
+			cmd.Stdout = stdoutW
+
+			stdoutErr := make(chan error, 1)
+			go func() {
+				_, err := io.Copy(prevStdout, stdoutR)
+				if err != nil {
+					err = fmt.Errorf("copying stdout: %w", err)
+				}
+				stdoutErr <- err
+			}()
+			defer func() {
+				// We started a goroutine to copy a stdout pipe.
+				// Wait for it to finish, or terminate it if need be.
+				var err2 error
+				select {
+				case err2 = <-stdoutErr:
+					stdoutR.Close()
+				case <-ctx.Done():
+					stdoutR.Close()
+					// Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close
+					// should cause the Read call in io.Copy to unblock and return
+					// immediately, but we still need to receive from stdoutErr to confirm
+					// that it has happened.
+					<-stdoutErr
+					err2 = ctx.Err()
+				}
+				if err == nil {
+					err = err2
+				}
+			}()
+
+			// Per https://pkg.go.dev/os/exec#Cmd, “If Stdout and Stderr are the
+			// same writer, and have a type that can be compared with ==, at most
+			// one goroutine at a time will call Write.”
+			//
+			// Since we're starting a goroutine that writes to cmd.Stdout, we must
+			// also update cmd.Stderr so that it still holds.
+			func() {
+				defer func() { recover() }()
+				if cmd.Stderr == prevStdout {
+					cmd.Stderr = cmd.Stdout
+				}
+			}()
+		}
+	}
+
+	err = cmd.Start()
+	if stdoutW != nil {
+		// The child process has inherited the pipe file,
+		// so close the copy held in this process.
+		stdoutW.Close()
+		stdoutW = nil
+	}
+	if err != nil {
 		return err
 	}
+
 	resChan := make(chan error, 1)
 	go func() {
 		resChan <- cmd.Wait()
@@ -253,11 +364,14 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
 
 	// If we're interested in debugging hanging Go commands, stop waiting after a
 	// minute and panic with interesting information.
-	if DebugHangingGoCommands {
+	debug := DebugHangingGoCommands
+	if debug {
+		timer := time.NewTimer(1 * time.Minute)
+		defer timer.Stop()
 		select {
 		case err := <-resChan:
 			return err
-		case <-time.After(1 * time.Minute):
+		case <-timer.C:
 			HandleHangingGoCommand(cmd.Process)
 		case <-ctx.Done():
 		}
@@ -270,30 +384,25 @@ func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
 	}
 
 	// Cancelled. Interrupt and see if it ends voluntarily.
-	cmd.Process.Signal(os.Interrupt)
-	select {
-	case err := <-resChan:
-		return err
-	case <-time.After(time.Second):
+	if err := cmd.Process.Signal(os.Interrupt); err == nil {
+		// (We used to wait only 1s but this proved
+		// fragile on loaded builder machines.)
+		timer := time.NewTimer(5 * time.Second)
+		defer timer.Stop()
+		select {
+		case err := <-resChan:
+			return err
+		case <-timer.C:
+		}
 	}
 
 	// Didn't shut down in response to interrupt. Kill it hard.
 	// TODO(rfindley): per advice from bcmills@, it may be better to send SIGQUIT
 	// on certain platforms, such as unix.
-	if err := cmd.Process.Kill(); err != nil && DebugHangingGoCommands {
-		// Don't panic here as this reliably fails on windows with EINVAL.
+	if err := cmd.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) && debug {
 		log.Printf("error killing the Go command: %v", err)
 	}
 
-	// See above: don't wait indefinitely if we're debugging hanging Go commands.
-	if DebugHangingGoCommands {
-		select {
-		case err := <-resChan:
-			return err
-		case <-time.After(10 * time.Second): // a shorter wait as resChan should return quickly following Kill
-			HandleHangingGoCommand(cmd.Process)
-		}
-	}
 	return <-resChan
 }
 
diff --git a/vendor/golang.org/x/tools/internal/gocommand/version.go b/vendor/golang.org/x/tools/internal/gocommand/version.go
index 307a76d47..446c5846a 100644
--- a/vendor/golang.org/x/tools/internal/gocommand/version.go
+++ b/vendor/golang.org/x/tools/internal/gocommand/version.go
@@ -23,21 +23,11 @@ import (
 func GoVersion(ctx context.Context, inv Invocation, r *Runner) (int, error) {
 	inv.Verb = "list"
 	inv.Args = []string{"-e", "-f", `{{context.ReleaseTags}}`, `--`, `unsafe`}
-	inv.Env = append(append([]string{}, inv.Env...), "GO111MODULE=off")
-	// Unset any unneeded flags, and remove them from BuildFlags, if they're
-	// present.
-	inv.ModFile = ""
+	inv.BuildFlags = nil // This is not a build command.
 	inv.ModFlag = ""
-	var buildFlags []string
-	for _, flag := range inv.BuildFlags {
-		// Flags can be prefixed by one or two dashes.
-		f := strings.TrimPrefix(strings.TrimPrefix(flag, "-"), "-")
-		if strings.HasPrefix(f, "mod=") || strings.HasPrefix(f, "modfile=") {
-			continue
-		}
-		buildFlags = append(buildFlags, flag)
-	}
-	inv.BuildFlags = buildFlags
+	inv.ModFile = ""
+	inv.Env = append(inv.Env[:len(inv.Env):len(inv.Env)], "GO111MODULE=off")
+
 	stdoutBytes, err := r.Run(ctx, inv)
 	if err != nil {
 		return 0, err
diff --git a/vendor/golang.org/x/tools/internal/packagesinternal/packages.go b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go
index d9950b1f0..44719de17 100644
--- a/vendor/golang.org/x/tools/internal/packagesinternal/packages.go
+++ b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go
@@ -5,10 +5,6 @@
 // Package packagesinternal exposes internal-only fields from go/packages.
 package packagesinternal
 
-import (
-	"golang.org/x/tools/internal/gocommand"
-)
-
 var GetForTest = func(p interface{}) string { return "" }
 var GetDepsErrors = func(p interface{}) []*PackageError { return nil }
 
@@ -18,10 +14,6 @@ type PackageError struct {
 	Err         string   // the error itself
 }
 
-var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil }
-
-var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {}
-
 var TypecheckCgo int
 var DepsErrors int // must be set as a LoadMode to call GetDepsErrors
 var ForTest int    // must be set as a LoadMode to call GetForTest
diff --git a/vendor/golang.org/x/tools/internal/typeparams/common.go b/vendor/golang.org/x/tools/internal/typeparams/common.go
index cfba8189f..cdab98853 100644
--- a/vendor/golang.org/x/tools/internal/typeparams/common.go
+++ b/vendor/golang.org/x/tools/internal/typeparams/common.go
@@ -23,6 +23,7 @@
 package typeparams
 
 import (
+	"fmt"
 	"go/ast"
 	"go/token"
 	"go/types"
@@ -41,7 +42,7 @@ func UnpackIndexExpr(n ast.Node) (x ast.Expr, lbrack token.Pos, indices []ast.Ex
 	switch e := n.(type) {
 	case *ast.IndexExpr:
 		return e.X, e.Lbrack, []ast.Expr{e.Index}, e.Rbrack
-	case *IndexListExpr:
+	case *ast.IndexListExpr:
 		return e.X, e.Lbrack, e.Indices, e.Rbrack
 	}
 	return nil, token.NoPos, nil, token.NoPos
@@ -62,7 +63,7 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke
 			Rbrack: rbrack,
 		}
 	default:
-		return &IndexListExpr{
+		return &ast.IndexListExpr{
 			X:       x,
 			Lbrack:  lbrack,
 			Indices: indices,
@@ -73,7 +74,7 @@ func PackIndexExpr(x ast.Expr, lbrack token.Pos, indices []ast.Expr, rbrack toke
 
 // IsTypeParam reports whether t is a type parameter.
 func IsTypeParam(t types.Type) bool {
-	_, ok := t.(*TypeParam)
+	_, ok := t.(*types.TypeParam)
 	return ok
 }
 
@@ -99,12 +100,37 @@ func OriginMethod(fn *types.Func) *types.Func {
 		// Receiver is a *types.Interface.
 		return fn
 	}
-	if ForNamed(named).Len() == 0 {
+	if named.TypeParams().Len() == 0 {
 		// Receiver base has no type parameters, so we can avoid the lookup below.
 		return fn
 	}
-	orig := NamedTypeOrigin(named)
+	orig := named.Origin()
 	gfn, _, _ := types.LookupFieldOrMethod(orig, true, fn.Pkg(), fn.Name())
+
+	// This is a fix for a gopls crash (#60628) due to a go/types bug (#60634). In:
+	// 	package p
+	//      type T *int
+	//      func (*T) f() {}
+	// LookupFieldOrMethod(T, true, p, f)=nil, but NewMethodSet(*T)={(*T).f}.
+	// Here we make them consistent by force.
+	// (The go/types bug is general, but this workaround is reached only
+	// for generic T thanks to the early return above.)
+	if gfn == nil {
+		mset := types.NewMethodSet(types.NewPointer(orig))
+		for i := 0; i < mset.Len(); i++ {
+			m := mset.At(i)
+			if m.Obj().Id() == fn.Id() {
+				gfn = m.Obj()
+				break
+			}
+		}
+	}
+
+	// In golang/go#61196, we observe another crash, this time inexplicable.
+	if gfn == nil {
+		panic(fmt.Sprintf("missing origin method for %s.%s; named == origin: %t, named.NumMethods(): %d, origin.NumMethods(): %d", named, fn, named == orig, named.NumMethods(), orig.NumMethods()))
+	}
+
 	return gfn.(*types.Func)
 }
 
@@ -131,7 +157,7 @@ func OriginMethod(fn *types.Func) *types.Func {
 //
 // In this case, GenericAssignableTo reports that instantiations of Container
 // are assignable to the corresponding instantiation of Interface.
-func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
+func GenericAssignableTo(ctxt *types.Context, V, T types.Type) bool {
 	// If V and T are not both named, or do not have matching non-empty type
 	// parameter lists, fall back on types.AssignableTo.
 
@@ -141,9 +167,9 @@ func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
 		return types.AssignableTo(V, T)
 	}
 
-	vtparams := ForNamed(VN)
-	ttparams := ForNamed(TN)
-	if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || NamedTypeArgs(VN).Len() != 0 || NamedTypeArgs(TN).Len() != 0 {
+	vtparams := VN.TypeParams()
+	ttparams := TN.TypeParams()
+	if vtparams.Len() == 0 || vtparams.Len() != ttparams.Len() || VN.TypeArgs().Len() != 0 || TN.TypeArgs().Len() != 0 {
 		return types.AssignableTo(V, T)
 	}
 
@@ -156,7 +182,7 @@ func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
 	// Minor optimization: ensure we share a context across the two
 	// instantiations below.
 	if ctxt == nil {
-		ctxt = NewContext()
+		ctxt = types.NewContext()
 	}
 
 	var targs []types.Type
@@ -164,12 +190,12 @@ func GenericAssignableTo(ctxt *Context, V, T types.Type) bool {
 		targs = append(targs, vtparams.At(i))
 	}
 
-	vinst, err := Instantiate(ctxt, V, targs, true)
+	vinst, err := types.Instantiate(ctxt, V, targs, true)
 	if err != nil {
 		panic("type parameters should satisfy their own constraints")
 	}
 
-	tinst, err := Instantiate(ctxt, T, targs, true)
+	tinst, err := types.Instantiate(ctxt, T, targs, true)
 	if err != nil {
 		return false
 	}
diff --git a/vendor/golang.org/x/tools/internal/typeparams/coretype.go b/vendor/golang.org/x/tools/internal/typeparams/coretype.go
index 993135ec9..7ea8840ea 100644
--- a/vendor/golang.org/x/tools/internal/typeparams/coretype.go
+++ b/vendor/golang.org/x/tools/internal/typeparams/coretype.go
@@ -81,13 +81,13 @@ func CoreType(T types.Type) types.Type {
 // restrictions may be arbitrarily complex. For example, consider the
 // following:
 //
-//  type A interface{ ~string|~[]byte }
+//	type A interface{ ~string|~[]byte }
 //
-//  type B interface{ int|string }
+//	type B interface{ int|string }
 //
-//  type C interface { ~string|~int }
+//	type C interface { ~string|~int }
 //
-//  type T[P interface{ A|B; C }] int
+//	type T[P interface{ A|B; C }] int
 //
 // In this example, the structural type restriction of P is ~string|int: A|B
 // expands to ~string|~[]byte|int|string, which reduces to ~string|~[]byte|int,
@@ -108,15 +108,15 @@ func CoreType(T types.Type) types.Type {
 //
 // _NormalTerms makes no guarantees about the order of terms, except that it
 // is deterministic.
-func _NormalTerms(typ types.Type) ([]*Term, error) {
+func _NormalTerms(typ types.Type) ([]*types.Term, error) {
 	switch typ := typ.(type) {
-	case *TypeParam:
+	case *types.TypeParam:
 		return StructuralTerms(typ)
-	case *Union:
+	case *types.Union:
 		return UnionTermSet(typ)
 	case *types.Interface:
 		return InterfaceTermSet(typ)
 	default:
-		return []*Term{NewTerm(false, typ)}, nil
+		return []*types.Term{types.NewTerm(false, typ)}, nil
 	}
 }
diff --git a/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go b/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go
deleted file mode 100644
index 18212390e..000000000
--- a/vendor/golang.org/x/tools/internal/typeparams/enabled_go117.go
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.18
-// +build !go1.18
-
-package typeparams
-
-// Enabled reports whether type parameters are enabled in the current build
-// environment.
-const Enabled = false
diff --git a/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go b/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go
deleted file mode 100644
index d67148823..000000000
--- a/vendor/golang.org/x/tools/internal/typeparams/enabled_go118.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.18
-// +build go1.18
-
-package typeparams
-
-// Note: this constant is in a separate file as this is the only acceptable
-// diff between the <1.18 API of this package and the 1.18 API.
-
-// Enabled reports whether type parameters are enabled in the current build
-// environment.
-const Enabled = true
diff --git a/vendor/golang.org/x/tools/internal/typeparams/normalize.go b/vendor/golang.org/x/tools/internal/typeparams/normalize.go
index 9c631b651..93c80fdc9 100644
--- a/vendor/golang.org/x/tools/internal/typeparams/normalize.go
+++ b/vendor/golang.org/x/tools/internal/typeparams/normalize.go
@@ -60,7 +60,7 @@ var ErrEmptyTypeSet = errors.New("empty type set")
 //
 // StructuralTerms makes no guarantees about the order of terms, except that it
 // is deterministic.
-func StructuralTerms(tparam *TypeParam) ([]*Term, error) {
+func StructuralTerms(tparam *types.TypeParam) ([]*types.Term, error) {
 	constraint := tparam.Constraint()
 	if constraint == nil {
 		return nil, fmt.Errorf("%s has nil constraint", tparam)
@@ -78,7 +78,7 @@ func StructuralTerms(tparam *TypeParam) ([]*Term, error) {
 //
 // See the documentation of StructuralTerms for more information on
 // normalization.
-func InterfaceTermSet(iface *types.Interface) ([]*Term, error) {
+func InterfaceTermSet(iface *types.Interface) ([]*types.Term, error) {
 	return computeTermSet(iface)
 }
 
@@ -88,11 +88,11 @@ func InterfaceTermSet(iface *types.Interface) ([]*Term, error) {
 //
 // See the documentation of StructuralTerms for more information on
 // normalization.
-func UnionTermSet(union *Union) ([]*Term, error) {
+func UnionTermSet(union *types.Union) ([]*types.Term, error) {
 	return computeTermSet(union)
 }
 
-func computeTermSet(typ types.Type) ([]*Term, error) {
+func computeTermSet(typ types.Type) ([]*types.Term, error) {
 	tset, err := computeTermSetInternal(typ, make(map[types.Type]*termSet), 0)
 	if err != nil {
 		return nil, err
@@ -103,9 +103,9 @@ func computeTermSet(typ types.Type) ([]*Term, error) {
 	if tset.terms.isAll() {
 		return nil, nil
 	}
-	var terms []*Term
+	var terms []*types.Term
 	for _, term := range tset.terms {
-		terms = append(terms, NewTerm(term.tilde, term.typ))
+		terms = append(terms, types.NewTerm(term.tilde, term.typ))
 	}
 	return terms, nil
 }
@@ -162,7 +162,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in
 		tset.terms = allTermlist
 		for i := 0; i < u.NumEmbeddeds(); i++ {
 			embedded := u.EmbeddedType(i)
-			if _, ok := embedded.Underlying().(*TypeParam); ok {
+			if _, ok := embedded.Underlying().(*types.TypeParam); ok {
 				return nil, fmt.Errorf("invalid embedded type %T", embedded)
 			}
 			tset2, err := computeTermSetInternal(embedded, seen, depth+1)
@@ -171,7 +171,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in
 			}
 			tset.terms = tset.terms.intersect(tset2.terms)
 		}
-	case *Union:
+	case *types.Union:
 		// The term set of a union is the union of term sets of its terms.
 		tset.terms = nil
 		for i := 0; i < u.Len(); i++ {
@@ -184,7 +184,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in
 					return nil, err
 				}
 				terms = tset2.terms
-			case *TypeParam, *Union:
+			case *types.TypeParam, *types.Union:
 				// A stand-alone type parameter or union is not permitted as union
 				// term.
 				return nil, fmt.Errorf("invalid union term %T", t)
@@ -199,7 +199,7 @@ func computeTermSetInternal(t types.Type, seen map[types.Type]*termSet, depth in
 				return nil, fmt.Errorf("exceeded max term count %d", maxTermCount)
 			}
 		}
-	case *TypeParam:
+	case *types.TypeParam:
 		panic("unreachable")
 	default:
 		// For all other types, the term set is just a single non-tilde term
diff --git a/vendor/golang.org/x/tools/internal/typeparams/termlist.go b/vendor/golang.org/x/tools/internal/typeparams/termlist.go
index 933106a23..cbd12f801 100644
--- a/vendor/golang.org/x/tools/internal/typeparams/termlist.go
+++ b/vendor/golang.org/x/tools/internal/typeparams/termlist.go
@@ -30,7 +30,7 @@ func (xl termlist) String() string {
 	var buf bytes.Buffer
 	for i, x := range xl {
 		if i > 0 {
-			buf.WriteString(" ∪ ")
+			buf.WriteString(" | ")
 		}
 		buf.WriteString(x.String())
 	}
diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go
deleted file mode 100644
index b4788978f..000000000
--- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go117.go
+++ /dev/null
@@ -1,197 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build !go1.18
-// +build !go1.18
-
-package typeparams
-
-import (
-	"go/ast"
-	"go/token"
-	"go/types"
-)
-
-func unsupported() {
-	panic("type parameters are unsupported at this go version")
-}
-
-// IndexListExpr is a placeholder type, as type parameters are not supported at
-// this Go version. Its methods panic on use.
-type IndexListExpr struct {
-	ast.Expr
-	X       ast.Expr   // expression
-	Lbrack  token.Pos  // position of "["
-	Indices []ast.Expr // index expressions
-	Rbrack  token.Pos  // position of "]"
-}
-
-// ForTypeSpec returns an empty field list, as type parameters on not supported
-// at this Go version.
-func ForTypeSpec(*ast.TypeSpec) *ast.FieldList {
-	return nil
-}
-
-// ForFuncType returns an empty field list, as type parameters are not
-// supported at this Go version.
-func ForFuncType(*ast.FuncType) *ast.FieldList {
-	return nil
-}
-
-// TypeParam is a placeholder type, as type parameters are not supported at
-// this Go version. Its methods panic on use.
-type TypeParam struct{ types.Type }
-
-func (*TypeParam) Index() int             { unsupported(); return 0 }
-func (*TypeParam) Constraint() types.Type { unsupported(); return nil }
-func (*TypeParam) Obj() *types.TypeName   { unsupported(); return nil }
-
-// TypeParamList is a placeholder for an empty type parameter list.
-type TypeParamList struct{}
-
-func (*TypeParamList) Len() int          { return 0 }
-func (*TypeParamList) At(int) *TypeParam { unsupported(); return nil }
-
-// TypeList is a placeholder for an empty type list.
-type TypeList struct{}
-
-func (*TypeList) Len() int          { return 0 }
-func (*TypeList) At(int) types.Type { unsupported(); return nil }
-
-// NewTypeParam is unsupported at this Go version, and panics.
-func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
-	unsupported()
-	return nil
-}
-
-// SetTypeParamConstraint is unsupported at this Go version, and panics.
-func SetTypeParamConstraint(tparam *TypeParam, constraint types.Type) {
-	unsupported()
-}
-
-// NewSignatureType calls types.NewSignature, panicking if recvTypeParams or
-// typeParams is non-empty.
-func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
-	if len(recvTypeParams) != 0 || len(typeParams) != 0 {
-		panic("signatures cannot have type parameters at this Go version")
-	}
-	return types.NewSignature(recv, params, results, variadic)
-}
-
-// ForSignature returns an empty slice.
-func ForSignature(*types.Signature) *TypeParamList {
-	return nil
-}
-
-// RecvTypeParams returns a nil slice.
-func RecvTypeParams(sig *types.Signature) *TypeParamList {
-	return nil
-}
-
-// IsComparable returns false, as no interfaces are type-restricted at this Go
-// version.
-func IsComparable(*types.Interface) bool {
-	return false
-}
-
-// IsMethodSet returns true, as no interfaces are type-restricted at this Go
-// version.
-func IsMethodSet(*types.Interface) bool {
-	return true
-}
-
-// IsImplicit returns false, as no interfaces are implicit at this Go version.
-func IsImplicit(*types.Interface) bool {
-	return false
-}
-
-// MarkImplicit does nothing, because this Go version does not have implicit
-// interfaces.
-func MarkImplicit(*types.Interface) {}
-
-// ForNamed returns an empty type parameter list, as type parameters are not
-// supported at this Go version.
-func ForNamed(*types.Named) *TypeParamList {
-	return nil
-}
-
-// SetForNamed panics if tparams is non-empty.
-func SetForNamed(_ *types.Named, tparams []*TypeParam) {
-	if len(tparams) > 0 {
-		unsupported()
-	}
-}
-
-// NamedTypeArgs returns nil.
-func NamedTypeArgs(*types.Named) *TypeList {
-	return nil
-}
-
-// NamedTypeOrigin is the identity method at this Go version.
-func NamedTypeOrigin(named *types.Named) types.Type {
-	return named
-}
-
-// Term holds information about a structural type restriction.
-type Term struct {
-	tilde bool
-	typ   types.Type
-}
-
-func (m *Term) Tilde() bool      { return m.tilde }
-func (m *Term) Type() types.Type { return m.typ }
-func (m *Term) String() string {
-	pre := ""
-	if m.tilde {
-		pre = "~"
-	}
-	return pre + m.typ.String()
-}
-
-// NewTerm is unsupported at this Go version, and panics.
-func NewTerm(tilde bool, typ types.Type) *Term {
-	return &Term{tilde, typ}
-}
-
-// Union is a placeholder type, as type parameters are not supported at this Go
-// version. Its methods panic on use.
-type Union struct{ types.Type }
-
-func (*Union) Len() int         { return 0 }
-func (*Union) Term(i int) *Term { unsupported(); return nil }
-
-// NewUnion is unsupported at this Go version, and panics.
-func NewUnion(terms []*Term) *Union {
-	unsupported()
-	return nil
-}
-
-// InitInstanceInfo is a noop at this Go version.
-func InitInstanceInfo(*types.Info) {}
-
-// Instance is a placeholder type, as type parameters are not supported at this
-// Go version.
-type Instance struct {
-	TypeArgs *TypeList
-	Type     types.Type
-}
-
-// GetInstances returns a nil map, as type parameters are not supported at this
-// Go version.
-func GetInstances(info *types.Info) map[*ast.Ident]Instance { return nil }
-
-// Context is a placeholder type, as type parameters are not supported at
-// this Go version.
-type Context struct{}
-
-// NewContext returns a placeholder Context instance.
-func NewContext() *Context {
-	return &Context{}
-}
-
-// Instantiate is unsupported on this Go version, and panics.
-func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
-	unsupported()
-	return nil, nil
-}
diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go b/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go
deleted file mode 100644
index 114a36b86..000000000
--- a/vendor/golang.org/x/tools/internal/typeparams/typeparams_go118.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2021 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-//go:build go1.18
-// +build go1.18
-
-package typeparams
-
-import (
-	"go/ast"
-	"go/types"
-)
-
-// IndexListExpr is an alias for ast.IndexListExpr.
-type IndexListExpr = ast.IndexListExpr
-
-// ForTypeSpec returns n.TypeParams.
-func ForTypeSpec(n *ast.TypeSpec) *ast.FieldList {
-	if n == nil {
-		return nil
-	}
-	return n.TypeParams
-}
-
-// ForFuncType returns n.TypeParams.
-func ForFuncType(n *ast.FuncType) *ast.FieldList {
-	if n == nil {
-		return nil
-	}
-	return n.TypeParams
-}
-
-// TypeParam is an alias for types.TypeParam
-type TypeParam = types.TypeParam
-
-// TypeParamList is an alias for types.TypeParamList
-type TypeParamList = types.TypeParamList
-
-// TypeList is an alias for types.TypeList
-type TypeList = types.TypeList
-
-// NewTypeParam calls types.NewTypeParam.
-func NewTypeParam(name *types.TypeName, constraint types.Type) *TypeParam {
-	return types.NewTypeParam(name, constraint)
-}
-
-// SetTypeParamConstraint calls tparam.SetConstraint(constraint).
-func SetTypeParamConstraint(tparam *TypeParam, constraint types.Type) {
-	tparam.SetConstraint(constraint)
-}
-
-// NewSignatureType calls types.NewSignatureType.
-func NewSignatureType(recv *types.Var, recvTypeParams, typeParams []*TypeParam, params, results *types.Tuple, variadic bool) *types.Signature {
-	return types.NewSignatureType(recv, recvTypeParams, typeParams, params, results, variadic)
-}
-
-// ForSignature returns sig.TypeParams()
-func ForSignature(sig *types.Signature) *TypeParamList {
-	return sig.TypeParams()
-}
-
-// RecvTypeParams returns sig.RecvTypeParams().
-func RecvTypeParams(sig *types.Signature) *TypeParamList {
-	return sig.RecvTypeParams()
-}
-
-// IsComparable calls iface.IsComparable().
-func IsComparable(iface *types.Interface) bool {
-	return iface.IsComparable()
-}
-
-// IsMethodSet calls iface.IsMethodSet().
-func IsMethodSet(iface *types.Interface) bool {
-	return iface.IsMethodSet()
-}
-
-// IsImplicit calls iface.IsImplicit().
-func IsImplicit(iface *types.Interface) bool {
-	return iface.IsImplicit()
-}
-
-// MarkImplicit calls iface.MarkImplicit().
-func MarkImplicit(iface *types.Interface) {
-	iface.MarkImplicit()
-}
-
-// ForNamed extracts the (possibly empty) type parameter object list from
-// named.
-func ForNamed(named *types.Named) *TypeParamList {
-	return named.TypeParams()
-}
-
-// SetForNamed sets the type params tparams on n. Each tparam must be of
-// dynamic type *types.TypeParam.
-func SetForNamed(n *types.Named, tparams []*TypeParam) {
-	n.SetTypeParams(tparams)
-}
-
-// NamedTypeArgs returns named.TypeArgs().
-func NamedTypeArgs(named *types.Named) *TypeList {
-	return named.TypeArgs()
-}
-
-// NamedTypeOrigin returns named.Orig().
-func NamedTypeOrigin(named *types.Named) types.Type {
-	return named.Origin()
-}
-
-// Term is an alias for types.Term.
-type Term = types.Term
-
-// NewTerm calls types.NewTerm.
-func NewTerm(tilde bool, typ types.Type) *Term {
-	return types.NewTerm(tilde, typ)
-}
-
-// Union is an alias for types.Union
-type Union = types.Union
-
-// NewUnion calls types.NewUnion.
-func NewUnion(terms []*Term) *Union {
-	return types.NewUnion(terms)
-}
-
-// InitInstanceInfo initializes info to record information about type and
-// function instances.
-func InitInstanceInfo(info *types.Info) {
-	info.Instances = make(map[*ast.Ident]types.Instance)
-}
-
-// Instance is an alias for types.Instance.
-type Instance = types.Instance
-
-// GetInstances returns info.Instances.
-func GetInstances(info *types.Info) map[*ast.Ident]Instance {
-	return info.Instances
-}
-
-// Context is an alias for types.Context.
-type Context = types.Context
-
-// NewContext calls types.NewContext.
-func NewContext() *Context {
-	return types.NewContext()
-}
-
-// Instantiate calls types.Instantiate.
-func Instantiate(ctxt *Context, typ types.Type, targs []types.Type, validate bool) (types.Type, error) {
-	return types.Instantiate(ctxt, typ, targs, validate)
-}
diff --git a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go
index 7ddee28d9..7350bb702 100644
--- a/vendor/golang.org/x/tools/internal/typeparams/typeterm.go
+++ b/vendor/golang.org/x/tools/internal/typeparams/typeterm.go
@@ -10,11 +10,10 @@ import "go/types"
 
 // A term describes elementary type sets:
 //
-//   ∅:  (*term)(nil)     == ∅                      // set of no types (empty set)
-//   𝓤:  &term{}          == 𝓤                      // set of all types (𝓤niverse)
-//   T:  &term{false, T}  == {T}                    // set of type T
-//  ~t:  &term{true, t}   == {t' | under(t') == t}  // set of types with underlying type t
-//
+//	 ∅:  (*term)(nil)     == ∅                      // set of no types (empty set)
+//	 𝓤:  &term{}          == 𝓤                      // set of all types (𝓤niverse)
+//	 T:  &term{false, T}  == {T}                    // set of type T
+//	~t:  &term{true, t}   == {t' | under(t') == t}  // set of types with underlying type t
 type term struct {
 	tilde bool // valid if typ != nil
 	typ   types.Type
diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go
index 3c53fbc63..ce7d4351b 100644
--- a/vendor/golang.org/x/tools/internal/typesinternal/types.go
+++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go
@@ -11,8 +11,6 @@ import (
 	"go/types"
 	"reflect"
 	"unsafe"
-
-	"golang.org/x/tools/go/types/objectpath"
 )
 
 func SetUsesCgo(conf *types.Config) bool {
@@ -52,10 +50,3 @@ func ReadGo116ErrorData(err types.Error) (code ErrorCode, start, end token.Pos,
 }
 
 var SetGoVersion = func(conf *types.Config, version string) bool { return false }
-
-// NewObjectpathEncoder returns a function closure equivalent to
-// objectpath.For but amortized for multiple (sequential) calls.
-// It is a temporary workaround, pending the approval of proposal 58668.
-//
-//go:linkname NewObjectpathFunc golang.org/x/tools/go/types/objectpath.newEncoderFor
-func NewObjectpathFunc() func(types.Object) (objectpath.Path, error)
diff --git a/vendor/golang.org/x/tools/internal/versions/gover.go b/vendor/golang.org/x/tools/internal/versions/gover.go
new file mode 100644
index 000000000..bbabcd22e
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/versions/gover.go
@@ -0,0 +1,172 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This is a fork of internal/gover for use by x/tools until
+// go1.21 and earlier are no longer supported by x/tools.
+
+package versions
+
+import "strings"
+
+// A gover is a parsed Go gover: major[.Minor[.Patch]][kind[pre]]
+// The numbers are the original decimal strings to avoid integer overflows
+// and since there is very little actual math. (Probably overflow doesn't matter in practice,
+// but at the time this code was written, there was an existing test that used
+// go1.99999999999, which does not fit in an int on 32-bit platforms.
+// The "big decimal" representation avoids the problem entirely.)
+type gover struct {
+	major string // decimal
+	minor string // decimal or ""
+	patch string // decimal or ""
+	kind  string // "", "alpha", "beta", "rc"
+	pre   string // decimal or ""
+}
+
+// compare returns -1, 0, or +1 depending on whether
+// x < y, x == y, or x > y, interpreted as toolchain versions.
+// The versions x and y must not begin with a "go" prefix: just "1.21" not "go1.21".
+// Malformed versions compare less than well-formed versions and equal to each other.
+// The language version "1.21" compares less than the release candidate and eventual releases "1.21rc1" and "1.21.0".
+func compare(x, y string) int {
+	vx := parse(x)
+	vy := parse(y)
+
+	if c := cmpInt(vx.major, vy.major); c != 0 {
+		return c
+	}
+	if c := cmpInt(vx.minor, vy.minor); c != 0 {
+		return c
+	}
+	if c := cmpInt(vx.patch, vy.patch); c != 0 {
+		return c
+	}
+	if c := strings.Compare(vx.kind, vy.kind); c != 0 { // "" < alpha < beta < rc
+		return c
+	}
+	if c := cmpInt(vx.pre, vy.pre); c != 0 {
+		return c
+	}
+	return 0
+}
+
+// lang returns the Go language version. For example, lang("1.2.3") == "1.2".
+func lang(x string) string {
+	v := parse(x)
+	if v.minor == "" || v.major == "1" && v.minor == "0" {
+		return v.major
+	}
+	return v.major + "." + v.minor
+}
+
+// isValid reports whether the version x is valid.
+func isValid(x string) bool {
+	return parse(x) != gover{}
+}
+
+// parse parses the Go version string x into a version.
+// It returns the zero version if x is malformed.
+func parse(x string) gover {
+	var v gover
+
+	// Parse major version.
+	var ok bool
+	v.major, x, ok = cutInt(x)
+	if !ok {
+		return gover{}
+	}
+	if x == "" {
+		// Interpret "1" as "1.0.0".
+		v.minor = "0"
+		v.patch = "0"
+		return v
+	}
+
+	// Parse . before minor version.
+	if x[0] != '.' {
+		return gover{}
+	}
+
+	// Parse minor version.
+	v.minor, x, ok = cutInt(x[1:])
+	if !ok {
+		return gover{}
+	}
+	if x == "" {
+		// Patch missing is same as "0" for older versions.
+		// Starting in Go 1.21, patch missing is different from explicit .0.
+		if cmpInt(v.minor, "21") < 0 {
+			v.patch = "0"
+		}
+		return v
+	}
+
+	// Parse patch if present.
+	if x[0] == '.' {
+		v.patch, x, ok = cutInt(x[1:])
+		if !ok || x != "" {
+			// Note that we are disallowing prereleases (alpha, beta, rc) for patch releases here (x != "").
+			// Allowing them would be a bit confusing because we already have:
+			//	1.21 < 1.21rc1
+			// But a prerelease of a patch would have the opposite effect:
+			//	1.21.3rc1 < 1.21.3
+			// We've never needed them before, so let's not start now.
+			return gover{}
+		}
+		return v
+	}
+
+	// Parse prerelease.
+	i := 0
+	for i < len(x) && (x[i] < '0' || '9' < x[i]) {
+		if x[i] < 'a' || 'z' < x[i] {
+			return gover{}
+		}
+		i++
+	}
+	if i == 0 {
+		return gover{}
+	}
+	v.kind, x = x[:i], x[i:]
+	if x == "" {
+		return v
+	}
+	v.pre, x, ok = cutInt(x)
+	if !ok || x != "" {
+		return gover{}
+	}
+
+	return v
+}
+
+// cutInt scans the leading decimal number at the start of x to an integer
+// and returns that value and the rest of the string.
+func cutInt(x string) (n, rest string, ok bool) {
+	i := 0
+	for i < len(x) && '0' <= x[i] && x[i] <= '9' {
+		i++
+	}
+	if i == 0 || x[0] == '0' && i != 1 { // no digits or unnecessary leading zero
+		return "", "", false
+	}
+	return x[:i], x[i:], true
+}
+
+// cmpInt returns cmp.Compare(x, y) interpreting x and y as decimal numbers.
+// (Copied from golang.org/x/mod/semver's compareInt.)
+func cmpInt(x, y string) int {
+	if x == y {
+		return 0
+	}
+	if len(x) < len(y) {
+		return -1
+	}
+	if len(x) > len(y) {
+		return +1
+	}
+	if x < y {
+		return -1
+	} else {
+		return +1
+	}
+}
diff --git a/vendor/golang.org/x/tools/internal/versions/types.go b/vendor/golang.org/x/tools/internal/versions/types.go
new file mode 100644
index 000000000..562eef21f
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/versions/types.go
@@ -0,0 +1,19 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package versions
+
+import (
+	"go/types"
+)
+
+// GoVersion returns the Go version of the type package.
+// It returns zero if no version can be determined.
+func GoVersion(pkg *types.Package) string {
+	// TODO(taking): x/tools can call GoVersion() [from 1.21] after 1.25.
+	if pkg, ok := any(pkg).(interface{ GoVersion() string }); ok {
+		return pkg.GoVersion()
+	}
+	return ""
+}
diff --git a/vendor/golang.org/x/tools/internal/versions/types_go121.go b/vendor/golang.org/x/tools/internal/versions/types_go121.go
new file mode 100644
index 000000000..a7b79207a
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/versions/types_go121.go
@@ -0,0 +1,20 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build !go1.22
+// +build !go1.22
+
+package versions
+
+import (
+	"go/ast"
+	"go/types"
+)
+
+// FileVersions always reports the a file's Go version as the
+// zero version at this Go version.
+func FileVersions(info *types.Info, file *ast.File) string { return "" }
+
+// InitFileVersions is a noop at this Go version.
+func InitFileVersions(*types.Info) {}
diff --git a/vendor/golang.org/x/tools/internal/versions/types_go122.go b/vendor/golang.org/x/tools/internal/versions/types_go122.go
new file mode 100644
index 000000000..7b9ba89a8
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/versions/types_go122.go
@@ -0,0 +1,24 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+//go:build go1.22
+// +build go1.22
+
+package versions
+
+import (
+	"go/ast"
+	"go/types"
+)
+
+// FileVersions maps a file to the file's semantic Go version.
+// The reported version is the zero version if a version cannot be determined.
+func FileVersions(info *types.Info, file *ast.File) string {
+	return info.FileVersions[file]
+}
+
+// InitFileVersions initializes info to record Go versions for Go files.
+func InitFileVersions(info *types.Info) {
+	info.FileVersions = make(map[*ast.File]string)
+}
diff --git a/vendor/golang.org/x/tools/internal/versions/versions.go b/vendor/golang.org/x/tools/internal/versions/versions.go
new file mode 100644
index 000000000..e16f6c33a
--- /dev/null
+++ b/vendor/golang.org/x/tools/internal/versions/versions.go
@@ -0,0 +1,52 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package versions
+
+// Note: If we use build tags to use go/versions when go >=1.22,
+// we run into go.dev/issue/53737. Under some operations users would see an
+// import of "go/versions" even if they would not compile the file.
+// For example, during `go get -u ./...` (go.dev/issue/64490) we do not try to include
+// For this reason, this library just a clone of go/versions for the moment.
+
+// Lang returns the Go language version for version x.
+// If x is not a valid version, Lang returns the empty string.
+// For example:
+//
+//	Lang("go1.21rc2") = "go1.21"
+//	Lang("go1.21.2") = "go1.21"
+//	Lang("go1.21") = "go1.21"
+//	Lang("go1") = "go1"
+//	Lang("bad") = ""
+//	Lang("1.21") = ""
+func Lang(x string) string {
+	v := lang(stripGo(x))
+	if v == "" {
+		return ""
+	}
+	return x[:2+len(v)] // "go"+v without allocation
+}
+
+// Compare returns -1, 0, or +1 depending on whether
+// x < y, x == y, or x > y, interpreted as Go versions.
+// The versions x and y must begin with a "go" prefix: "go1.21" not "1.21".
+// Invalid versions, including the empty string, compare less than
+// valid versions and equal to each other.
+// The language version "go1.21" compares less than the
+// release candidate and eventual releases "go1.21rc1" and "go1.21.0".
+// Custom toolchain suffixes are ignored during comparison:
+// "go1.21.0" and "go1.21.0-bigcorp" are equal.
+func Compare(x, y string) int { return compare(stripGo(x), stripGo(y)) }
+
+// IsValid reports whether the version x is valid.
+func IsValid(x string) bool { return isValid(stripGo(x)) }
+
+// stripGo converts from a "go1.21" version to a "1.21" version.
+// If v does not start with "go", stripGo returns the empty string (a known invalid version).
+func stripGo(v string) string {
+	if len(v) < 2 || v[:2] != "go" {
+		return ""
+	}
+	return v[2:]
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f3dcad020..0e88531bf 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -209,7 +209,7 @@ github.com/letsencrypt/validator/v10
 # github.com/matttproud/golang_protobuf_extensions v1.0.4
 ## explicit; go 1.9
 github.com/matttproud/golang_protobuf_extensions/pbutil
-# github.com/miekg/dns v1.1.55
+# github.com/miekg/dns v1.1.58
 ## explicit; go 1.19
 github.com/miekg/dns
 # github.com/miekg/pkcs11 v1.1.1
@@ -350,8 +350,8 @@ golang.org/x/crypto/cryptobyte/asn1
 golang.org/x/crypto/ed25519
 golang.org/x/crypto/ocsp
 golang.org/x/crypto/pbkdf2
-# golang.org/x/mod v0.10.0
-## explicit; go 1.17
+# golang.org/x/mod v0.14.0
+## explicit; go 1.18
 golang.org/x/mod/semver
 # golang.org/x/net v0.20.0
 ## explicit; go 1.18
@@ -368,13 +368,12 @@ golang.org/x/net/internal/timeseries
 golang.org/x/net/ipv4
 golang.org/x/net/ipv6
 golang.org/x/net/trace
-# golang.org/x/sync v0.4.0
-## explicit; go 1.17
+# golang.org/x/sync v0.6.0
+## explicit; go 1.18
 golang.org/x/sync/errgroup
 golang.org/x/sync/semaphore
 # golang.org/x/sys v0.16.0
 ## explicit; go 1.18
-golang.org/x/sys/execabs
 golang.org/x/sys/plan9
 golang.org/x/sys/unix
 golang.org/x/sys/windows
@@ -388,7 +387,7 @@ golang.org/x/text/secure/bidirule
 golang.org/x/text/transform
 golang.org/x/text/unicode/bidi
 golang.org/x/text/unicode/norm
-# golang.org/x/tools v0.8.0
+# golang.org/x/tools v0.17.0
 ## explicit; go 1.18
 golang.org/x/tools/go/gcexportdata
 golang.org/x/tools/go/internal/packagesdriver
@@ -398,6 +397,7 @@ golang.org/x/tools/internal/event
 golang.org/x/tools/internal/event/core
 golang.org/x/tools/internal/event/keys
 golang.org/x/tools/internal/event/label
+golang.org/x/tools/internal/event/tag
 golang.org/x/tools/internal/gcimporter
 golang.org/x/tools/internal/gocommand
 golang.org/x/tools/internal/packagesinternal
@@ -405,6 +405,7 @@ golang.org/x/tools/internal/pkgbits
 golang.org/x/tools/internal/tokeninternal
 golang.org/x/tools/internal/typeparams
 golang.org/x/tools/internal/typesinternal
+golang.org/x/tools/internal/versions
 # google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97
 ## explicit; go 1.19
 google.golang.org/genproto/googleapis/api/httpbody
diff --git a/wfe2/wfe.go b/wfe2/wfe.go
index e3be4348c..3436e405b 100644
--- a/wfe2/wfe.go
+++ b/wfe2/wfe.go
@@ -170,6 +170,7 @@ type WebFrontEndImpl struct {
 	pendingAuthorizationLifetime time.Duration
 	limiter                      *ratelimits.Limiter
 	txnBuilder                   *ratelimits.TransactionBuilder
+	maxNames                     int
 }
 
 // NewWebFrontEndImpl constructs a web service for Boulder
@@ -193,6 +194,7 @@ func NewWebFrontEndImpl(
 	accountGetter AccountGetter,
 	limiter *ratelimits.Limiter,
 	txnBuilder *ratelimits.TransactionBuilder,
+	maxNames int,
 ) (WebFrontEndImpl, error) {
 	if len(issuerCertificates) == 0 {
 		return WebFrontEndImpl{}, errors.New("must provide at least one issuer certificate")
@@ -231,6 +233,7 @@ func NewWebFrontEndImpl(
 		accountGetter:                accountGetter,
 		limiter:                      limiter,
 		txnBuilder:                   txnBuilder,
+		maxNames:                     maxNames,
 	}
 
 	return wfe, nil
@@ -2041,6 +2044,81 @@ func (wfe *WebFrontEndImpl) orderToOrderJSON(request *http.Request, order *corep
 	return respObj
 }
 
+func (wfe *WebFrontEndImpl) newNewOrderLimitTransactions(regId int64, names []string) []ratelimits.Transaction {
+	if wfe.limiter == nil && wfe.txnBuilder == nil {
+		// Limiter is disabled.
+		return nil
+	}
+
+	logTxnErr := func(err error, limit ratelimits.Name) {
+		// TODO(#5545): Once key-value rate limits are authoritative this log
+		// line should be removed in favor of returning the error.
+		wfe.log.Errf("constructing rate limit transaction for %s rate limit: %s", limit, err)
+	}
+
+	var transactions []ratelimits.Transaction
+	txn, err := wfe.txnBuilder.OrdersPerAccountTransaction(regId)
+	if err != nil {
+		logTxnErr(err, ratelimits.NewOrdersPerAccount)
+		return nil
+	}
+	transactions = append(transactions, txn)
+
+	txn, err = wfe.txnBuilder.FailedAuthorizationsPerAccountCheckOnlyTransaction(regId)
+	if err != nil {
+		logTxnErr(err, ratelimits.FailedAuthorizationsPerAccount)
+		return nil
+	}
+	transactions = append(transactions, txn)
+
+	txns, err := wfe.txnBuilder.CertificatesPerDomainTransactions(regId, names, wfe.maxNames)
+	if err != nil {
+		logTxnErr(err, ratelimits.CertificatesPerDomain)
+		return nil
+	}
+	transactions = append(transactions, txns...)
+
+	txn, err = wfe.txnBuilder.CertificatesPerFQDNSetTransaction(names)
+	if err != nil {
+		logTxnErr(err, ratelimits.CertificatesPerFQDNSet)
+		return nil
+	}
+	return append(transactions, txn)
+}
+
+// checkNewOrderLimits checks whether sufficient limit quota exists for the
+// creation of a new order. If so, that quota is spent. If an error is
+// encountered during the check, it is logged but not returned.
+//
+// TODO(#5545): For now we're simply exercising the new rate limiter codepath.
+// This should eventually return a berrors.RateLimit error containing the retry
+// after duration among other information available in the ratelimits.Decision.
+func (wfe *WebFrontEndImpl) checkNewOrderLimits(ctx context.Context, transactions []ratelimits.Transaction) {
+	if wfe.limiter == nil && wfe.txnBuilder == nil {
+		// Limiter is disabled.
+		return
+	}
+
+	_, err := wfe.limiter.BatchSpend(ctx, transactions)
+	if err != nil {
+		if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+			return
+		}
+		wfe.log.Errf("checking newOrder limits: %s", err)
+	}
+}
+
+func (wfe *WebFrontEndImpl) refundNewOrderLimits(ctx context.Context, transactions []ratelimits.Transaction) {
+	if wfe.limiter == nil || wfe.txnBuilder == nil {
+		return
+	}
+
+	_, err := wfe.limiter.BatchRefund(ctx, transactions)
+	if err != nil {
+		wfe.log.Errf("refunding newOrder limits: %s", err)
+	}
+}
+
 // NewOrder is used by clients to create a new order object and a set of
 // authorizations to fulfill for issuance.
 func (wfe *WebFrontEndImpl) NewOrder(
@@ -2115,6 +2193,24 @@ func (wfe *WebFrontEndImpl) NewOrder(
 
 	logEvent.DNSNames = names
 
+	// TODO(#5545): Spending and Refunding can be async until these rate limits
+	// are authoritative. This saves us from adding latency to each request.
+	// Goroutines spun out below will respect a context deadline set by the
+	// ratelimits package and cannot be prematurely canceled by the requester.
+	txns := wfe.newNewOrderLimitTransactions(acct.ID, names)
+	go wfe.checkNewOrderLimits(ctx, txns)
+
+	var newOrderSuccessful bool
+	var errIsRateLimit bool
+	defer func() {
+		if !newOrderSuccessful && !errIsRateLimit {
+			// This can be a little racy, but we're not going to worry about it
+			// for now. If the check hasn't completed yet, we can pretty safely
+			// assume that the refund will be similarly delayed.
+			go wfe.refundNewOrderLimits(ctx, txns)
+		}
+	}()
+
 	order, err := wfe.ra.NewOrder(ctx, &rapb.NewOrderRequest{
 		RegistrationID: acct.ID,
 		Names:          names,
@@ -2122,6 +2218,15 @@ func (wfe *WebFrontEndImpl) NewOrder(
 	// TODO(#7153): Check each value via core.IsAnyNilOrZero
 	if err != nil || order == nil || order.Id == 0 || order.RegistrationID == 0 || len(order.Names) == 0 || core.IsAnyNilOrZero(order.Created, order.Expires) {
 		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error creating new order"), err)
+		if errors.Is(err, berrors.RateLimit) {
+			// Request was denied by a legacy rate limit. In this error case we
+			// do not want to refund the quota consumed by the request because
+			// repeated requests would result in unearned refunds.
+			//
+			// TODO(#5545): Once key-value rate limits are authoritative this
+			// can be removed.
+			errIsRateLimit = true
+		}
 		return
 	}
 	logEvent.Created = fmt.Sprintf("%d", order.Id)
@@ -2136,6 +2241,7 @@ func (wfe *WebFrontEndImpl) NewOrder(
 		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling order"), err)
 		return
 	}
+	newOrderSuccessful = true
 }
 
 // GetOrder is used to retrieve a existing order object



Test patch:
diff --git a/bdns/dns_test.go b/bdns/dns_test.go
index 3f0636bed..4b5ad453e 100644
--- a/bdns/dns_test.go
+++ b/bdns/dns_test.go
@@ -9,18 +9,20 @@ import (
 	"net/url"
 	"os"
 	"regexp"
+	"slices"
 	"strings"
 	"sync"
 	"testing"
 	"time"
 
 	"github.com/jmhodges/clock"
+	"github.com/miekg/dns"
+	"github.com/prometheus/client_golang/prometheus"
+
 	"github.com/letsencrypt/boulder/features"
 	blog "github.com/letsencrypt/boulder/log"
 	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/test"
-	"github.com/miekg/dns"
-	"github.com/prometheus/client_golang/prometheus"
 )
 
 const dnsLoopbackAddr = "127.0.0.1:4053"
@@ -252,13 +254,14 @@ func TestDNSNoServers(t *testing.T) {
 
 	obj := NewTest(time.Hour, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 
-	_, err = obj.LookupHost(context.Background(), "letsencrypt.org")
+	_, resolvers, err := obj.LookupHost(context.Background(), "letsencrypt.org")
+	test.AssertEquals(t, len(resolvers), 0)
 	test.AssertError(t, err, "No servers")
 
-	_, err = obj.LookupTXT(context.Background(), "letsencrypt.org")
+	_, _, err = obj.LookupTXT(context.Background(), "letsencrypt.org")
 	test.AssertError(t, err, "No servers")
 
-	_, _, err = obj.LookupCAA(context.Background(), "letsencrypt.org")
+	_, _, _, err = obj.LookupCAA(context.Background(), "letsencrypt.org")
 	test.AssertError(t, err, "No servers")
 }
 
@@ -268,8 +271,10 @@ func TestDNSOneServer(t *testing.T) {
 
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 
-	_, err = obj.LookupHost(context.Background(), "cps.letsencrypt.org")
-
+	_, resolvers, err := obj.LookupHost(context.Background(), "cps.letsencrypt.org")
+	test.AssertEquals(t, len(resolvers), 2)
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 	test.AssertNotError(t, err, "No message")
 }
 
@@ -279,8 +284,10 @@ func TestDNSDuplicateServers(t *testing.T) {
 
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 
-	_, err = obj.LookupHost(context.Background(), "cps.letsencrypt.org")
-
+	_, resolvers, err := obj.LookupHost(context.Background(), "cps.letsencrypt.org")
+	test.AssertEquals(t, len(resolvers), 2)
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 	test.AssertNotError(t, err, "No message")
 }
 
@@ -291,13 +298,13 @@ func TestDNSServFail(t *testing.T) {
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 	bad := "servfail.com"
 
-	_, err = obj.LookupTXT(context.Background(), bad)
+	_, _, err = obj.LookupTXT(context.Background(), bad)
 	test.AssertError(t, err, "LookupTXT didn't return an error")
 
-	_, err = obj.LookupHost(context.Background(), bad)
+	_, _, err = obj.LookupHost(context.Background(), bad)
 	test.AssertError(t, err, "LookupHost didn't return an error")
 
-	emptyCaa, _, err := obj.LookupCAA(context.Background(), bad)
+	emptyCaa, _, _, err := obj.LookupCAA(context.Background(), bad)
 	test.Assert(t, len(emptyCaa) == 0, "Query returned non-empty list of CAA records")
 	test.AssertError(t, err, "LookupCAA should have returned an error")
 }
@@ -308,11 +315,11 @@ func TestDNSLookupTXT(t *testing.T) {
 
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 
-	a, err := obj.LookupTXT(context.Background(), "letsencrypt.org")
+	a, _, err := obj.LookupTXT(context.Background(), "letsencrypt.org")
 	t.Logf("A: %v", a)
 	test.AssertNotError(t, err, "No message")
 
-	a, err = obj.LookupTXT(context.Background(), "split-txt.letsencrypt.org")
+	a, _, err = obj.LookupTXT(context.Background(), "split-txt.letsencrypt.org")
 	t.Logf("A: %v ", a)
 	test.AssertNotError(t, err, "No message")
 	test.AssertEquals(t, len(a), 1)
@@ -325,34 +332,44 @@ func TestDNSLookupHost(t *testing.T) {
 
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 
-	ip, err := obj.LookupHost(context.Background(), "servfail.com")
+	ip, resolvers, err := obj.LookupHost(context.Background(), "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")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
-	ip, err = obj.LookupHost(context.Background(), "nonexistent.letsencrypt.org")
+	ip, resolvers, err = obj.LookupHost(context.Background(), "nonexistent.letsencrypt.org")
 	t.Logf("nonexistent.letsencrypt.org - IP: %s, Err: %s", ip, err)
 	test.AssertError(t, err, "No valid A or AAAA records should error")
 	test.Assert(t, len(ip) == 0, "Should not have IPs")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
 	// Single IPv4 address
-	ip, err = obj.LookupHost(context.Background(), "cps.letsencrypt.org")
+	ip, resolvers, err = obj.LookupHost(context.Background(), "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(context.Background(), "cps.letsencrypt.org")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
+	ip, resolvers, err = obj.LookupHost(context.Background(), "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")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
 	// Single IPv6 address
-	ip, err = obj.LookupHost(context.Background(), "v6.letsencrypt.org")
+	ip, resolvers, err = obj.LookupHost(context.Background(), "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) == 1, "Should not have IPs")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
 	// Both IPv6 and IPv4 address
-	ip, err = obj.LookupHost(context.Background(), "dualstack.letsencrypt.org")
+	ip, resolvers, err = obj.LookupHost(context.Background(), "dualstack.letsencrypt.org")
 	t.Logf("dualstack.letsencrypt.org - IP: %s, Err: %s", ip, err)
 	test.AssertNotError(t, err, "Not an error to exist")
 	test.Assert(t, len(ip) == 2, "Should have 2 IPs")
@@ -360,31 +377,39 @@ func TestDNSLookupHost(t *testing.T) {
 	test.Assert(t, ip[0].To4().Equal(expected), "wrong ipv4 address")
 	expected = net.ParseIP("::1")
 	test.Assert(t, ip[1].To16().Equal(expected), "wrong ipv6 address")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
 	// IPv6 error, IPv4 success
-	ip, err = obj.LookupHost(context.Background(), "v6error.letsencrypt.org")
+	ip, resolvers, err = obj.LookupHost(context.Background(), "v6error.letsencrypt.org")
 	t.Logf("v6error.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 1 IP")
 	expected = net.ParseIP("127.0.0.1")
 	test.Assert(t, ip[0].To4().Equal(expected), "wrong ipv4 address")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
 	// IPv6 success, IPv4 error
-	ip, err = obj.LookupHost(context.Background(), "v4error.letsencrypt.org")
+	ip, resolvers, err = obj.LookupHost(context.Background(), "v4error.letsencrypt.org")
 	t.Logf("v4error.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 1 IP")
 	expected = net.ParseIP("::1")
 	test.Assert(t, ip[0].To16().Equal(expected), "wrong ipv6 address")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 
 	// IPv6 error, IPv4 error
 	// Should return both the IPv4 error (Refused) and the IPv6 error (NotImplemented)
 	hostname := "dualstackerror.letsencrypt.org"
-	ip, err = obj.LookupHost(context.Background(), hostname)
+	ip, resolvers, err = obj.LookupHost(context.Background(), hostname)
 	t.Logf("%s - IP: %s, Err: %s", hostname, ip, err)
 	test.AssertError(t, err, "Should be an error")
 	test.AssertContains(t, err.Error(), "REFUSED looking up A for")
 	test.AssertContains(t, err.Error(), "NOTIMP looking up AAAA for")
+	slices.Sort(resolvers)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
 }
 
 func TestDNSNXDOMAIN(t *testing.T) {
@@ -394,11 +419,11 @@ func TestDNSNXDOMAIN(t *testing.T) {
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 
 	hostname := "nxdomain.letsencrypt.org"
-	_, err = obj.LookupHost(context.Background(), hostname)
+	_, _, err = obj.LookupHost(context.Background(), hostname)
 	test.AssertContains(t, err.Error(), "NXDOMAIN looking up A for")
 	test.AssertContains(t, err.Error(), "NXDOMAIN looking up AAAA for")
 
-	_, err = obj.LookupTXT(context.Background(), hostname)
+	_, _, err = obj.LookupTXT(context.Background(), hostname)
 	expected := Error{dns.TypeTXT, hostname, nil, dns.RcodeNameError, nil}
 	test.AssertDeepEquals(t, err, expected)
 }
@@ -410,9 +435,11 @@ func TestDNSLookupCAA(t *testing.T) {
 	obj := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), 1, blog.UseMock(), nil)
 	removeIDExp := regexp.MustCompile(" id: [[:digit:]]+")
 
-	caas, resp, err := obj.LookupCAA(context.Background(), "bracewel.net")
+	caas, resp, resolvers, err := obj.LookupCAA(context.Background(), "bracewel.net")
 	test.AssertNotError(t, err, "CAA lookup failed")
 	test.Assert(t, len(caas) > 0, "Should have CAA records")
+	test.AssertEquals(t, len(resolvers), 1)
+	test.AssertDeepEquals(t, resolvers, ResolverAddrs{"127.0.0.1:4053"})
 	expectedResp := `;; opcode: QUERY, status: NOERROR, id: XXXX
 ;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
 
@@ -424,21 +451,25 @@ bracewel.net.	0	IN	CAA	1 issue "letsencrypt.org"
 `
 	test.AssertEquals(t, removeIDExp.ReplaceAllString(resp, " id: XXXX"), expectedResp)
 
-	caas, resp, err = obj.LookupCAA(context.Background(), "nonexistent.letsencrypt.org")
+	caas, resp, resolvers, err = obj.LookupCAA(context.Background(), "nonexistent.letsencrypt.org")
 	test.AssertNotError(t, err, "CAA lookup failed")
 	test.Assert(t, len(caas) == 0, "Shouldn't have CAA records")
+	test.AssertEquals(t, resolvers[0], "127.0.0.1:4053")
 	expectedResp = ""
 	test.AssertEquals(t, resp, expectedResp)
 
-	caas, resp, err = obj.LookupCAA(context.Background(), "nxdomain.letsencrypt.org")
+	caas, resp, resolvers, err = obj.LookupCAA(context.Background(), "nxdomain.letsencrypt.org")
+	slices.Sort(resolvers)
 	test.AssertNotError(t, err, "CAA lookup failed")
 	test.Assert(t, len(caas) == 0, "Shouldn't have CAA records")
+	test.AssertEquals(t, resolvers[0], "127.0.0.1:4053")
 	expectedResp = ""
 	test.AssertEquals(t, resp, expectedResp)
 
-	caas, resp, err = obj.LookupCAA(context.Background(), "cname.example.com")
+	caas, resp, resolvers, err = obj.LookupCAA(context.Background(), "cname.example.com")
 	test.AssertNotError(t, err, "CAA lookup failed")
 	test.Assert(t, len(caas) > 0, "Should follow CNAME to find CAA")
+	test.AssertEquals(t, resolvers[0], "127.0.0.1:4053")
 	expectedResp = `;; opcode: QUERY, status: NOERROR, id: XXXX
 ;; flags: qr rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
 
@@ -450,9 +481,10 @@ caa.example.com.	0	IN	CAA	1 issue "letsencrypt.org"
 `
 	test.AssertEquals(t, removeIDExp.ReplaceAllString(resp, " id: XXXX"), expectedResp)
 
-	_, _, err = obj.LookupCAA(context.Background(), "gonetld")
+	_, _, resolvers, err = obj.LookupCAA(context.Background(), "gonetld")
 	test.AssertError(t, err, "should fail for TLD NXDOMAIN")
 	test.AssertContains(t, err.Error(), "NXDOMAIN")
+	test.AssertEquals(t, resolvers[0], "127.0.0.1:4053")
 }
 
 func TestIsPrivateIP(t *testing.T) {
@@ -644,7 +676,7 @@ func TestRetry(t *testing.T) {
 			testClient := NewTest(time.Second*10, staticProvider, metrics.NoopRegisterer, clock.NewFake(), tc.maxTries, blog.UseMock(), nil)
 			dr := testClient.(*impl)
 			dr.dnsClient = tc.te
-			_, err = dr.LookupTXT(context.Background(), "example.com")
+			_, _, err = dr.LookupTXT(context.Background(), "example.com")
 			if err == errTooManyRequests {
 				t.Errorf("#%d, sent more requests than the test case handles", i)
 			}
@@ -677,7 +709,7 @@ func TestRetry(t *testing.T) {
 	dr.dnsClient = &testExchanger{errs: []error{isTempErr, isTempErr, nil}}
 	ctx, cancel := context.WithCancel(context.Background())
 	cancel()
-	_, err = dr.LookupTXT(ctx, "example.com")
+	_, _, err = dr.LookupTXT(ctx, "example.com")
 	if err == nil ||
 		err.Error() != "DNS problem: query timed out (and was canceled) looking up TXT for example.com" {
 		t.Errorf("expected %s, got %s", context.Canceled, err)
@@ -686,7 +718,7 @@ func TestRetry(t *testing.T) {
 	dr.dnsClient = &testExchanger{errs: []error{isTempErr, isTempErr, nil}}
 	ctx, cancel = context.WithTimeout(context.Background(), -10*time.Hour)
 	defer cancel()
-	_, err = dr.LookupTXT(ctx, "example.com")
+	_, _, err = dr.LookupTXT(ctx, "example.com")
 	if err == nil ||
 		err.Error() != "DNS problem: query timed out looking up TXT for example.com" {
 		t.Errorf("expected %s, got %s", context.DeadlineExceeded, err)
@@ -695,7 +727,7 @@ func TestRetry(t *testing.T) {
 	dr.dnsClient = &testExchanger{errs: []error{isTempErr, isTempErr, nil}}
 	ctx, deadlineCancel := context.WithTimeout(context.Background(), -10*time.Hour)
 	deadlineCancel()
-	_, err = dr.LookupTXT(ctx, "example.com")
+	_, _, err = dr.LookupTXT(ctx, "example.com")
 	if err == nil ||
 		err.Error() != "DNS problem: query timed out looking up TXT for example.com" {
 		t.Errorf("expected %s, got %s", context.DeadlineExceeded, err)
@@ -796,7 +828,9 @@ func TestRotateServerOnErr(t *testing.T) {
 	// servers *all* queries should eventually succeed by being retried against
 	// server "[2606:4700:4700::1111]:53".
 	for i := 0; i < maxTries*2; i++ {
-		_, err := client.LookupTXT(context.Background(), "example.com")
+		_, resolvers, err := client.LookupTXT(context.Background(), "example.com")
+		test.AssertEquals(t, len(resolvers), 1)
+		test.AssertEquals(t, resolvers[0], "[2606:4700:4700::1111]:53")
 		// Any errors are unexpected - server "[2606:4700:4700::1111]:53" should
 		// have responded without error.
 		test.AssertNotError(t, err, "Expected no error from eventual retry with functional server")
@@ -852,7 +886,7 @@ func TestDOHMetric(t *testing.T) {
 	test.AssertMetricWithLabelsEquals(t, resolver.timeoutCounter, prometheus.Labels{"qtype": "None", "type": "out of retries", "resolver": "127.0.0.1", "isTLD": "false"}, 0)
 
 	// Trigger the error.
-	_, _ = resolver.exchangeOne(context.Background(), "example.com", 0)
+	_, _, _ = resolver.exchangeOne(context.Background(), "example.com", 0)
 
 	// Now, we should count 1 "out of retries" errors.
 	test.AssertMetricWithLabelsEquals(t, resolver.timeoutCounter, prometheus.Labels{"qtype": "None", "type": "out of retries", "resolver": "127.0.0.1", "isTLD": "false"}, 1)
diff --git a/ca/ca_test.go b/ca/ca_test.go
index 72395ff3e..205e70523 100644
--- a/ca/ca_test.go
+++ b/ca/ca_test.go
@@ -2,7 +2,6 @@ package ca
 
 import (
 	"context"
-	"crypto"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
@@ -32,7 +31,6 @@ import (
 	"github.com/letsencrypt/boulder/features"
 	"github.com/letsencrypt/boulder/goodkey"
 	"github.com/letsencrypt/boulder/issuance"
-	"github.com/letsencrypt/boulder/linter"
 	blog "github.com/letsencrypt/boulder/log"
 	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/must"
@@ -110,6 +108,7 @@ type testCtx struct {
 	pa             core.PolicyAuthority
 	ocsp           *ocspImpl
 	crl            *crlImpl
+	profile        *issuance.Profile
 	certExpiry     time.Duration
 	certBackdate   time.Duration
 	serialPrefix   int
@@ -152,30 +151,8 @@ func (m *mockSA) SetCertificateStatusReady(ctx context.Context, req *sapb.Serial
 	return &emptypb.Empty{}, nil
 }
 
-var caKey crypto.Signer
-var caCert *issuance.Certificate
-var caCert2 *issuance.Certificate
-var caLinter *linter.Linter
-var caLinter2 *linter.Linter
 var ctx = context.Background()
 
-func init() {
-	var err error
-	caCert, caKey, err = issuance.LoadIssuer(issuance.IssuerLoc{
-		File:     caKeyFile,
-		CertFile: caCertFile,
-	})
-	if err != nil {
-		panic(fmt.Sprintf("Unable to load %q and %q: %s", caKeyFile, caCertFile, err))
-	}
-	caCert2, err = issuance.LoadCertificate(caCertFile2)
-	if err != nil {
-		panic(fmt.Sprintf("Unable to parse %q: %s", caCertFile2, err))
-	}
-	caLinter, _ = linter.New(caCert.Certificate, caKey, []string{"w_subject_common_name_included"})
-	caLinter2, _ = linter.New(caCert2.Certificate, caKey, []string{"w_subject_common_name_included"})
-}
-
 func setup(t *testing.T) *testCtx {
 	features.Reset()
 	fc := clock.NewFake()
@@ -186,46 +163,44 @@ func setup(t *testing.T) *testCtx {
 	err = pa.LoadHostnamePolicyFile("../test/hostname-policy.yaml")
 	test.AssertNotError(t, err, "Couldn't set hostname policy")
 
-	boulderProfile := func(rsa, ecdsa bool) *issuance.Profile {
-		res, _ := issuance.NewProfile(
-			issuance.ProfileConfig{
-				AllowMustStaple: true,
-				AllowCTPoison:   true,
-				AllowSCTList:    true,
-				AllowCommonName: true,
-				Policies: []issuance.PolicyConfig{
-					{OID: "2.23.140.1.2.1"},
-				},
-				MaxValidityPeriod:   config.Duration{Duration: time.Hour * 8760},
-				MaxValidityBackdate: config.Duration{Duration: time.Hour},
+	boulderProfile, err := issuance.NewProfile(
+		issuance.ProfileConfig{
+			AllowMustStaple: true,
+			AllowCTPoison:   true,
+			AllowSCTList:    true,
+			AllowCommonName: true,
+			Policies: []issuance.PolicyConfig{
+				{OID: "2.23.140.1.2.1"},
 			},
-			issuance.IssuerConfig{
-				UseForECDSALeaves: ecdsa,
-				UseForRSALeaves:   rsa,
-				IssuerURL:         "http://not-example.com/issuer-url",
-				OCSPURL:           "http://not-example.com/ocsp",
-				CRLURL:            "http://not-example.com/crl",
-			},
-		)
-		return res
-	}
-	boulderIssuers := []*issuance.Issuer{
-		// Must list ECDSA-only issuer first, so it is the default for ECDSA.
-		{
-			Cert:    caCert2,
-			Signer:  caKey,
-			Profile: boulderProfile(false, true),
-			Linter:  caLinter2,
-			Clk:     fc,
+			MaxValidityPeriod:   config.Duration{Duration: time.Hour * 8760},
+			MaxValidityBackdate: config.Duration{Duration: time.Hour},
 		},
-		{
-			Cert:    caCert,
-			Signer:  caKey,
-			Profile: boulderProfile(true, true),
-			Linter:  caLinter,
-			Clk:     fc,
-		},
-	}
+		[]string{"w_subject_common_name_included"},
+	)
+	test.AssertNotError(t, err, "Couldn't create test profile")
+
+	ecdsaOnlyIssuer, err := issuance.LoadIssuer(issuance.IssuerConfig{
+		UseForRSALeaves:   false,
+		UseForECDSALeaves: true,
+		IssuerURL:         "http://not-example.com/issuer-url",
+		OCSPURL:           "http://not-example.com/ocsp",
+		CRLURL:            "http://not-example.com/crl",
+		Location:          issuance.IssuerLoc{File: caKeyFile, CertFile: caCertFile2},
+	}, fc)
+	test.AssertNotError(t, err, "Couldn't load test issuer")
+
+	ecdsaAndRSAIssuer, err := issuance.LoadIssuer(issuance.IssuerConfig{
+		UseForRSALeaves:   true,
+		UseForECDSALeaves: true,
+		IssuerURL:         "http://not-example.com/issuer-url",
+		OCSPURL:           "http://not-example.com/ocsp",
+		CRLURL:            "http://not-example.com/crl",
+		Location:          issuance.IssuerLoc{File: caKeyFile, CertFile: caCertFile},
+	}, fc)
+	test.AssertNotError(t, err, "Couldn't load test issuer")
+
+	// Must list ECDSA-only issuer first, so it is the default for ECDSA.
+	boulderIssuers := []*issuance.Issuer{ecdsaOnlyIssuer, ecdsaAndRSAIssuer}
 
 	keyPolicy := goodkey.KeyPolicy{
 		AllowRSA:           true,
@@ -258,6 +233,7 @@ func setup(t *testing.T) *testCtx {
 
 	crl, err := NewCRLImpl(
 		boulderIssuers,
+		boulderProfile.Lints,
 		time.Hour,
 		"http://c.boulder.test",
 		100,
@@ -269,6 +245,7 @@ func setup(t *testing.T) *testCtx {
 		pa:             pa,
 		ocsp:           ocsp,
 		crl:            crl,
+		profile:        boulderProfile,
 		certExpiry:     8760 * time.Hour,
 		certBackdate:   time.Hour,
 		serialPrefix:   17,
@@ -291,6 +268,7 @@ func TestSerialPrefix(t *testing.T) {
 		nil,
 		nil,
 		nil,
+		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
 		0,
@@ -308,6 +286,7 @@ func TestSerialPrefix(t *testing.T) {
 		nil,
 		nil,
 		nil,
+		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
 		128,
@@ -400,6 +379,7 @@ func issueCertificateSubTestSetup(t *testing.T, e *ECDSAAllowList) (*certificate
 		sa,
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		e,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -445,6 +425,7 @@ func TestNoIssuers(t *testing.T) {
 		sa,
 		testCtx.pa,
 		nil, // No issuers
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -468,6 +449,7 @@ func TestMultipleIssuers(t *testing.T) {
 		sa,
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -481,23 +463,46 @@ func TestMultipleIssuers(t *testing.T) {
 		testCtx.fc)
 	test.AssertNotError(t, err, "Failed to remake CA")
 
-	// Test that an RSA CSR gets issuance from the RSA issuer, caCert.
+	// Test that an RSA CSR gets issuance from the RSA issuer.
 	issuedCert, err := ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: CNandSANCSR, RegistrationID: arbitraryRegID})
 	test.AssertNotError(t, err, "Failed to issue certificate")
 	cert, err := x509.ParseCertificate(issuedCert.DER)
 	test.AssertNotError(t, err, "Certificate failed to parse")
-	err = cert.CheckSignatureFrom(caCert2.Certificate)
+	err = cert.CheckSignatureFrom(testCtx.boulderIssuers[1].Cert.Certificate)
 	test.AssertNotError(t, err, "Certificate failed signature validation")
 
-	// Test that an ECDSA CSR gets issuance from the ECDSA issuer, caCert2.
+	// Test that an ECDSA CSR gets issuance from the ECDSA issuer.
 	issuedCert, err = ca.IssuePrecertificate(ctx, &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID})
 	test.AssertNotError(t, err, "Failed to issue certificate")
 	cert, err = x509.ParseCertificate(issuedCert.DER)
 	test.AssertNotError(t, err, "Certificate failed to parse")
-	err = cert.CheckSignatureFrom(caCert2.Certificate)
+	err = cert.CheckSignatureFrom(testCtx.boulderIssuers[0].Cert.Certificate)
 	test.AssertNotError(t, err, "Certificate failed signature validation")
 }
 
+func TestNoProfile(t *testing.T) {
+	testCtx := setup(t)
+	sa := &mockSA{}
+	_, err := NewCertificateAuthorityImpl(
+		sa,
+		testCtx.pa,
+		testCtx.boulderIssuers,
+		nil, // no profile
+		nil,
+		testCtx.certExpiry,
+		testCtx.certBackdate,
+		testCtx.serialPrefix,
+		testCtx.maxNames,
+		testCtx.keyPolicy,
+		testCtx.logger,
+		testCtx.stats,
+		testCtx.signatureCount,
+		testCtx.signErrorCount,
+		testCtx.fc)
+	test.AssertError(t, err, "No profile found during CA construction.")
+	test.AssertEquals(t, err.Error(), "must have at least one certificate profile")
+}
+
 func TestECDSAAllowList(t *testing.T) {
 	req := &capb.IssueCertificateRequest{Csr: ECDSACSR, RegistrationID: arbitraryRegID}
 
@@ -508,7 +513,7 @@ func TestECDSAAllowList(t *testing.T) {
 	test.AssertNotError(t, err, "Failed to issue certificate")
 	cert, err := x509.ParseCertificate(result.DER)
 	test.AssertNotError(t, err, "Certificate failed to parse")
-	test.AssertByteEquals(t, cert.RawIssuer, caCert2.RawSubject)
+	test.AssertByteEquals(t, cert.RawIssuer, ca.issuers.byAlg[x509.ECDSA].Cert.RawSubject)
 
 	// With allowlist not containing arbitraryRegID, issuance should fall back to RSA issuer.
 	regIDMap = makeRegIDsMap([]int64{2002})
@@ -517,7 +522,7 @@ func TestECDSAAllowList(t *testing.T) {
 	test.AssertNotError(t, err, "Failed to issue certificate")
 	cert, err = x509.ParseCertificate(result.DER)
 	test.AssertNotError(t, err, "Certificate failed to parse")
-	test.AssertByteEquals(t, cert.RawIssuer, caCert.RawSubject)
+	test.AssertByteEquals(t, cert.RawIssuer, ca.issuers.byAlg[x509.RSA].Cert.RawSubject)
 
 	// With empty allowlist but ECDSAForAll enabled, issuance should come from ECDSA issuer.
 	ca, _ = issueCertificateSubTestSetup(t, nil)
@@ -527,7 +532,7 @@ func TestECDSAAllowList(t *testing.T) {
 	test.AssertNotError(t, err, "Failed to issue certificate")
 	cert, err = x509.ParseCertificate(result.DER)
 	test.AssertNotError(t, err, "Certificate failed to parse")
-	test.AssertByteEquals(t, cert.RawIssuer, caCert2.RawSubject)
+	test.AssertByteEquals(t, cert.RawIssuer, ca.issuers.byAlg[x509.ECDSA].Cert.RawSubject)
 }
 
 func TestInvalidCSRs(t *testing.T) {
@@ -589,6 +594,7 @@ func TestInvalidCSRs(t *testing.T) {
 			sa,
 			testCtx.pa,
 			testCtx.boulderIssuers,
+			testCtx.profile,
 			nil,
 			testCtx.certExpiry,
 			testCtx.certBackdate,
@@ -625,6 +631,7 @@ func TestRejectValidityTooLong(t *testing.T) {
 		sa,
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -725,6 +732,7 @@ func TestIssueCertificateForPrecertificate(t *testing.T) {
 		sa,
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -830,6 +838,7 @@ func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
 		sa,
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -871,6 +880,7 @@ func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
 		errorsa,
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
diff --git a/ca/ocsp_test.go b/ca/ocsp_test.go
index 49a1bee8e..f493bc43f 100644
--- a/ca/ocsp_test.go
+++ b/ca/ocsp_test.go
@@ -7,12 +7,13 @@ import (
 	"testing"
 	"time"
 
+	"golang.org/x/crypto/ocsp"
+
 	capb "github.com/letsencrypt/boulder/ca/proto"
 	"github.com/letsencrypt/boulder/core"
 	blog "github.com/letsencrypt/boulder/log"
 	"github.com/letsencrypt/boulder/metrics"
 	"github.com/letsencrypt/boulder/test"
-	"golang.org/x/crypto/ocsp"
 )
 
 func TestImplementationOCSP(t *testing.T) {
@@ -34,6 +35,7 @@ func TestOCSP(t *testing.T) {
 		&mockSA{},
 		testCtx.pa,
 		testCtx.boulderIssuers,
+		testCtx.profile,
 		nil,
 		testCtx.certExpiry,
 		testCtx.certBackdate,
@@ -60,7 +62,7 @@ func TestOCSP(t *testing.T) {
 		Status:   string(core.OCSPStatusGood),
 	})
 	test.AssertNotError(t, err, "Failed to generate OCSP")
-	rsaOCSP, err := ocsp.ParseResponse(rsaOCSPPB.Response, caCert.Certificate)
+	rsaOCSP, err := ocsp.ParseResponse(rsaOCSPPB.Response, testCtx.boulderIssuers[1].Cert.Certificate)
 	test.AssertNotError(t, err, "Failed to parse / validate OCSP for rsaCert")
 	test.AssertEquals(t, rsaOCSP.Status, 0)
 	test.AssertEquals(t, rsaOCSP.RevocationReason, 0)
@@ -78,7 +80,7 @@ func TestOCSP(t *testing.T) {
 		Status:   string(core.OCSPStatusGood),
 	})
 	test.AssertNotError(t, err, "Failed to generate OCSP")
-	ecdsaOCSP, err := ocsp.ParseResponse(ecdsaOCSPPB.Response, caCert2.Certificate)
+	ecdsaOCSP, err := ocsp.ParseResponse(ecdsaOCSPPB.Response, testCtx.boulderIssuers[0].Cert.Certificate)
 	test.AssertNotError(t, err, "Failed to parse / validate OCSP for ecdsaCert")
 	test.AssertEquals(t, ecdsaOCSP.Status, 0)
 	test.AssertEquals(t, ecdsaOCSP.RevocationReason, 0)
diff --git a/core/objects_test.go b/core/objects_test.go
index 57c7831a7..3144ede7d 100644
--- a/core/objects_test.go
+++ b/core/objects_test.go
@@ -40,6 +40,7 @@ func TestRecordSanityCheckOnUnsupportedChallengeType(t *testing.T) {
 			Port:              "80",
 			AddressesResolved: []net.IP{{127, 0, 0, 1}},
 			AddressUsed:       net.IP{127, 0, 0, 1},
+			ResolverAddrs:     []string{"eastUnboundAndDown"},
 		},
 	}
 
diff --git a/crl/checker/checker_test.go b/crl/checker/checker_test.go
index 689c6d91c..d9f192dbf 100644
--- a/crl/checker/checker_test.go
+++ b/crl/checker/checker_test.go
@@ -10,6 +10,8 @@ import (
 	"testing"
 	"time"
 
+	"github.com/jmhodges/clock"
+
 	"github.com/letsencrypt/boulder/core"
 	"github.com/letsencrypt/boulder/issuance"
 	"github.com/letsencrypt/boulder/test"
@@ -52,10 +54,16 @@ func TestValidate(t *testing.T) {
 }
 
 func TestDiff(t *testing.T) {
-	issuer, signer, err := issuance.LoadIssuer(issuance.IssuerLoc{
-		File:     "../../test/hierarchy/int-e1.key.pem",
-		CertFile: "../../test/hierarchy/int-e1.cert.pem",
-	})
+	issuer, err := issuance.LoadIssuer(
+		issuance.IssuerConfig{
+			Location: issuance.IssuerLoc{
+				File:     "../../test/hierarchy/int-e1.key.pem",
+				CertFile: "../../test/hierarchy/int-e1.cert.pem",
+			},
+			IssuerURL: "http://not-example.com/issuer-url",
+			OCSPURL:   "http://not-example.com/ocsp",
+			CRLURL:    "http://not-example.com/crl",
+		}, clock.NewFake())
 	test.AssertNotError(t, err, "loading test issuer")
 
 	now := time.Now()
@@ -75,7 +83,7 @@ func TestDiff(t *testing.T) {
 		},
 	}
 
-	oldCRLDER, err := x509.CreateRevocationList(rand.Reader, &template, issuer.Certificate, signer)
+	oldCRLDER, err := x509.CreateRevocationList(rand.Reader, &template, issuer.Cert.Certificate, issuer.Signer)
 	test.AssertNotError(t, err, "creating old crl")
 	oldCRL, err := x509.ParseRevocationList(oldCRLDER)
 	test.AssertNotError(t, err, "parsing old crl")
@@ -97,7 +105,7 @@ func TestDiff(t *testing.T) {
 		},
 	}
 
-	newCRLDER, err := x509.CreateRevocationList(rand.Reader, &template, issuer.Certificate, signer)
+	newCRLDER, err := x509.CreateRevocationList(rand.Reader, &template, issuer.Cert.Certificate, issuer.Signer)
 	test.AssertNotError(t, err, "creating old crl")
 	newCRL, err := x509.ParseRevocationList(newCRLDER)
 	test.AssertNotError(t, err, "parsing old crl")
diff --git a/crl/storer/storer_test.go b/crl/storer/storer_test.go
index 5df24f37f..c04c2b9cd 100644
--- a/crl/storer/storer_test.go
+++ b/crl/storer/storer_test.go
@@ -57,20 +57,26 @@ func setupTestUploadCRL(t *testing.T) (*crlStorer, *issuance.Issuer) {
 
 	r3, err := issuance.LoadCertificate("../../test/hierarchy/int-r3.cert.pem")
 	test.AssertNotError(t, err, "loading fake RSA issuer cert")
-	e1, e1Signer, err := issuance.LoadIssuer(issuance.IssuerLoc{
-		File:     "../../test/hierarchy/int-e1.key.pem",
-		CertFile: "../../test/hierarchy/int-e1.cert.pem",
-	})
+	issuerE1, err := issuance.LoadIssuer(
+		issuance.IssuerConfig{
+			Location: issuance.IssuerLoc{
+				File:     "../../test/hierarchy/int-e1.key.pem",
+				CertFile: "../../test/hierarchy/int-e1.cert.pem",
+			},
+			IssuerURL: "http://not-example.com/issuer-url",
+			OCSPURL:   "http://not-example.com/ocsp",
+			CRLURL:    "http://not-example.com/crl",
+		}, clock.NewFake())
 	test.AssertNotError(t, err, "loading fake ECDSA issuer cert")
 
 	storer, err := New(
-		[]*issuance.Certificate{r3, e1},
+		[]*issuance.Certificate{r3, issuerE1.Cert},
 		nil, "le-crl.s3.us-west.amazonaws.com",
 		metrics.NoopRegisterer, blog.NewMock(), clock.NewFake(),
 	)
 	test.AssertNotError(t, err, "creating test crl-storer")
 
-	return storer, &issuance.Issuer{Cert: e1, Signer: e1Signer}
+	return storer, issuerE1
 }
 
 // Test that we get an error when no metadata is sent.
diff --git a/grpc/pb-marshalling_test.go b/grpc/pb-marshalling_test.go
index a514683de..6f2876900 100644
--- a/grpc/pb-marshalling_test.go
+++ b/grpc/pb-marshalling_test.go
@@ -121,6 +121,7 @@ func TestValidationRecord(t *testing.T) {
 		AddressUsed:       ip,
 		URL:               "http://exampleA.com",
 		AddressesTried:    []net.IP{ip},
+		ResolverAddrs:     []string{"resolver:5353"},
 	}
 
 	pb, err := ValidationRecordToPB(vr)
@@ -141,6 +142,7 @@ func TestValidationResult(t *testing.T) {
 		AddressUsed:       ip,
 		URL:               "https://exampleA.com",
 		AddressesTried:    []net.IP{ip},
+		ResolverAddrs:     []string{"resolver:5353"},
 	}
 	vrB := core.ValidationRecord{
 		Hostname:          "exampleB.com",
@@ -149,6 +151,7 @@ func TestValidationResult(t *testing.T) {
 		AddressUsed:       ip,
 		URL:               "https://exampleB.com",
 		AddressesTried:    []net.IP{ip},
+		ResolverAddrs:     []string{"resolver:5353"},
 	}
 	result := []core.ValidationRecord{vrA, vrB}
 	prob := &probs.ProblemDetails{Type: probs.TLSProblem, Detail: "asd", HTTPStatus: 200}
diff --git a/issuance/cert_test.go b/issuance/cert_test.go
index 6708e434d..e5e81d89f 100644
--- a/issuance/cert_test.go
+++ b/issuance/cert_test.go
@@ -27,70 +27,63 @@ var (
 )
 
 func defaultProfile() *Profile {
-	p, _ := NewProfile(defaultProfileConfig(), defaultIssuerConfig())
+	p, _ := NewProfile(defaultProfileConfig(), []string{
+		"w_ct_sct_policy_count_unsatisfied",
+		"e_scts_from_same_operator",
+	})
 	return p
 }
 
-func TestNewProfileNoIssuerURL(t *testing.T) {
-	_, err := NewProfile(ProfileConfig{}, IssuerConfig{})
-	test.AssertError(t, err, "NewProfile didn't fail with no issuer URL")
-	test.AssertEquals(t, err.Error(), "Issuer URL is required")
-}
-
-func TestNewProfileNoOCSPURL(t *testing.T) {
-	_, err := NewProfile(ProfileConfig{}, IssuerConfig{IssuerURL: "issuer-url"})
-	test.AssertError(t, err, "NewProfile didn't fail with no OCSP URL")
-	test.AssertEquals(t, err.Error(), "OCSP URL is required")
-}
-
 func TestRequestValid(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Add(time.Hour * 24)
 
 	tests := []struct {
 		name          string
+		issuer        *Issuer
 		profile       *Profile
 		request       *IssuanceRequest
 		expectedError string
 	}{
 		{
 			name:          "unsupported key type",
+			issuer:        &Issuer{},
 			profile:       &Profile{},
 			request:       &IssuanceRequest{PublicKey: &dsa.PublicKey{}},
 			expectedError: "unsupported public key type",
 		},
 		{
 			name:          "cannot sign rsa",
+			issuer:        &Issuer{},
 			profile:       &Profile{},
 			request:       &IssuanceRequest{PublicKey: &rsa.PublicKey{}},
 			expectedError: "cannot sign RSA public keys",
 		},
 		{
 			name:          "cannot sign ecdsa",
+			issuer:        &Issuer{},
 			profile:       &Profile{},
 			request:       &IssuanceRequest{PublicKey: &ecdsa.PublicKey{}},
 			expectedError: "cannot sign ECDSA public keys",
 		},
 		{
 			name: "skid too short",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour * 2,
 			},
+			profile: &Profile{},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
 				SubjectKeyId: []byte{0, 1, 2, 3, 4},
-				NotBefore:    fc.Now(),
-				NotAfter:     fc.Now().Add(time.Hour),
-				Serial:       []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
 			},
 			expectedError: "unexpected subject key ID length",
 		},
 		{
 			name: "must staple not allowed",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
 			},
+			profile: &Profile{},
 			request: &IssuanceRequest{
 				PublicKey:         &ecdsa.PublicKey{},
 				SubjectKeyId:      goodSKID,
@@ -100,9 +93,10 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "ct poison not allowed",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
 			},
+			profile: &Profile{},
 			request: &IssuanceRequest{
 				PublicKey:       &ecdsa.PublicKey{},
 				SubjectKeyId:    goodSKID,
@@ -112,9 +106,10 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "sct list not allowed",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
 			},
+			profile: &Profile{},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
 				SubjectKeyId: goodSKID,
@@ -124,10 +119,12 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "sct list and ct poison not allowed",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				allowCTPoison:     true,
-				allowSCTList:      true,
+			},
+			profile: &Profile{
+				allowCTPoison: true,
+				allowSCTList:  true,
 			},
 			request: &IssuanceRequest{
 				PublicKey:       &ecdsa.PublicKey{},
@@ -139,9 +136,10 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "common name not allowed",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
 			},
+			profile: &Profile{},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
 				SubjectKeyId: goodSKID,
@@ -151,9 +149,10 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "negative validity",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
 			},
+			profile: &Profile{},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
 				SubjectKeyId: goodSKID,
@@ -164,9 +163,11 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "validity larger than max",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Minute,
+			},
+			profile: &Profile{
+				maxValidity: time.Minute,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -178,9 +179,11 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "validity larger than max due to inclusivity",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour,
+			},
+			profile: &Profile{
+				maxValidity: time.Hour,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -192,10 +195,12 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "validity backdated more than max",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour * 2,
-				maxBackdate:       time.Hour,
+			},
+			profile: &Profile{
+				maxValidity: time.Hour * 2,
+				maxBackdate: time.Hour,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -207,10 +212,12 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "validity is forward dated",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour * 2,
-				maxBackdate:       time.Hour,
+			},
+			profile: &Profile{
+				maxValidity: time.Hour * 2,
+				maxBackdate: time.Hour,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -222,9 +229,11 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "serial too short",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour * 2,
+			},
+			profile: &Profile{
+				maxValidity: time.Hour * 2,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -237,9 +246,11 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "serial too long",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour * 2,
+			},
+			profile: &Profile{
+				maxValidity: time.Hour * 2,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -252,9 +263,11 @@ func TestRequestValid(t *testing.T) {
 		},
 		{
 			name: "good",
-			profile: &Profile{
+			issuer: &Issuer{
 				useForECDSALeaves: true,
-				maxValidity:       time.Hour * 2,
+			},
+			profile: &Profile{
+				maxValidity: time.Hour * 2,
 			},
 			request: &IssuanceRequest{
 				PublicKey:    &ecdsa.PublicKey{},
@@ -267,7 +280,7 @@ func TestRequestValid(t *testing.T) {
 	}
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			err := tc.profile.requestValid(fc, tc.request)
+			err := tc.issuer.requestValid(fc, tc.profile, tc.request)
 			if err != nil {
 				if tc.expectedError == "" {
 					t.Errorf("failed with unexpected error: %s", err)
@@ -285,19 +298,22 @@ func TestRequestValid(t *testing.T) {
 func TestGenerateTemplate(t *testing.T) {
 	tests := []struct {
 		name             string
-		profile          *Profile
+		issuer           *Issuer
 		expectedTemplate *x509.Certificate
 	}{
 		{
 			name: "crl url",
-			profile: &Profile{
+			issuer: &Issuer{
 				crlURL: "crl-url",
 				sigAlg: x509.SHA256WithRSA,
 			},
 			expectedTemplate: &x509.Certificate{
 				BasicConstraintsValid: true,
 				SignatureAlgorithm:    x509.SHA256WithRSA,
-				ExtKeyUsage:           defaultEKU,
+				ExtKeyUsage: []x509.ExtKeyUsage{
+					x509.ExtKeyUsageServerAuth,
+					x509.ExtKeyUsageClientAuth,
+				},
 				IssuingCertificateURL: []string{""},
 				OCSPServer:            []string{""},
 				CRLDistributionPoints: []string{"crl-url"},
@@ -308,7 +324,7 @@ func TestGenerateTemplate(t *testing.T) {
 
 	for _, tc := range tests {
 		t.Run(tc.name, func(t *testing.T) {
-			template := tc.profile.generateTemplate()
+			template := tc.issuer.generateTemplate()
 			test.AssertDeepEquals(t, *template, *tc.expectedTemplate)
 		})
 	}
@@ -338,25 +354,14 @@ func TestIssue(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			fc := clock.NewFake()
 			fc.Set(time.Now())
-			linter, err := linter.New(
-				issuerCert.Certificate,
-				issuerSigner,
-				[]string{
-					"w_ct_sct_policy_count_unsatisfied",
-					"e_scts_from_same_operator",
-					"w_subject_common_name_included",
-				},
-			)
-			test.AssertNotError(t, err, "failed to create linter")
-			signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+			signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 			test.AssertNotError(t, err, "NewIssuer failed")
 			pk, err := tc.generateFunc()
 			test.AssertNotError(t, err, "failed to generate test key")
-			lintCertBytes, issuanceToken, err := signer.Prepare(&IssuanceRequest{
+			lintCertBytes, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 				PublicKey:       pk.Public(),
 				SubjectKeyId:    goodSKID,
 				Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
-				CommonName:      "example.com",
 				DNSNames:        []string{"example.com"},
 				NotBefore:       fc.Now(),
 				NotAfter:        fc.Now().Add(time.Hour - time.Second),
@@ -372,7 +377,6 @@ func TestIssue(t *testing.T) {
 			err = cert.CheckSignatureFrom(issuerCert.Certificate)
 			test.AssertNotError(t, err, "signature validation failed")
 			test.AssertDeepEquals(t, cert.DNSNames, []string{"example.com"})
-			test.AssertEquals(t, cert.Subject.CommonName, "example.com")
 			test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
 			test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
 			test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Poison
@@ -381,58 +385,17 @@ func TestIssue(t *testing.T) {
 	}
 }
 
-func TestIssueRSA(t *testing.T) {
-	fc := clock.NewFake()
-	fc.Set(time.Now())
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{
-			"w_ct_sct_policy_count_unsatisfied",
-			"e_scts_from_same_operator",
-		},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
-	test.AssertNotError(t, err, "NewIssuer failed")
-	pk, err := rsa.GenerateKey(rand.Reader, 2048)
-	test.AssertNotError(t, err, "failed to generate test key")
-	_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
-		PublicKey:       pk.Public(),
-		SubjectKeyId:    goodSKID,
-		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
-		DNSNames:        []string{"example.com"},
-		NotBefore:       fc.Now(),
-		NotAfter:        fc.Now().Add(time.Hour - time.Second),
-		IncludeCTPoison: true,
-	})
-	test.AssertNotError(t, err, "failed to prepare lint certificate")
-	certBytes, err := signer.Issue(issuanceToken)
-	test.AssertNotError(t, err, "failed to parse certificate")
-	cert, err := x509.ParseCertificate(certBytes)
-	test.AssertNotError(t, err, "failed to parse certificate")
-	err = cert.CheckSignatureFrom(issuerCert.Certificate)
-	test.AssertNotError(t, err, "signature validation failed")
-	test.AssertByteEquals(t, cert.SerialNumber.Bytes(), []byte{1, 2, 3, 4, 5, 6, 7, 8, 9})
-	test.AssertDeepEquals(t, cert.PublicKey, pk.Public())
-	test.AssertEquals(t, len(cert.Extensions), 9) // Constraints, KU, EKU, SKID, AKID, AIA, SAN, Policies, Poison
-	test.AssertEquals(t, cert.KeyUsage, x509.KeyUsageDigitalSignature|x509.KeyUsageKeyEncipherment)
-}
-
 func TestIssueCommonName(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Set(time.Now())
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{
-			"w_ct_sct_policy_count_unsatisfied",
-			"e_scts_from_same_operator",
-			"w_subject_common_name_included",
-		},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+
+	cnProfile, err := NewProfile(defaultProfileConfig(), []string{
+		"w_subject_common_name_included",
+		"w_ct_sct_policy_count_unsatisfied",
+		"e_scts_from_same_operator",
+	})
+	test.AssertNotError(t, err, "NewProfile failed")
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
@@ -447,7 +410,7 @@ func TestIssueCommonName(t *testing.T) {
 		IncludeCTPoison: true,
 	}
 
-	_, issuanceToken, err := signer.Prepare(ir)
+	_, issuanceToken, err := signer.Prepare(cnProfile, ir)
 	test.AssertNotError(t, err, "Prepare failed")
 	certBytes, err := signer.Issue(issuanceToken)
 	test.AssertNotError(t, err, "Issue failed")
@@ -455,12 +418,12 @@ func TestIssueCommonName(t *testing.T) {
 	test.AssertNotError(t, err, "failed to parse certificate")
 	test.AssertEquals(t, cert.Subject.CommonName, "example.com")
 
-	signer.Profile.allowCommonName = false
-	_, _, err = signer.Prepare(ir)
+	cnProfile.allowCommonName = false
+	_, _, err = signer.Prepare(cnProfile, ir)
 	test.AssertError(t, err, "Prepare should have failed")
 
 	ir.CommonName = ""
-	_, issuanceToken, err = signer.Prepare(ir)
+	_, issuanceToken, err = signer.Prepare(cnProfile, ir)
 	test.AssertNotError(t, err, "Prepare failed")
 	certBytes, err = signer.Issue(issuanceToken)
 	test.AssertNotError(t, err, "Issue failed")
@@ -473,20 +436,12 @@ func TestIssueCommonName(t *testing.T) {
 func TestIssueCTPoison(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Set(time.Now())
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{
-			"w_ct_sct_policy_count_unsatisfied",
-			"e_scts_from_same_operator",
-		},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
+	test.AssertNotError(t, err, "NewIssuer failed")
 	test.AssertNotError(t, err, "NewIssuer failed")
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
+	_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -519,19 +474,17 @@ func mustDecodeB64(b string) []byte {
 func TestIssueSCTList(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Set(time.Now())
+
 	err := loglist.InitLintList("../test/ct-test-srv/log_list.json")
 	test.AssertNotError(t, err, "failed to load log list")
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+
+	enforceSCTsProfile, err := NewProfile(defaultProfileConfig(), []string{})
+	test.AssertNotError(t, err, "NewProfile failed")
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
+	_, issuanceToken, err := signer.Prepare(enforceSCTsProfile, &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -560,7 +513,7 @@ func TestIssueSCTList(t *testing.T) {
 	request2, err := RequestFromPrecert(precert, sctList)
 	test.AssertNotError(t, err, "generating request from precert")
 
-	_, issuanceToken2, err := signer.Prepare(request2)
+	_, issuanceToken2, err := signer.Prepare(enforceSCTsProfile, request2)
 	test.AssertNotError(t, err, "preparing final cert issuance")
 
 	finalCertBytes, err := signer.Issue(issuanceToken2)
@@ -590,20 +543,12 @@ func TestIssueSCTList(t *testing.T) {
 func TestIssueMustStaple(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Set(time.Now())
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{
-			"w_ct_sct_policy_count_unsatisfied",
-			"e_scts_from_same_operator",
-		},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
+	_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 		PublicKey:         pk.Public(),
 		SubjectKeyId:      goodSKID,
 		Serial:            []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -629,13 +574,14 @@ func TestIssueMustStaple(t *testing.T) {
 func TestIssueBadLint(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Set(time.Now())
-	lint, err := linter.New(issuerCert.Certificate, issuerSigner, []string{})
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), lint, fc)
+
+	noSkipLintsProfile, err := NewProfile(defaultProfileConfig(), []string{})
+	test.AssertNotError(t, err, "NewProfile failed")
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, _, err = signer.Prepare(&IssuanceRequest{
+	_, _, err = signer.Prepare(noSkipLintsProfile, &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -651,9 +597,9 @@ func TestIssueBadLint(t *testing.T) {
 
 func TestIssuanceToken(t *testing.T) {
 	fc := clock.NewFake()
-	linter, err := linter.New(issuerCert.Certificate, issuerSigner, []string{})
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+	fc.Set(time.Now())
+
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 
 	_, err = signer.Issue(&issuanceToken{})
@@ -664,7 +610,7 @@ func TestIssuanceToken(t *testing.T) {
 
 	pk, err := rsa.GenerateKey(rand.Reader, 2048)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, issuanceToken, err := signer.Prepare(&IssuanceRequest{
+	_, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -681,7 +627,7 @@ func TestIssuanceToken(t *testing.T) {
 	test.AssertError(t, err, "expected second issuance with the same issuance token to fail")
 	test.AssertContains(t, err.Error(), "issuance token already redeemed")
 
-	_, issuanceToken, err = signer.Prepare(&IssuanceRequest{
+	_, issuanceToken, err = signer.Prepare(defaultProfile(), &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -692,7 +638,7 @@ func TestIssuanceToken(t *testing.T) {
 	})
 	test.AssertNotError(t, err, "expected Prepare to succeed")
 
-	signer2, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+	signer2, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 
 	_, err = signer2.Issue(issuanceToken)
@@ -703,19 +649,15 @@ func TestIssuanceToken(t *testing.T) {
 func TestInvalidProfile(t *testing.T) {
 	fc := clock.NewFake()
 	fc.Set(time.Now())
+
 	err := loglist.InitLintList("../test/ct-test-srv/log_list.json")
 	test.AssertNotError(t, err, "failed to load log list")
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-	signer, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+
+	signer, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, _, err = signer.Prepare(&IssuanceRequest{
+	_, _, err = signer.Prepare(defaultProfile(), &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -727,7 +669,7 @@ func TestInvalidProfile(t *testing.T) {
 	})
 	test.AssertError(t, err, "Invalid IssuanceRequest")
 
-	_, _, err = signer.Prepare(&IssuanceRequest{
+	_, _, err = signer.Prepare(defaultProfile(), &IssuanceRequest{
 		PublicKey:    pk.Public(),
 		SubjectKeyId: goodSKID,
 		Serial:       []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -753,18 +695,20 @@ func TestMismatchedProfiles(t *testing.T) {
 	fc.Set(time.Now())
 	err := loglist.InitLintList("../test/ct-test-srv/log_list.json")
 	test.AssertNotError(t, err, "failed to load log list")
-	linter, err := linter.New(
-		issuerCert.Certificate,
-		issuerSigner,
-		[]string{"w_subject_common_name_included"},
-	)
-	test.AssertNotError(t, err, "failed to create linter")
-
-	issuer1, err := NewIssuer(issuerCert, issuerSigner, defaultProfile(), linter, fc)
+
+	issuer1, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
+
+	cnProfile, err := NewProfile(defaultProfileConfig(), []string{
+		"w_subject_common_name_included",
+		"w_ct_sct_policy_count_unsatisfied",
+		"e_scts_from_same_operator",
+	})
+	test.AssertNotError(t, err, "NewProfile failed")
+
 	pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
 	test.AssertNotError(t, err, "failed to generate test key")
-	_, issuanceToken, err := issuer1.Prepare(&IssuanceRequest{
+	_, issuanceToken, err := issuer1.Prepare(cnProfile, &IssuanceRequest{
 		PublicKey:       pk.Public(),
 		SubjectKeyId:    goodSKID,
 		Serial:          []byte{1, 2, 3, 4, 5, 6, 7, 8, 9},
@@ -782,9 +726,12 @@ func TestMismatchedProfiles(t *testing.T) {
 	// Create a new profile that differs slightly (no common name)
 	profileConfig := defaultProfileConfig()
 	profileConfig.AllowCommonName = false
-	p, err := NewProfile(profileConfig, defaultIssuerConfig())
+	noCNProfile, err := NewProfile(profileConfig, []string{
+		"w_ct_sct_policy_count_unsatisfied",
+		"e_scts_from_same_operator",
+	})
 	test.AssertNotError(t, err, "NewProfile failed")
-	issuer2, err := NewIssuer(issuerCert, issuerSigner, p, linter, fc)
+	issuer2, err := newIssuer(defaultIssuerConfig(), issuerCert, issuerSigner, fc)
 	test.AssertNotError(t, err, "NewIssuer failed")
 
 	sctList := []ct.SignedCertificateTimestamp{
@@ -805,7 +752,7 @@ func TestMismatchedProfiles(t *testing.T) {
 	test.AssertNotError(t, err, "RequestFromPrecert")
 	request2.CommonName = ""
 
-	_, _, err = issuer2.Prepare(request2)
+	_, _, err = issuer2.Prepare(noCNProfile, request2)
 	test.AssertError(t, err, "preparing final cert issuance")
 	test.AssertContains(t, err.Error(), "precert does not correspond to linted final cert")
 }
diff --git a/issuance/issuer_test.go b/issuance/issuer_test.go
index e80ffad92..cd299a3fc 100644
--- a/issuance/issuer_test.go
+++ b/issuance/issuer_test.go
@@ -19,7 +19,6 @@ import (
 	"github.com/letsencrypt/boulder/cmd"
 	"github.com/letsencrypt/boulder/config"
 	"github.com/letsencrypt/boulder/core"
-	"github.com/letsencrypt/boulder/linter"
 	"github.com/letsencrypt/boulder/test"
 )
 
@@ -57,7 +56,7 @@ func TestMain(m *testing.M) {
 		Subject: pkix.Name{
 			CommonName: "big ca",
 		},
-		KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
+		KeyUsage: x509.KeyUsageCRLSign | x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature,
 	}
 	issuer, err := x509.CreateCertificate(rand.Reader, template, template, tk.Public(), tk)
 	cmd.FailOnError(err, "failed to generate test issuer")
@@ -67,90 +66,150 @@ func TestMain(m *testing.M) {
 	os.Exit(m.Run())
 }
 
-func TestNewIssuer(t *testing.T) {
-	_, err := NewIssuer(
+func TestLoadCertificate(t *testing.T) {
+	t.Parallel()
+	tests := []struct {
+		name    string
+		path    string
+		wantErr string
+	}{
+		{"invalid cert file", "../test/hierarchy/int-e1.crl.pem", "loading issuer certificate"},
+		{"non-CA cert file", "../test/hierarchy/ee-e1.cert.pem", "not a CA certificate"},
+		{"happy path", "../test/hierarchy/int-e1.cert.pem", ""},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			_, err := LoadCertificate(tc.path)
+			if err != nil {
+				if tc.wantErr != "" {
+					test.AssertContains(t, err.Error(), tc.wantErr)
+				} else {
+					t.Errorf("expected no error but got %v", err)
+				}
+			} else {
+				if tc.wantErr != "" {
+					t.Errorf("expected error %q but got none", tc.wantErr)
+				}
+			}
+		})
+	}
+}
+
+func TestLoadSigner(t *testing.T) {
+	t.Parallel()
+
+	// We're using this for its pubkey. This definitely doesn't match the private
+	// key loaded in any of the tests below, but that's okay because it still gets
+	// us through all the logic in loadSigner.
+	fakeKey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
+	test.AssertNotError(t, err, "generating test key")
+
+	tests := []struct {
+		name    string
+		loc     IssuerLoc
+		wantErr string
+	}{
+		{"empty IssuerLoc", IssuerLoc{}, "must supply"},
+		{"invalid key file", IssuerLoc{File: "../test/hierarchy/int-e1.crl.pem"}, "unable to parse"},
+		{"ECDSA key file", IssuerLoc{File: "../test/hierarchy/int-e1.key.pem"}, ""},
+		{"RSA key file", IssuerLoc{File: "../test/hierarchy/int-r3.key.pem"}, ""},
+		{"invalid config file", IssuerLoc{ConfigFile: "../test/example-weak-keys.json"}, "json: cannot unmarshal"},
+		// Note that we don't have a test for "valid config file" because it would
+		// always fail -- in CI, the softhsm hasn't been initialized, so there's no
+		// key to look up; locally even if the softhsm has been initialized, the
+		// keys in it don't match the fakeKey we generated above.
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			_, err := loadSigner(tc.loc, fakeKey.Public())
+			if err != nil {
+				if tc.wantErr != "" {
+					test.AssertContains(t, err.Error(), tc.wantErr)
+				} else {
+					t.Errorf("expected no error but got %v", err)
+				}
+			} else {
+				if tc.wantErr != "" {
+					t.Errorf("expected error %q but got none", tc.wantErr)
+				}
+			}
+		})
+	}
+}
+
+func TestLoadIssuer(t *testing.T) {
+	_, err := newIssuer(
+		defaultIssuerConfig(),
 		issuerCert,
 		issuerSigner,
-		defaultProfile(),
-		&linter.Linter{},
 		clock.NewFake(),
 	)
-	test.AssertNotError(t, err, "NewIssuer failed")
+	test.AssertNotError(t, err, "newIssuer failed")
 }
 
 func TestNewIssuerUnsupportedKeyType(t *testing.T) {
-	_, err := NewIssuer(
+	_, err := newIssuer(
+		defaultIssuerConfig(),
 		&Certificate{
 			Certificate: &x509.Certificate{
 				PublicKey: &ed25519.PublicKey{},
 			},
 		},
 		&ed25519.PrivateKey{},
-		defaultProfile(),
-		&linter.Linter{},
 		clock.NewFake(),
 	)
-	test.AssertError(t, err, "NewIssuer didn't fail")
+	test.AssertError(t, err, "newIssuer didn't fail")
 	test.AssertEquals(t, err.Error(), "unsupported issuer key type")
 }
 
-func TestNewIssuerNoCertSign(t *testing.T) {
-	_, err := NewIssuer(
-		&Certificate{
-			Certificate: &x509.Certificate{
-				PublicKey: &ecdsa.PublicKey{
-					Curve: elliptic.P256(),
-				},
-				KeyUsage: 0,
-			},
-		},
-		issuerSigner,
-		defaultProfile(),
-		&linter.Linter{},
-		clock.NewFake(),
-	)
-	test.AssertError(t, err, "NewIssuer didn't fail")
-	test.AssertEquals(t, err.Error(), "end-entity signing cert does not have keyUsage certSign")
-}
-
-func TestNewIssuerNoDigitalSignature(t *testing.T) {
-	_, err := NewIssuer(
-		&Certificate{
-			Certificate: &x509.Certificate{
-				PublicKey: &ecdsa.PublicKey{
-					Curve: elliptic.P256(),
-				},
-				KeyUsage: x509.KeyUsageCertSign,
-			},
-		},
-		issuerSigner,
-		defaultProfile(),
-		&linter.Linter{},
-		clock.NewFake(),
-	)
-	test.AssertError(t, err, "NewIssuer didn't fail")
-	test.AssertEquals(t, err.Error(), "end-entity ocsp signing cert does not have keyUsage digitalSignature")
-}
+func TestNewIssuerKeyUsage(t *testing.T) {
+	t.Parallel()
 
-func TestNewIssuerOCSPOnly(t *testing.T) {
-	p := defaultProfile()
-	p.useForRSALeaves = false
-	p.useForECDSALeaves = false
-	_, err := NewIssuer(
-		&Certificate{
-			Certificate: &x509.Certificate{
-				PublicKey: &ecdsa.PublicKey{
-					Curve: elliptic.P256(),
+	tests := []struct {
+		name    string
+		ku      x509.KeyUsage
+		wantErr string
+	}{
+		{"missing certSign", x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, "does not have keyUsage certSign"},
+		{"missing crlSign", x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, "does not have keyUsage crlSign"},
+		{"missing digitalSignature", x509.KeyUsageCertSign | x509.KeyUsageCRLSign, "does not have keyUsage digitalSignature"},
+		{"all three", x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, ""},
+	}
+	for _, tc := range tests {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
+			_, err := newIssuer(
+				defaultIssuerConfig(),
+				&Certificate{
+					Certificate: &x509.Certificate{
+						SerialNumber: big.NewInt(123),
+						PublicKey: &ecdsa.PublicKey{
+							Curve: elliptic.P256(),
+						},
+						KeyUsage: tc.ku,
+					},
 				},
-				KeyUsage: x509.KeyUsageDigitalSignature,
-			},
-		},
-		issuerSigner,
-		p,
-		&linter.Linter{},
-		clock.NewFake(),
-	)
-	test.AssertNotError(t, err, "NewIssuer failed")
+				issuerSigner,
+				clock.NewFake(),
+			)
+			if err != nil {
+				if tc.wantErr != "" {
+					test.AssertContains(t, err.Error(), tc.wantErr)
+				} else {
+					t.Errorf("expected no error but got %v", err)
+				}
+			} else {
+				if tc.wantErr != "" {
+					t.Errorf("expected error %q but got none", tc.wantErr)
+				}
+			}
+		})
+	}
 }
 
 func TestLoadChain_Valid(t *testing.T) {
diff --git a/ra/ra_test.go b/ra/ra_test.go
index ef2d09064..123b92f29 100644
--- a/ra/ra_test.go
+++ b/ra/ra_test.go
@@ -847,10 +847,11 @@ func TestPerformValidationSuccess(t *testing.T) {
 	va.PerformValidationRequestResultReturn = &vapb.ValidationResult{
 		Records: []*corepb.ValidationRecord{
 			{
-				AddressUsed: []byte("192.168.0.1"),
-				Hostname:    "example.com",
-				Port:        "8080",
-				Url:         "http://example.com/",
+				AddressUsed:   []byte("192.168.0.1"),
+				Hostname:      "example.com",
+				Port:          "8080",
+				Url:           "http://example.com/",
+				ResolverAddrs: []string{"rebound"},
 			},
 		},
 		Problems: nil,
@@ -3652,53 +3653,79 @@ rA==
 type mockSARevocation struct {
 	mocks.StorageAuthority
 
-	known   *corepb.CertificateStatus
+	known   map[string]*x509.Certificate
+	revoked map[string]*corepb.CertificateStatus
 	blocked []*sapb.AddBlockedKeyRequest
-	revoked map[string]int64
 }
 
 func newMockSARevocation(known *x509.Certificate, clk clock.Clock) *mockSARevocation {
 	return &mockSARevocation{
 		StorageAuthority: *mocks.NewStorageAuthority(clk),
-		known: &corepb.CertificateStatus{
-			Serial:   core.SerialToString(known.SerialNumber),
-			IssuerID: int64(issuance.IssuerNameID(known)),
-		},
-		blocked: make([]*sapb.AddBlockedKeyRequest, 0),
-		revoked: make(map[string]int64),
+		known:            map[string]*x509.Certificate{core.SerialToString(known.SerialNumber): known},
+		revoked:          make(map[string]*corepb.CertificateStatus),
+		blocked:          make([]*sapb.AddBlockedKeyRequest, 0),
 	}
 }
 
+func (msar *mockSARevocation) reset() {
+	msar.revoked = make(map[string]*corepb.CertificateStatus)
+	msar.blocked = make([]*sapb.AddBlockedKeyRequest, 0)
+}
+
 func (msar *mockSARevocation) AddBlockedKey(_ context.Context, req *sapb.AddBlockedKeyRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
 	msar.blocked = append(msar.blocked, req)
 	return &emptypb.Empty{}, nil
 }
 
+func (msar *mockSARevocation) GetLintPrecertificate(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
+	if cert, present := msar.known[req.Serial]; present {
+		return &corepb.Certificate{Der: cert.Raw}, nil
+	}
+	return nil, berrors.UnknownSerialError()
+}
+
 func (msar *mockSARevocation) GetCertificateStatus(_ context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.CertificateStatus, error) {
-	if msar.known != nil && req.Serial == msar.known.Serial {
-		return msar.known, nil
+	if status, present := msar.revoked[req.Serial]; present {
+		return status, nil
+	}
+	if cert, present := msar.known[req.Serial]; present {
+		return &corepb.CertificateStatus{
+			Serial:   core.SerialToString(cert.SerialNumber),
+			IssuerID: int64(issuance.IssuerNameID(cert)),
+		}, nil
 	}
-	return nil, fmt.Errorf("unknown certificate status")
+	return nil, berrors.UnknownSerialError()
 }
 
 func (msar *mockSARevocation) RevokeCertificate(_ context.Context, req *sapb.RevokeCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
 	if _, present := msar.revoked[req.Serial]; present {
 		return nil, berrors.AlreadyRevokedError("already revoked")
 	}
-	msar.revoked[req.Serial] = req.Reason
-	msar.known.Status = string(core.OCSPStatusRevoked)
+	cert, present := msar.known[req.Serial]
+	if !present {
+		return nil, berrors.UnknownSerialError()
+	}
+	msar.revoked[req.Serial] = &corepb.CertificateStatus{
+		Serial:        req.Serial,
+		IssuerID:      int64(issuance.IssuerNameID(cert)),
+		Status:        string(core.OCSPStatusRevoked),
+		RevokedReason: req.Reason,
+	}
 	return &emptypb.Empty{}, nil
 }
 
 func (msar *mockSARevocation) UpdateRevokedCertificate(_ context.Context, req *sapb.RevokeCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) {
-	reason, present := msar.revoked[req.Serial]
+	status, present := msar.revoked[req.Serial]
 	if !present {
 		return nil, errors.New("not already revoked")
 	}
-	if present && reason == ocsp.KeyCompromise {
+	if req.Reason != ocsp.KeyCompromise {
+		return nil, errors.New("cannot re-revoke except for keyCompromise")
+	}
+	if present && status.RevokedReason == ocsp.KeyCompromise {
 		return nil, berrors.AlreadyRevokedError("already revoked for keyCompromise")
 	}
-	msar.revoked[req.Serial] = req.Reason
+	msar.revoked[req.Serial].RevokedReason = req.Reason
 	return &emptypb.Empty{}, nil
 }
 
@@ -3904,7 +3931,7 @@ func TestRevokeCertByApplicant_Controller(t *testing.T) {
 		RegID: 5,
 	})
 	test.AssertNotError(t, err, "should have succeeded")
-	test.AssertEquals(t, mockSA.revoked[core.SerialToString(cert.SerialNumber)], int64(ocsp.CessationOfOperation))
+	test.AssertEquals(t, mockSA.revoked[core.SerialToString(cert.SerialNumber)].RevokedReason, int64(ocsp.CessationOfOperation))
 }
 
 func TestRevokeCertByKey(t *testing.T) {
@@ -3936,7 +3963,7 @@ func TestRevokeCertByKey(t *testing.T) {
 	test.Assert(t, bytes.Equal(digest[:], mockSA.blocked[0].KeyHash), "key hash mismatch")
 	test.AssertEquals(t, mockSA.blocked[0].Source, "API")
 	test.AssertEquals(t, len(mockSA.blocked[0].Comment), 0)
-	test.AssertEquals(t, mockSA.revoked[core.SerialToString(cert.SerialNumber)], int64(ocsp.KeyCompromise))
+	test.AssertEquals(t, mockSA.revoked[core.SerialToString(cert.SerialNumber)].RevokedReason, int64(ocsp.KeyCompromise))
 
 	// Re-revoking should fail, because it is already revoked for keyCompromise.
 	_, err = ra.RevokeCertByKey(context.Background(), &rapb.RevokeCertByKeyRequest{
@@ -3946,7 +3973,7 @@ func TestRevokeCertByKey(t *testing.T) {
 
 	// Reset and have the Subscriber revoke for a different reason.
 	// Then re-revoking using the key should work.
-	mockSA.revoked = make(map[string]int64)
+	mockSA.revoked = make(map[string]*corepb.CertificateStatus)
 	_, err = ra.RevokeCertByApplicant(context.Background(), &rapb.RevokeCertByApplicantRequest{
 		Cert:  cert.Raw,
 		Code:  ocsp.Unspecified,
@@ -3983,33 +4010,26 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{})
 	test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed for nil request object")
 
-	// Revoking with neither a cert nor a serial should fail immediately.
+	// Revoking with no serial should fail immediately.
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
 		Code:      ocsp.Unspecified,
 		AdminName: "root",
 	})
 	test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with no cert or serial")
 
-	// Revoking with a nil cert and no serial should fail immediately.
-	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Cert:      []byte{},
-		Code:      ocsp.KeyCompromise,
-		AdminName: "",
-	})
-	test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed for nil `Cert`")
-
 	// Revoking without an admin name should fail immediately.
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Cert:      cert.Raw,
 		Serial:    serial,
-		Code:      ocsp.KeyCompromise,
+		Code:      ocsp.Unspecified,
 		AdminName: "",
 	})
 	test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with empty string for `AdminName`")
 
 	// Revoking for a forbidden reason should fail immediately.
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Cert:      cert.Raw,
 		Serial:    serial,
 		Code:      ocsp.CertificateHold,
 		AdminName: "root",
@@ -4017,8 +4037,8 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
 	test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with forbidden revocation reason")
 
 	// Revoking a cert for an unspecified reason should work but not block the key.
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Cert:      cert.Raw,
 		Serial:    serial,
 		Code:      ocsp.Unspecified,
 		AdminName: "root",
@@ -4029,9 +4049,9 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
 		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 1)
 
 	// Revoking a serial for an unspecified reason should work but not block the key.
-	mockSA.revoked = make(map[string]int64)
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Serial:    core.SerialToString(cert.SerialNumber),
+		Serial:    serial,
 		Code:      ocsp.Unspecified,
 		AdminName: "root",
 	})
@@ -4041,11 +4061,27 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
 		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 2)
 
 	// Duplicate administrative revocation of a serial for an unspecified reason
-	// should fail and not block the key
+	// should succeed because the akamai cache purge succeeds.
+	// Note that we *don't* call reset() here, so it recognizes the duplicate.
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Serial:    core.SerialToString(cert.SerialNumber),
+		Serial:    serial,
+		Code:      ocsp.Unspecified,
+		AdminName: "root",
+	})
+	test.AssertNotError(t, err, "AdministrativelyRevokeCertificate failed")
+	test.AssertEquals(t, len(mockSA.blocked), 0)
+	test.AssertMetricWithLabelsEquals(
+		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 2)
+
+	// Duplicate administrative revocation of a serial for a *malformed* cert for
+	// an unspecified reason should fail because we can't attempt an akamai cache
+	// purge so the underlying AlreadyRevoked error gets propagated upwards.
+	// Note that we *don't* call reset() here, so it recognizes the duplicate.
+	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
+		Serial:    serial,
 		Code:      ocsp.Unspecified,
 		AdminName: "root",
+		Malformed: true,
 	})
 	test.AssertError(t, err, "Should be revoked")
 	test.AssertContains(t, err.Error(), "already revoked")
@@ -4053,10 +4089,23 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
 	test.AssertMetricWithLabelsEquals(
 		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "unspecified"}, 2)
 
+	// Revoking a cert for key compromise with skipBlockKey set should work but
+	// not block the key.
+	mockSA.reset()
+	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
+		Serial:       serial,
+		Code:         ocsp.KeyCompromise,
+		AdminName:    "root",
+		SkipBlockKey: true,
+	})
+	test.AssertNotError(t, err, "AdministrativelyRevokeCertificate failed")
+	test.AssertEquals(t, len(mockSA.blocked), 0)
+	test.AssertMetricWithLabelsEquals(
+		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "keyCompromise"}, 1)
+
 	// Revoking a cert for key compromise should work and block the key.
-	mockSA.revoked = make(map[string]int64)
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
-		Cert:      cert.Raw,
 		Serial:    serial,
 		Code:      ocsp.KeyCompromise,
 		AdminName: "root",
@@ -4068,14 +4117,16 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
 	test.AssertEquals(t, mockSA.blocked[0].Comment, "revoked by root")
 	test.AssertEquals(t, mockSA.blocked[0].Added.AsTime(), clk.Now())
 	test.AssertMetricWithLabelsEquals(
-		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "keyCompromise"}, 1)
+		t, ra.revocationReasonCounter, prometheus.Labels{"reason": "keyCompromise"}, 2)
 
-	// Revoking a serial for key compromise should fail because we don't have the pubkey to block.
-	mockSA.revoked = make(map[string]int64)
+	// Revoking a malformed cert for key compromise should fail because we don't
+	// have the pubkey to block.
+	mockSA.reset()
 	_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
 		Serial:    core.SerialToString(cert.SerialNumber),
 		Code:      ocsp.KeyCompromise,
 		AdminName: "root",
+		Malformed: true,
 	})
 	test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with just serial for keyCompromise")
 }
diff --git a/sa/sa_test.go b/sa/sa_test.go
index f072062b8..96522509b 100644
--- a/sa/sa_test.go
+++ b/sa/sa_test.go
@@ -25,6 +25,14 @@ import (
 
 	"github.com/go-sql-driver/mysql"
 	"github.com/jmhodges/clock"
+	"github.com/prometheus/client_golang/prometheus"
+	"golang.org/x/crypto/ocsp"
+	"google.golang.org/grpc"
+	"google.golang.org/protobuf/types/known/durationpb"
+	"google.golang.org/protobuf/types/known/emptypb"
+	"google.golang.org/protobuf/types/known/timestamppb"
+	"gopkg.in/go-jose/go-jose.v2"
+
 	"github.com/letsencrypt/boulder/core"
 	corepb "github.com/letsencrypt/boulder/core/proto"
 	"github.com/letsencrypt/boulder/db"
@@ -38,13 +46,6 @@ import (
 	sapb "github.com/letsencrypt/boulder/sa/proto"
 	"github.com/letsencrypt/boulder/test"
 	"github.com/letsencrypt/boulder/test/vars"
-	"github.com/prometheus/client_golang/prometheus"
-	"golang.org/x/crypto/ocsp"
-	"google.golang.org/grpc"
-	"google.golang.org/protobuf/types/known/durationpb"
-	"google.golang.org/protobuf/types/known/emptypb"
-	"google.golang.org/protobuf/types/known/timestamppb"
-	"gopkg.in/go-jose/go-jose.v2"
 )
 
 var log = blog.UseMock()
@@ -2595,10 +2596,11 @@ func TestFinalizeAuthorization2(t *testing.T) {
 		Id: authzID,
 		ValidationRecords: []*corepb.ValidationRecord{
 			{
-				Hostname:    "example.com",
-				Port:        "80",
-				Url:         "http://example.com",
-				AddressUsed: ip,
+				Hostname:      "example.com",
+				Port:          "80",
+				Url:           "http://example.com",
+				AddressUsed:   ip,
+				ResolverAddrs: []string{"resolver:5353"},
 			},
 		},
 		Status:      string(core.StatusValid),
@@ -2616,6 +2618,7 @@ func TestFinalizeAuthorization2(t *testing.T) {
 	test.AssertEquals(t, len(dbVer.Challenges[0].Validationrecords), 1)
 	test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Hostname, "example.com")
 	test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Port, "80")
+	test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].ResolverAddrs[0], "resolver:5353")
 	test.AssertEquals(t, dbVer.Challenges[0].Validated.AsTime(), attemptedAt)
 
 	authzID = createPendingAuthorization(t, sa, "aaa", fc.Now().Add(time.Hour))
@@ -2625,10 +2628,11 @@ func TestFinalizeAuthorization2(t *testing.T) {
 		Id: authzID,
 		ValidationRecords: []*corepb.ValidationRecord{
 			{
-				Hostname:    "example.com",
-				Port:        "80",
-				Url:         "http://example.com",
-				AddressUsed: ip,
+				Hostname:      "example.com",
+				Port:          "80",
+				Url:           "http://example.com",
+				AddressUsed:   ip,
+				ResolverAddrs: []string{"resolver:5353"},
 			},
 		},
 		ValidationError: prob,
@@ -2645,6 +2649,7 @@ func TestFinalizeAuthorization2(t *testing.T) {
 	test.AssertEquals(t, len(dbVer.Challenges[0].Validationrecords), 1)
 	test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Hostname, "example.com")
 	test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].Port, "80")
+	test.AssertEquals(t, dbVer.Challenges[0].Validationrecords[0].ResolverAddrs[0], "resolver:5353")
 	test.AssertDeepEquals(t, dbVer.Challenges[0].Error, prob)
 }
 
diff --git a/test/config-next/wfe2-ratelimit-defaults.yml b/test/config-next/wfe2-ratelimit-defaults.yml
index 70551e9d9..f10b5966d 100644
--- a/test/config-next/wfe2-ratelimit-defaults.yml
+++ b/test/config-next/wfe2-ratelimit-defaults.yml
@@ -1,2 +1,24 @@
-NewRegistrationsPerIPAddress: { burst: 10000, count: 10000, period: 168h }
-NewRegistrationsPerIPv6Range: { burst: 99999, count: 99999, period: 168h }
+NewRegistrationsPerIPAddress:
+  count: 10000
+  burst: 10000
+  period: 168h
+NewRegistrationsPerIPv6Range:
+  count: 99999
+  burst: 99999
+  period: 168h
+CertificatesPerDomain:
+  count: 2
+  burst: 2
+  period: 2160h
+FailedAuthorizationsPerAccount:
+  count: 3
+  burst: 3
+  period: 5m
+NewOrdersPerAccount:
+  count: 1500
+  burst: 1500
+  period: 3h
+CertificatesPerFQDNSet:
+  count: 6
+  burst: 6
+  period: 168h
diff --git a/test/config-next/wfe2-ratelimit-overrides.yml b/test/config-next/wfe2-ratelimit-overrides.yml
index 9d80ffaf4..4f45ba826 100644
--- a/test/config-next/wfe2-ratelimit-overrides.yml
+++ b/test/config-next/wfe2-ratelimit-overrides.yml
@@ -2,4 +2,39 @@
     burst: 1000000
     count: 1000000
     period: 168h
-    ids: [127.0.0.1]
+    ids:
+      - 127.0.0.1
+- CertificatesPerDomain:
+    burst: 1
+    count: 1
+    period: 2160h
+    ids:
+      - ratelimit.me
+- CertificatesPerDomain:
+    burst: 10000
+    count: 10000
+    period: 2160h
+    ids:
+      - le.wtf
+      - le1.wtf
+      - le2.wtf
+      - le3.wtf
+      - nginx.wtf
+      - good-caa-reserved.com
+      - bad-caa-reserved.com
+      - ecdsa.le.wtf
+      - must-staple.le.wtf
+- CertificatesPerFQDNSet:
+    burst: 10000
+    count: 10000
+    period: 168h
+    ids:
+      - le.wtf
+      - le1.wtf
+      - le2.wtf
+      - le3.wtf
+      - le.wtf,le1.wtf
+      - good-caa-reserved.com
+      - nginx.wtf
+      - ecdsa.le.wtf
+      - must-staple.le.wtf
diff --git a/test/consul/README.md b/test/consul/README.md
index 23d08bc8a..a9a344cab 100644
--- a/test/consul/README.md
+++ b/test/consul/README.md
@@ -21,8 +21,8 @@ in-memory server and client with persistence disabled for ease of use.
   services {
     id      = "foo-purger-b"
     name    = "foo-purger"
-    address = "10.88.88.88"
-    port    = 1338
+    address = "10.77.77.77"
+    port    = 1438
   }
   ```
 - To target individual `foo-purger`'s, add these additional `service` sections
@@ -39,8 +39,8 @@ in-memory server and client with persistence disabled for ease of use.
   services {
     id      = "foo-purger-2"
     name    = "foo-purger-2"
-    address = "10.88.88.88"
-    port    = 1338
+    address = "10.77.77.77"
+    port    = 1438
   }
   ```
 - For RFC 2782 (SRV RR) lookups to work ensure you that you add a tag for the
diff --git a/test/consul/config.hcl b/test/consul/config.hcl
index 668d5de3f..7f630d5a8 100644
--- a/test/consul/config.hcl
+++ b/test/consul/config.hcl
@@ -42,7 +42,7 @@ services {
 services {
   id      = "boulder-a"
   name    = "boulder"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
 }
 
 services {
@@ -56,7 +56,7 @@ services {
 services {
   id      = "ca-b"
   name    = "ca"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 9493
   tags    = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
 }
@@ -80,7 +80,7 @@ services {
 services {
   id      = "dns-b"
   name    = "dns"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 8054
   tags    = ["udp"] // Required for SRV RR support in VA RVA.
 }
@@ -96,7 +96,7 @@ services {
 services {
   id      = "doh-b"
   name    = "doh"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 8443
   tags    = ["tcp"]
 }
@@ -139,7 +139,7 @@ services {
 services {
   id      = "publisher-b"
   name    = "publisher"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 9491
   tags    = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
 }
@@ -155,7 +155,7 @@ services {
 services {
   id      = "ra-b"
   name    = "ra"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 9494
   tags    = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
 }
@@ -216,14 +216,14 @@ services {
 services {
   id      = "sa-b"
   name    = "sa"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 9495
   tags    = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
   checks = [
     {
       id              = "sa-b-grpc"
       name            = "sa-b-grpc"
-      grpc            = "10.88.88.88:9495"
+      grpc            = "10.77.77.77:9495"
       grpc_use_tls    = true
       tls_server_name = "sa.boulder"
       tls_skip_verify = false
@@ -232,7 +232,7 @@ services {
     {
       id              = "sa-b-grpc-sa"
       name            = "sa-b-grpc-sa"
-      grpc            = "10.88.88.88:9495/sa.StorageAuthority"
+      grpc            = "10.77.77.77:9495/sa.StorageAuthority"
       grpc_use_tls    = true
       tls_server_name = "sa.boulder"
       tls_skip_verify = false
@@ -241,7 +241,7 @@ services {
     {
       id              = "sa-b-grpc-saro"
       name            = "sa-b-grpc-saro"
-      grpc            = "10.88.88.88:9495/sa.StorageAuthorityReadOnly"
+      grpc            = "10.77.77.77:9495/sa.StorageAuthorityReadOnly"
       grpc_use_tls    = true
       tls_server_name = "sa.boulder"
       tls_skip_verify = false
@@ -261,7 +261,7 @@ services {
 services {
   id      = "va-b"
   name    = "va"
-  address = "10.88.88.88"
+  address = "10.77.77.77"
   port    = 9492
   tags    = ["tcp"] // Required for SRV RR support in gRPC DNS resolution.
 }
diff --git a/test/inmem/sa/sa.go b/test/inmem/sa/sa.go
index cd248de4a..5ae7cc5a6 100644
--- a/test/inmem/sa/sa.go
+++ b/test/inmem/sa/sa.go
@@ -117,6 +117,10 @@ func (sa SA) RevokeCertificate(ctx context.Context, req *sapb.RevokeCertificateR
 	return sa.Impl.RevokeCertificate(ctx, req)
 }
 
+func (sa SA) GetLintPrecertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) {
+	return sa.Impl.GetLintPrecertificate(ctx, req)
+}
+
 func (sa SA) GetCertificateStatus(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.CertificateStatus, error) {
 	return sa.Impl.GetCertificateStatus(ctx, req)
 }
diff --git a/test/integration/ratelimit_test.go b/test/integration/ratelimit_test.go
index 67543c713..cc5b40292 100644
--- a/test/integration/ratelimit_test.go
+++ b/test/integration/ratelimit_test.go
@@ -3,8 +3,17 @@
 package integration
 
 import (
+	"context"
+	"os"
+	"strings"
 	"testing"
 
+	"github.com/jmhodges/clock"
+	"github.com/letsencrypt/boulder/cmd"
+	blog "github.com/letsencrypt/boulder/log"
+	"github.com/letsencrypt/boulder/metrics"
+	"github.com/letsencrypt/boulder/ratelimits"
+	bredis "github.com/letsencrypt/boulder/redis"
 	"github.com/letsencrypt/boulder/test"
 )
 
@@ -20,4 +29,45 @@ func TestDuplicateFQDNRateLimit(t *testing.T) {
 
 	_, err = authAndIssue(nil, nil, []string{domain}, true)
 	test.AssertError(t, err, "Somehow managed to issue third certificate")
+
+	if strings.Contains(os.Getenv("BOULDER_CONFIG_DIR"), "test/config-next") {
+		// Setup rate limiting.
+		rc := bredis.Config{
+			Username: "unittest-rw",
+			TLS: cmd.TLSConfig{
+				CACertFile: "test/redis-tls/minica.pem",
+				CertFile:   "test/redis-tls/boulder/cert.pem",
+				KeyFile:    "test/redis-tls/boulder/key.pem",
+			},
+			Lookups: []cmd.ServiceDomain{
+				{
+					Service: "redisratelimits",
+					Domain:  "service.consul",
+				},
+			},
+			LookupDNSAuthority: "consul.service.consul",
+		}
+		rc.PasswordConfig = cmd.PasswordConfig{
+			PasswordFile: "test/secrets/ratelimits_redis_password",
+		}
+
+		fc := clock.NewFake()
+		stats := metrics.NoopRegisterer
+		log := blog.NewMock()
+		ring, err := bredis.NewRingFromConfig(rc, stats, log)
+		test.AssertNotError(t, err, "making redis ring client")
+		source := ratelimits.NewRedisSource(ring.Ring, fc, stats)
+		test.AssertNotNil(t, source, "source should not be nil")
+		limiter, err := ratelimits.NewLimiter(fc, source, stats)
+		test.AssertNotError(t, err, "making limiter")
+		txnBuilder, err := ratelimits.NewTransactionBuilder("test/config-next/wfe2-ratelimit-defaults.yml", "")
+		test.AssertNotError(t, err, "making transaction composer")
+
+		// Check that the CertificatesPerFQDNSet limit is reached.
+		txn, err := txnBuilder.CertificatesPerFQDNSetTransaction([]string{domain})
+		test.AssertNotError(t, err, "making transaction")
+		result, err := limiter.Check(context.Background(), txn)
+		test.AssertNotError(t, err, "checking transaction")
+		test.Assert(t, !result.Allowed, "should not be allowed")
+	}
 }
diff --git a/test/rate-limit-policies-b.yml b/test/rate-limit-policies-b.yml
deleted file mode 100644
index 2582c3a6e..000000000
--- a/test/rate-limit-policies-b.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-# See cmd/shell.go for definitions of these rate limits.
-certificatesPerName:
-  window: 2160h
-  threshold: 99
-  overrides:
-    ratelimit.me: 1
-    lim.it: 0
-    # Hostnames used by the letsencrypt client integration test.
-    le.wtf: 9999
-    le1.wtf: 9999
-    le2.wtf: 9999
-    le3.wtf: 9999
-    le4.wtf: 9999
-    nginx.wtf: 9999
-    good-caa-reserved.com: 9999
-    bad-caa-reserved.com: 9999
-    ecdsa.le.wtf: 9999
-    must-staple.le.wtf: 9999
-  registrationOverrides:
-    101: 1000
-registrationsPerIP:
-  window: 168h # 1 week
-  threshold: 9999
-  overrides:
-    127.0.0.1: 999990
-registrationsPerIPRange:
-  window: 168h # 1 week
-  threshold: 99999
-  overrides:
-    127.0.0.1: 1000000
-pendingAuthorizationsPerAccount:
-  window: 168h # 1 week, should match pending authorization lifetime.
-  threshold: 999
-newOrdersPerAccount:
-  window: 3h
-  threshold: 9999
-certificatesPerFQDNSet:
-  window: 168h
-  threshold: 99999
-  overrides:
-    le.wtf: 9999
-    le1.wtf: 9999
-    le2.wtf: 9999
-    le3.wtf: 9999
-    le.wtf,le1.wtf: 9999
-    good-caa-reserved.com: 9999
-    nginx.wtf: 9999
-    ecdsa.le.wtf: 9999
-    must-staple.le.wtf: 9999
-certificatesPerFQDNSetFast:
-  window: 2h
-  threshold: 20
-  overrides:
-    le.wtf: 9
diff --git a/test/v2_integration.py b/test/v2_integration.py
index d20318eca..93355e72a 100644
--- a/test/v2_integration.py
+++ b/test/v2_integration.py
@@ -1565,6 +1565,11 @@ def test_renewal_exemption():
     chisel2.expect_problem("urn:ietf:params:acme:error:rateLimited",
         lambda: chisel2.auth_and_issue(["mail." + base_domain]))
 
+# TODO(#5545)
+#   - Phase 2: Once the new rate limits are authoritative in config-next, ensure
+#     that this test only runs in config.
+#   - Phase 3: Once the new rate limits are authoritative in config, remove this
+#     test entirely.
 def test_certificates_per_name():
     chisel2.expect_problem("urn:ietf:params:acme:error:rateLimited",
         lambda: chisel2.auth_and_issue([random_domain() + ".lim.it"]))
diff --git a/va/caa_test.go b/va/caa_test.go
index dc3c62e48..56a38a818 100644
--- a/va/caa_test.go
+++ b/va/caa_test.go
@@ -25,21 +25,21 @@ import (
 // answers for CAA queries.
 type caaMockDNS struct{}
 
-func (mock caaMockDNS) LookupTXT(_ context.Context, hostname string) ([]string, error) {
-	return nil, nil
+func (mock caaMockDNS) LookupTXT(_ context.Context, hostname string) ([]string, bdns.ResolverAddrs, error) {
+	return nil, bdns.ResolverAddrs{"caaMockDNS"}, nil
 }
 
-func (mock caaMockDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
+func (mock caaMockDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
 	ip := net.ParseIP("127.0.0.1")
-	return []net.IP{ip}, nil
+	return []net.IP{ip}, bdns.ResolverAddrs{"caaMockDNS"}, nil
 }
 
-func (mock caaMockDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, error) {
+func (mock caaMockDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, bdns.ResolverAddrs, error) {
 	var results []*dns.CAA
 	var record dns.CAA
 	switch strings.TrimRight(domain, ".") {
 	case "caa-timeout.com":
-		return nil, "", fmt.Errorf("error")
+		return nil, "", bdns.ResolverAddrs{"caaMockDNS"}, fmt.Errorf("error")
 	case "reserved.com":
 		record.Tag = "issue"
 		record.Value = "ca.com"
@@ -59,11 +59,11 @@ func (mock caaMockDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA,
 		results = append(results, &record)
 	case "com":
 		// com has no CAA records.
-		return nil, "", nil
+		return nil, "", bdns.ResolverAddrs{"caaMockDNS"}, nil
 	case "gonetld":
-		return nil, "", fmt.Errorf("NXDOMAIN")
+		return nil, "", bdns.ResolverAddrs{"caaMockDNS"}, fmt.Errorf("NXDOMAIN")
 	case "servfail.com", "servfail.present.com":
-		return results, "", fmt.Errorf("SERVFAIL")
+		return results, "", bdns.ResolverAddrs{"caaMockDNS"}, fmt.Errorf("SERVFAIL")
 	case "multi-crit-present.com":
 		record.Flag = 1
 		record.Tag = "issue"
@@ -185,7 +185,7 @@ func (mock caaMockDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA,
 	if len(results) > 0 {
 		response = "foo"
 	}
-	return results, response, nil
+	return results, response, bdns.ResolverAddrs{"caaMockDNS"}, nil
 }
 
 func TestCAATimeout(t *testing.T) {
@@ -581,16 +581,16 @@ var errCAABrokenDNSClient = errors.New("dnsClient is broken")
 // errors.
 type caaBrokenDNS struct{}
 
-func (b caaBrokenDNS) LookupTXT(_ context.Context, hostname string) ([]string, error) {
-	return nil, errCAABrokenDNSClient
+func (b caaBrokenDNS) LookupTXT(_ context.Context, hostname string) ([]string, bdns.ResolverAddrs, error) {
+	return nil, bdns.ResolverAddrs{"caaBrokenDNS"}, errCAABrokenDNSClient
 }
 
-func (b caaBrokenDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
-	return nil, errCAABrokenDNSClient
+func (b caaBrokenDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
+	return nil, bdns.ResolverAddrs{"caaBrokenDNS"}, errCAABrokenDNSClient
 }
 
-func (b caaBrokenDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, error) {
-	return nil, "", errCAABrokenDNSClient
+func (b caaBrokenDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, bdns.ResolverAddrs, error) {
+	return nil, "", bdns.ResolverAddrs{"caaBrokenDNS"}, errCAABrokenDNSClient
 }
 
 func TestDisabledMultiCAARechecking(t *testing.T) {
@@ -623,15 +623,15 @@ func TestDisabledMultiCAARechecking(t *testing.T) {
 // changed while queries were inflight.
 type caaHijackedDNS struct{}
 
-func (h caaHijackedDNS) LookupTXT(_ context.Context, hostname string) ([]string, error) {
-	return nil, nil
+func (h caaHijackedDNS) LookupTXT(_ context.Context, hostname string) ([]string, bdns.ResolverAddrs, error) {
+	return nil, bdns.ResolverAddrs{"caaHijackedDNS"}, nil
 }
 
-func (h caaHijackedDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
+func (h caaHijackedDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
 	ip := net.ParseIP("127.0.0.1")
-	return []net.IP{ip}, nil
+	return []net.IP{ip}, bdns.ResolverAddrs{"caaHijackedDNS"}, nil
 }
-func (h caaHijackedDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, error) {
+func (h caaHijackedDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, bdns.ResolverAddrs, error) {
 	// These records are altered from their caaMockDNS counterparts. Use this to
 	// tickle remoteValidationFailures.
 	var results []*dns.CAA
@@ -642,7 +642,7 @@ func (h caaHijackedDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA,
 		record.Value = "other-ca.com"
 		results = append(results, &record)
 	case "present-dns-only.com":
-		return results, "", fmt.Errorf("SERVFAIL")
+		return results, "", bdns.ResolverAddrs{"caaHijackedDNS"}, fmt.Errorf("SERVFAIL")
 	case "satisfiable-wildcard.com":
 		record.Tag = "issuewild"
 		record.Value = ";"
@@ -656,7 +656,7 @@ func (h caaHijackedDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA,
 	if len(results) > 0 {
 		response = "foo"
 	}
-	return results, response, nil
+	return results, response, bdns.ResolverAddrs{"caaHijackedDNS"}, nil
 }
 
 func TestMultiCAARechecking(t *testing.T) {
@@ -1066,9 +1066,9 @@ func TestSelectCAA(t *testing.T) {
 
 	// A slice of empty caaResults should return nil, "", nil
 	r = []caaResult{
-		{"", false, nil, nil, false, "", nil},
-		{"", false, nil, nil, false, "", nil},
-		{"", false, nil, nil, false, "", nil},
+		{"", false, nil, nil, false, "", nil, nil},
+		{"", false, nil, nil, false, "", nil, nil},
+		{"", false, nil, nil, false, "", nil, nil},
 	}
 	s, err = selectCAA(r)
 	test.Assert(t, s == nil, "set is not nil")
@@ -1077,8 +1077,8 @@ func TestSelectCAA(t *testing.T) {
 	// A slice of caaResults containing an error followed by a CAA
 	// record should return the error
 	r = []caaResult{
-		{"foo.com", false, nil, nil, false, "", errors.New("oops")},
-		{"com", true, []*dns.CAA{&expected}, nil, false, "foo", nil},
+		{"foo.com", false, nil, nil, false, "", nil, errors.New("oops")},
+		{"com", true, []*dns.CAA{&expected}, nil, false, "foo", nil, nil},
 	}
 	s, err = selectCAA(r)
 	test.Assert(t, s == nil, "set is not nil")
@@ -1088,8 +1088,8 @@ func TestSelectCAA(t *testing.T) {
 	//  A slice of caaResults containing a good record that precedes an
 	//  error, should return that good record, not the error
 	r = []caaResult{
-		{"foo.com", true, []*dns.CAA{&expected}, nil, false, "foo", nil},
-		{"com", false, nil, nil, false, "", errors.New("")},
+		{"foo.com", true, []*dns.CAA{&expected}, nil, false, "foo", nil, nil},
+		{"com", false, nil, nil, false, "", nil, errors.New("")},
 	}
 	s, err = selectCAA(r)
 	test.AssertEquals(t, len(s.issue), 1)
@@ -1100,9 +1100,9 @@ func TestSelectCAA(t *testing.T) {
 	// A slice of caaResults containing multiple CAA records should
 	// return the first non-empty CAA record
 	r = []caaResult{
-		{"bar.foo.com", false, []*dns.CAA{}, []*dns.CAA{}, false, "", nil},
-		{"foo.com", true, []*dns.CAA{&expected}, nil, false, "foo", nil},
-		{"com", true, []*dns.CAA{&expected}, nil, false, "bar", nil},
+		{"bar.foo.com", false, []*dns.CAA{}, []*dns.CAA{}, false, "", nil, nil},
+		{"foo.com", true, []*dns.CAA{&expected}, nil, false, "foo", nil, nil},
+		{"com", true, []*dns.CAA{&expected}, nil, false, "bar", nil, nil},
 	}
 	s, err = selectCAA(r)
 	test.AssertEquals(t, len(s.issue), 1)
diff --git a/va/http_test.go b/va/http_test.go
index 771c70365..19f3ec748 100644
--- a/va/http_test.go
+++ b/va/http_test.go
@@ -17,6 +17,8 @@ import (
 	"time"
 	"unicode/utf8"
 
+	"github.com/miekg/dns"
+
 	"github.com/letsencrypt/boulder/bdns"
 	"github.com/letsencrypt/boulder/core"
 	berrors "github.com/letsencrypt/boulder/errors"
@@ -24,7 +26,6 @@ import (
 	"github.com/letsencrypt/boulder/must"
 	"github.com/letsencrypt/boulder/probs"
 	"github.com/letsencrypt/boulder/test"
-	"github.com/miekg/dns"
 
 	"testing"
 )
@@ -430,6 +431,7 @@ func TestSetupHTTPValidation(t *testing.T) {
 				URL:               "http://ipv4.and.ipv6.localhost/yellow/brick/road",
 				AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
 				AddressUsed:       net.ParseIP("::1"),
+				ResolverAddrs:     []string{"MockClient"},
 			},
 			ExpectedDialer: &preresolvedDialer{
 				ip:      net.ParseIP("::1"),
@@ -447,6 +449,7 @@ func TestSetupHTTPValidation(t *testing.T) {
 				URL:               "https://ipv4.and.ipv6.localhost/yellow/brick/road",
 				AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
 				AddressUsed:       net.ParseIP("::1"),
+				ResolverAddrs:     []string{"MockClient"},
 			},
 			ExpectedDialer: &preresolvedDialer{
 				ip:      net.ParseIP("::1"),
@@ -764,6 +767,7 @@ func TestFetchHTTP(t *testing.T) {
 				URL:               url,
 				AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 				AddressUsed:       net.ParseIP("127.0.0.1"),
+				ResolverAddrs:     []string{"MockClient"},
 			})
 	}
 
@@ -784,6 +788,7 @@ func TestFetchHTTP(t *testing.T) {
 				URL:               url,
 				AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 				AddressUsed:       net.ParseIP("127.0.0.1"),
+				ResolverAddrs:     []string{"MockClient"},
 			})
 	}
 
@@ -824,6 +829,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/timeout",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -841,6 +847,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com:" + strconv.Itoa(httpPort) + "/timeout",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -875,6 +882,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/redir-bad-proto",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -892,6 +900,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/redir-bad-port",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -909,6 +918,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/redir-bad-host",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -925,6 +935,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/redir-path-too-long",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -941,6 +952,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/bad-status-code",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -957,6 +969,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/303-see-other",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -974,6 +987,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/resp-too-big",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -990,6 +1004,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://ipv6.localhost/ok",
 					AddressesResolved: []net.IP{net.ParseIP("::1")},
 					AddressUsed:       net.ParseIP("::1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -1005,7 +1020,8 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://ipv4.and.ipv6.localhost/ok",
 					AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
 					// The first validation record should have used the IPv6 addr
-					AddressUsed: net.ParseIP("::1"),
+					AddressUsed:   net.ParseIP("::1"),
+					ResolverAddrs: []string{"MockClient"},
 				},
 				{
 					Hostname:          "ipv4.and.ipv6.localhost",
@@ -1013,7 +1029,8 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://ipv4.and.ipv6.localhost/ok",
 					AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
 					// The second validation record should have used the IPv4 addr as a fallback
-					AddressUsed: net.ParseIP("127.0.0.1"),
+					AddressUsed:   net.ParseIP("127.0.0.1"),
+					ResolverAddrs: []string{"MockClient"},
 				},
 			},
 		},
@@ -1029,6 +1046,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/ok",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -1044,6 +1062,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/redir-uppercase-publicsuffix",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 				{
 					Hostname:          "example.com",
@@ -1051,6 +1070,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/ok",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -1071,6 +1091,7 @@ func TestFetchHTTP(t *testing.T) {
 					URL:               "http://example.com/printf-verbs",
 					AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
 					AddressUsed:       net.ParseIP("127.0.0.1"),
+					ResolverAddrs:     []string{"MockClient"},
 				},
 			},
 		},
@@ -1342,8 +1363,8 @@ type dnsMockReturnsUnroutable struct {
 	*bdns.MockClient
 }
 
-func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
-	return []net.IP{net.ParseIP("198.51.100.1")}, nil
+func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
+	return []net.IP{net.ParseIP("198.51.100.1")}, bdns.ResolverAddrs{"dnsMockReturnsUnroutable"}, nil
 }
 
 // TestHTTPDialTimeout tests that we give the proper "Timeout during connect"
diff --git a/wfe2/wfe_test.go b/wfe2/wfe_test.go
index 85fdfe13b..dfad78833 100644
--- a/wfe2/wfe_test.go
+++ b/wfe2/wfe_test.go
@@ -425,7 +425,9 @@ func setupWFE(t *testing.T) (WebFrontEndImpl, clock.FakeClock, requestSigner) {
 		rncKey,
 		mockSA,
 		limiter,
-		txnBuilder)
+		txnBuilder,
+		100,
+	)
 	test.AssertNotError(t, err, "Unable to create WFE")
 
 	wfe.SubscriberAgreementURL = agreementURL