Replace most uses of net.IP with netip.Addr (#8205)

Retain `net.IP` only where we directly work with `x509.Certificate` and
friends.

Fixes #5925
Depends on #8196
This commit is contained in:
James Renken 2025-05-27 15:05:35 -07:00 committed by GitHub
parent 9b9ed86c10
commit ac68828f43
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 262 additions and 240 deletions

View File

@ -36,7 +36,7 @@ type ResolverAddrs []string
// Client queries for DNS records
type Client interface {
LookupTXT(context.Context, string) (txts []string, resolver ResolverAddrs, err error)
LookupHost(context.Context, string) ([]net.IP, ResolverAddrs, error)
LookupHost(context.Context, string) ([]netip.Addr, ResolverAddrs, error)
LookupCAA(context.Context, string) ([]*dns.CAA, string, ResolverAddrs, error)
}
@ -381,10 +381,7 @@ 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.
//
// TODO(#5925): Changing from net.IP to netip.Addr could start from here
// outwards.
func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.IP, ResolverAddrs, error) {
func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]netip.Addr, ResolverAddrs, error) {
var recordsA, recordsAAAA []dns.RR
var errA, errAAAA error
var resolverA, resolverAAAA string
@ -407,7 +404,7 @@ func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.I
return a == ""
})
var addrsA []net.IP
var addrsA []netip.Addr
if errA == nil {
for _, answer := range recordsA {
if answer.Header().Rrtype == dns.TypeA {
@ -415,7 +412,7 @@ func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.I
if ok && a.A.To4() != nil {
netIP, ok := netip.AddrFromSlice(a.A)
if ok && (policy.IsReservedIP(netIP) == nil || dnsClient.allowRestrictedAddresses) {
addrsA = append(addrsA, a.A)
addrsA = append(addrsA, netIP)
}
}
}
@ -425,7 +422,7 @@ func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.I
}
}
var addrsAAAA []net.IP
var addrsAAAA []netip.Addr
if errAAAA == nil {
for _, answer := range recordsAAAA {
if answer.Header().Rrtype == dns.TypeAAAA {
@ -433,7 +430,7 @@ func (dnsClient *impl) LookupHost(ctx context.Context, hostname string) ([]net.I
if ok && aaaa.AAAA.To16() != nil {
netIP, ok := netip.AddrFromSlice(aaaa.AAAA)
if ok && (policy.IsReservedIP(netIP) == nil || dnsClient.allowRestrictedAddresses) {
addrsAAAA = append(addrsAAAA, aaaa.AAAA)
addrsAAAA = append(addrsAAAA, netIP)
}
}
}

View File

@ -6,6 +6,7 @@ import (
"fmt"
"log"
"net"
"net/netip"
"net/url"
"os"
"regexp"
@ -373,10 +374,10 @@ func TestDNSLookupHost(t *testing.T) {
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")
expected := net.ParseIP("127.0.0.1")
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")
expected := netip.MustParseAddr("127.0.0.1")
test.Assert(t, ip[0] == expected, "wrong ipv4 address")
expected = netip.MustParseAddr("::1")
test.Assert(t, ip[1] == expected, "wrong ipv6 address")
slices.Sort(resolvers)
test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
@ -385,8 +386,8 @@ func TestDNSLookupHost(t *testing.T) {
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")
expected = netip.MustParseAddr("127.0.0.1")
test.Assert(t, ip[0] == expected, "wrong ipv4 address")
slices.Sort(resolvers)
test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})
@ -395,8 +396,8 @@ func TestDNSLookupHost(t *testing.T) {
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")
expected = netip.MustParseAddr("::1")
test.Assert(t, ip[0] == expected, "wrong ipv6 address")
slices.Sort(resolvers)
test.AssertDeepEquals(t, resolvers, ResolverAddrs{"A:127.0.0.1:4053", "AAAA:127.0.0.1:4053"})

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net"
"net/netip"
"os"
"github.com/miekg/dns"
@ -67,13 +68,13 @@ func (t timeoutError) Timeout() bool {
}
// LookupHost is a mock
func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]net.IP, ResolverAddrs, error) {
func (mock *MockClient) LookupHost(_ context.Context, hostname string) ([]netip.Addr, ResolverAddrs, error) {
if hostname == "always.invalid" ||
hostname == "invalid.invalid" {
return []net.IP{}, ResolverAddrs{"MockClient"}, nil
return []netip.Addr{}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "always.timeout" {
return []net.IP{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, "always.timeout", makeTimeoutError(), -1, nil}
return []netip.Addr{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, "always.timeout", makeTimeoutError(), -1, nil}
}
if hostname == "always.error" {
err := &net.OpError{
@ -86,7 +87,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{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
return []netip.Addr{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
}
if hostname == "id.mismatch" {
err := dns.ErrId
@ -100,22 +101,21 @@ 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{}, ResolverAddrs{"MockClient"}, &Error{dns.TypeA, hostname, err, -1, nil}
return []netip.Addr{}, 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"),
return []netip.Addr{
netip.MustParseAddr("::1"),
netip.MustParseAddr("127.0.0.1"),
}, ResolverAddrs{"MockClient"}, nil
}
if hostname == "ipv6.localhost" {
return []net.IP{
net.ParseIP("::1"),
return []netip.Addr{
netip.MustParseAddr("::1"),
}, ResolverAddrs{"MockClient"}, nil
}
ip := net.ParseIP("127.0.0.1")
return []net.IP{ip}, ResolverAddrs{"MockClient"}, nil
return []netip.Addr{netip.MustParseAddr("127.0.0.1")}, ResolverAddrs{"MockClient"}, nil
}
// LookupCAA returns mock records for use in tests.

View File

@ -6,6 +6,7 @@ import (
"fmt"
"math/rand/v2"
"net"
"net/netip"
"strconv"
"sync"
"time"
@ -61,10 +62,9 @@ func validateServerAddress(address string) error {
}
// Ensure the `host` portion of `address` is a valid FQDN or IP address.
IPv6 := net.ParseIP(host).To16()
IPv4 := net.ParseIP(host).To4()
_, err = netip.ParseAddr(host)
FQDN := dns.IsFqdn(dns.Fqdn(host))
if IPv6 == nil && IPv4 == nil && !FQDN {
if err != nil && !FQDN {
return errors.New("host is not an FQDN or IP address")
}
return nil

View File

@ -5,6 +5,7 @@ import (
"flag"
"fmt"
"net"
"net/netip"
"os"
"github.com/letsencrypt/boulder/cmd"
@ -41,8 +42,8 @@ func derivePrefix(key []byte, grpcAddr string) (string, error) {
return "", fmt.Errorf("nonce service gRPC address must include an IP address: got %q", grpcAddr)
}
if host != "" && port != "" {
hostIP := net.ParseIP(host)
if hostIP == nil {
hostIP, err := netip.ParseAddr(host)
if err != nil {
return "", fmt.Errorf("gRPC address host part was not an IP address")
}
if hostIP.IsUnspecified() {

View File

@ -6,7 +6,7 @@ import (
"encoding/json"
"fmt"
"hash/fnv"
"net"
"net/netip"
"strings"
"time"
@ -124,10 +124,10 @@ type ValidationRecord struct {
// Shared
//
// TODO(#7311): Replace DnsName with Identifier.
DnsName string `json:"hostname,omitempty"`
Port string `json:"port,omitempty"`
AddressesResolved []net.IP `json:"addressesResolved,omitempty"`
AddressUsed net.IP `json:"addressUsed,omitempty"`
DnsName string `json:"hostname,omitempty"`
Port string `json:"port,omitempty"`
AddressesResolved []netip.Addr `json:"addressesResolved,omitempty"`
AddressUsed netip.Addr `json:"addressUsed,omitempty"`
// AddressesTried contains a list of addresses tried before the `AddressUsed`.
// Presently this will only ever be one IP from `AddressesResolved` since the
@ -143,7 +143,7 @@ type ValidationRecord struct {
// AddressesTried: [ ::1 ],
// ...
// }
AddressesTried []net.IP `json:"addressesTried,omitempty"`
AddressesTried []netip.Addr `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
@ -210,7 +210,7 @@ func (ch Challenge) RecordsSane() bool {
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.DnsName == "" || rec.Port == "" || rec.AddressUsed == nil ||
if rec.URL == "" || rec.DnsName == "" || rec.Port == "" || (rec.AddressUsed == netip.Addr{}) ||
len(rec.AddressesResolved) == 0 {
return false
}
@ -225,7 +225,7 @@ func (ch Challenge) RecordsSane() bool {
// TODO(#7140): Add a check for ResolverAddress == "" only after the
// core.proto change has been deployed.
if ch.ValidationRecord[0].DnsName == "" || ch.ValidationRecord[0].Port == "" ||
ch.ValidationRecord[0].AddressUsed == nil || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
(ch.ValidationRecord[0].AddressUsed == netip.Addr{}) || len(ch.ValidationRecord[0].AddressesResolved) == 0 {
return false
}
case ChallengeTypeDNS01:

View File

@ -4,7 +4,7 @@ import (
"crypto/rsa"
"encoding/json"
"math/big"
"net"
"net/netip"
"testing"
"time"
@ -39,8 +39,8 @@ func TestRecordSanityCheckOnUnsupportedChallengeType(t *testing.T) {
URL: "http://localhost/test",
DnsName: "localhost",
Port: "80",
AddressesResolved: []net.IP{{127, 0, 0, 1}},
AddressUsed: net.IP{127, 0, 0, 1},
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"eastUnboundAndDown"},
},
}

View File

@ -183,14 +183,14 @@ type ValidationRecord struct {
// TODO(#7311): Replace hostname with Identifier.
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()
AddressUsed []byte `protobuf:"bytes,4,opt,name=addressUsed,proto3" json:"addressUsed,omitempty"` // net.IP.MarshalText()
AddressesResolved [][]byte `protobuf:"bytes,3,rep,name=addressesResolved,proto3" json:"addressesResolved,omitempty"` // netip.Addr.MarshalText()
AddressUsed []byte `protobuf:"bytes,4,opt,name=addressUsed,proto3" json:"addressUsed,omitempty"` // netip.Addr.MarshalText()
Authorities []string `protobuf:"bytes,5,rep,name=authorities,proto3" json:"authorities,omitempty"`
Url string `protobuf:"bytes,6,opt,name=url,proto3" json:"url,omitempty"`
// A list of addresses tried before the address used (see
// 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()
AddressesTried [][]byte `protobuf:"bytes,7,rep,name=addressesTried,proto3" json:"addressesTried,omitempty"` // netip.Addr.MarshalText()
ResolverAddrs []string `protobuf:"bytes,8,rep,name=resolverAddrs,proto3" json:"resolverAddrs,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache

View File

@ -31,15 +31,15 @@ message ValidationRecord {
// TODO(#7311): Replace hostname with Identifier.
string hostname = 1;
string port = 2;
repeated bytes addressesResolved = 3; // net.IP.MarshalText()
bytes addressUsed = 4; // net.IP.MarshalText()
repeated bytes addressesResolved = 3; // netip.Addr.MarshalText()
bytes addressUsed = 4; // netip.Addr.MarshalText()
repeated string authorities = 5;
string url = 6;
// A list of addresses tried before the address used (see
// core/objects.go and the comment on the ValidationRecord structure
// definition for more information.
repeated bytes addressesTried = 7; // net.IP.MarshalText()
repeated bytes addressesTried = 7; // netip.Addr.MarshalText()
repeated string resolverAddrs = 8;
}

View File

@ -27,17 +27,19 @@ import (
"errors"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
"sync"
"time"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/grpc/internal/backoff"
"github.com/letsencrypt/boulder/grpc/noncebalancer"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/serviceconfig"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/grpc/internal/backoff"
"github.com/letsencrypt/boulder/grpc/noncebalancer"
)
var logger = grpclog.Component("srv")
@ -292,11 +294,11 @@ func (d *dnsResolver) lookup() (*resolver.State, error) {
// If addr is an IPv4 address, return the addr and ok = true.
// If addr is an IPv6 address, return the addr enclosed in square brackets and ok = true.
func formatIP(addr string) (addrIP string, ok bool) {
ip := net.ParseIP(addr)
if ip == nil {
ip, err := netip.ParseAddr(addr)
if err != nil {
return "", false
}
if ip.To4() != nil {
if ip.Is4() {
return addr, true
}
return "[" + addr + "]", true

View File

@ -7,7 +7,7 @@ package grpc
import (
"fmt"
"net"
"net/netip"
"time"
"github.com/go-jose/go-jose/v4"
@ -23,6 +23,7 @@ import (
)
var ErrMissingParameters = CodedError(codes.FailedPrecondition, "required RPC parameter was missing")
var ErrInvalidParameters = CodedError(codes.InvalidArgument, "RPC parameter was invalid")
// This file defines functions to translate between the protobuf types and the
// code types.
@ -130,10 +131,10 @@ func ValidationRecordToPB(record core.ValidationRecord) (*corepb.ValidationRecor
addrsTried := make([][]byte, len(record.AddressesTried))
var err error
for i, v := range record.AddressesResolved {
addrs[i] = []byte(v)
addrs[i] = v.AsSlice()
}
for i, v := range record.AddressesTried {
addrsTried[i] = []byte(v)
addrsTried[i] = v.AsSlice()
}
addrUsed, err := record.AddressUsed.MarshalText()
if err != nil {
@ -154,15 +155,23 @@ func PBToValidationRecord(in *corepb.ValidationRecord) (record core.ValidationRe
if in == nil {
return core.ValidationRecord{}, ErrMissingParameters
}
addrs := make([]net.IP, len(in.AddressesResolved))
addrs := make([]netip.Addr, len(in.AddressesResolved))
for i, v := range in.AddressesResolved {
addrs[i] = net.IP(v)
netIP, ok := netip.AddrFromSlice(v)
if !ok {
return core.ValidationRecord{}, ErrInvalidParameters
}
addrs[i] = netIP
}
addrsTried := make([]net.IP, len(in.AddressesTried))
addrsTried := make([]netip.Addr, len(in.AddressesTried))
for i, v := range in.AddressesTried {
addrsTried[i] = net.IP(v)
netIP, ok := netip.AddrFromSlice(v)
if !ok {
return core.ValidationRecord{}, ErrInvalidParameters
}
addrsTried[i] = netIP
}
var addrUsed net.IP
var addrUsed netip.Addr
err = addrUsed.UnmarshalText(in.AddressUsed)
if err != nil {
return

View File

@ -2,7 +2,7 @@ package grpc
import (
"encoding/json"
"net"
"net/netip"
"testing"
"time"
@ -69,15 +69,15 @@ func TestChallenge(t *testing.T) {
test.AssertNotError(t, err, "PBToChallenge failed")
test.AssertDeepEquals(t, recon, chall)
ip := net.ParseIP("1.1.1.1")
ip := netip.MustParseAddr("1.1.1.1")
chall.ValidationRecord = []core.ValidationRecord{
{
DnsName: "example.com",
Port: "2020",
AddressesResolved: []net.IP{ip},
AddressesResolved: []netip.Addr{ip},
AddressUsed: ip,
URL: "https://example.com:2020",
AddressesTried: []net.IP{ip},
AddressesTried: []netip.Addr{ip},
},
}
chall.Error = &probs.ProblemDetails{Type: probs.TLSProblem, Detail: "asd", HTTPStatus: 200}
@ -111,14 +111,14 @@ func TestChallenge(t *testing.T) {
}
func TestValidationRecord(t *testing.T) {
ip := net.ParseIP("1.1.1.1")
ip := netip.MustParseAddr("1.1.1.1")
vr := core.ValidationRecord{
DnsName: "exampleA.com",
Port: "80",
AddressesResolved: []net.IP{ip},
AddressesResolved: []netip.Addr{ip},
AddressUsed: ip,
URL: "http://exampleA.com",
AddressesTried: []net.IP{ip},
AddressesTried: []netip.Addr{ip},
ResolverAddrs: []string{"resolver:5353"},
}
@ -132,23 +132,23 @@ func TestValidationRecord(t *testing.T) {
}
func TestValidationResult(t *testing.T) {
ip := net.ParseIP("1.1.1.1")
ip := netip.MustParseAddr("1.1.1.1")
vrA := core.ValidationRecord{
DnsName: "exampleA.com",
Port: "443",
AddressesResolved: []net.IP{ip},
AddressesResolved: []netip.Addr{ip},
AddressUsed: ip,
URL: "https://exampleA.com",
AddressesTried: []net.IP{ip},
AddressesTried: []netip.Addr{ip},
ResolverAddrs: []string{"resolver:5353"},
}
vrB := core.ValidationRecord{
DnsName: "exampleB.com",
Port: "443",
AddressesResolved: []net.IP{ip},
AddressesResolved: []netip.Addr{ip},
AddressUsed: ip,
URL: "https://exampleB.com",
AddressesTried: []net.IP{ip},
AddressesTried: []netip.Addr{ip},
ResolverAddrs: []string{"resolver:5353"},
}
result := []core.ValidationRecord{vrA, vrB}

View File

@ -3,6 +3,7 @@ package grpc
import (
"fmt"
"net"
"net/netip"
"strings"
"google.golang.org/grpc/resolver"
@ -91,7 +92,8 @@ func parseResolverIPAddress(addr string) (*resolver.Address, error) {
// empty (e.g. :80), the local system is assumed.
host = "127.0.0.1"
}
if net.ParseIP(host) == nil {
_, err = netip.ParseAddr(host)
if err != nil {
// Host is a DNS name or an IPv6 address without brackets.
return nil, fmt.Errorf("address %q is not an IP address", addr)
}

View File

@ -3,13 +3,15 @@ package probers
import (
"fmt"
"net"
"net/netip"
"strconv"
"strings"
"github.com/letsencrypt/boulder/observer/probers"
"github.com/letsencrypt/boulder/strictyaml"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"github.com/letsencrypt/boulder/observer/probers"
"github.com/letsencrypt/boulder/strictyaml"
)
var (
@ -58,13 +60,12 @@ func (c DNSConf) validateServer() error {
return fmt.Errorf(
"invalid `server`, %q, port number must be one in [1-65535]", c.Server)
}
// Ensure `server` is a valid FQDN or IPv4 / IPv6 address.
IPv6 := net.ParseIP(host).To16()
IPv4 := net.ParseIP(host).To4()
// Ensure `server` is a valid FQDN or IP address.
_, err = netip.ParseAddr(host)
FQDN := dns.IsFqdn(dns.Fqdn(host))
if IPv6 == nil && IPv4 == nil && !FQDN {
if err != nil && !FQDN {
return fmt.Errorf(
"invalid `server`, %q, is not an FQDN or IPv4 / IPv6 address", c.Server)
"invalid `server`, %q, is not an FQDN or IP address", c.Server)
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"encoding/hex"
"errors"
"fmt"
"net"
"net/mail"
"net/netip"
"os"
@ -228,7 +227,8 @@ func validNonWildcardDomain(domain string) error {
return errNameTooLong
}
if ip := net.ParseIP(domain); ip != nil {
_, err := netip.ParseAddr(domain)
if err == nil {
return errIPAddressInDNS
}
@ -343,11 +343,11 @@ func validIP(ip string) error {
return errEmptyIdentifier
}
// Check the output of net.IP.String(), to ensure the input complied with
// RFC 8738, Sec. 3. ("The identifier value MUST contain the textual form of
// the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC 5952,
// Sec. 4 for IPv6.") ParseIP() will accept a non-compliant but otherwise
// valid string; String() will output a compliant string.
// Check the output of netip.Addr.String(), to ensure the input complied
// with RFC 8738, Sec. 3. ("The identifier value MUST contain the textual
// form of the address as defined in RFC 1123, Sec. 2.1 for IPv4 and in RFC
// 5952, Sec. 4 for IPv6.") ParseAddr() will accept a non-compliant but
// otherwise valid string; String() will output a compliant string.
parsedIP, err := netip.ParseAddr(ip)
if err != nil || parsedIP.String() != ip {
return errIPInvalid

View File

@ -4,6 +4,7 @@ import (
"context"
"math/rand/v2"
"net"
"net/netip"
"testing"
"time"
@ -59,7 +60,7 @@ func TestLimiter_CheckWithLimitOverrides(t *testing.T) {
testCtx, limiters, txnBuilder, clk, testIP := setup(t)
for name, l := range limiters {
t.Run(name, func(t *testing.T) {
overriddenBucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, net.ParseIP(tenZeroZeroTwo))
overriddenBucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(tenZeroZeroTwo))
test.AssertNotError(t, err, "should not error")
overriddenLimit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, overriddenBucketKey)
test.AssertNotError(t, err, "should not error")
@ -115,8 +116,7 @@ func TestLimiter_CheckWithLimitOverrides(t *testing.T) {
// Wait 1 second for a full bucket reset.
clk.Add(d.resetIn)
testIP := net.ParseIP(testIP)
normalBucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, testIP)
normalBucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
test.AssertNotError(t, err, "should not error")
normalLimit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, normalBucketKey)
test.AssertNotError(t, err, "should not error")
@ -247,7 +247,7 @@ func TestLimiter_InitializationViaCheckAndSpend(t *testing.T) {
testCtx, limiters, txnBuilder, _, testIP := setup(t)
for name, l := range limiters {
t.Run(name, func(t *testing.T) {
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, net.ParseIP(testIP))
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
test.AssertNotError(t, err, "should not error")
limit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, bucketKey)
test.AssertNotError(t, err, "should not error")
@ -310,7 +310,7 @@ func TestLimiter_DefaultLimits(t *testing.T) {
testCtx, limiters, txnBuilder, clk, testIP := setup(t)
for name, l := range limiters {
t.Run(name, func(t *testing.T) {
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, net.ParseIP(testIP))
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
test.AssertNotError(t, err, "should not error")
limit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, bucketKey)
test.AssertNotError(t, err, "should not error")
@ -373,7 +373,7 @@ func TestLimiter_RefundAndReset(t *testing.T) {
testCtx, limiters, txnBuilder, clk, testIP := setup(t)
for name, l := range limiters {
t.Run(name, func(t *testing.T) {
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, net.ParseIP(testIP))
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, netip.MustParseAddr(testIP))
test.AssertNotError(t, err, "should not error")
limit, err := txnBuilder.getLimit(NewRegistrationsPerIPAddress, bucketKey)
test.AssertNotError(t, err, "should not error")

View File

@ -3,6 +3,7 @@ package ratelimits
import (
"fmt"
"net"
"net/netip"
"strconv"
"strings"
@ -126,8 +127,8 @@ func (n Name) EnumString() string {
// validIPAddress validates that the provided string is a valid IP address.
func validIPAddress(id string) error {
ip := net.ParseIP(id)
if ip == nil {
_, err := netip.ParseAddr(id)
if err != nil {
return fmt.Errorf("invalid IP address, %q must be an IP address", id)
}
return nil

View File

@ -3,7 +3,7 @@ package ratelimits
import (
"errors"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
)
@ -16,7 +16,7 @@ var ErrInvalidCostOverLimit = fmt.Errorf("invalid cost, must be <= limit.Burst")
// newIPAddressBucketKey validates and returns a bucketKey for limits that use
// the 'enum:ipAddress' bucket key format.
func newIPAddressBucketKey(name Name, ip net.IP) (string, error) { //nolint:unparam // Only one named rate limit uses this helper
func newIPAddressBucketKey(name Name, ip netip.Addr) (string, error) { //nolint:unparam // Only one named rate limit uses this helper
id := ip.String()
err := validateIdForName(name, id)
if err != nil {
@ -27,14 +27,16 @@ func newIPAddressBucketKey(name Name, ip net.IP) (string, error) { //nolint:unpa
// newIPv6RangeCIDRBucketKey validates and returns a bucketKey for limits that
// use the 'enum:ipv6RangeCIDR' bucket key format.
func newIPv6RangeCIDRBucketKey(name Name, ip net.IP) (string, error) {
if ip.To4() != nil {
func newIPv6RangeCIDRBucketKey(name Name, ip netip.Addr) (string, error) {
if ip.Is4() {
return "", fmt.Errorf("invalid IPv6 address, %q must be an IPv6 address", ip.String())
}
ipMask := net.CIDRMask(48, 128)
ipNet := &net.IPNet{IP: ip.Mask(ipMask), Mask: ipMask}
id := ipNet.String()
err := validateIdForName(name, id)
prefix, err := ip.Prefix(48)
if err != nil {
return "", fmt.Errorf("invalid IPv6 address, can't calculate prefix of %q: %s", ip.String(), err)
}
id := prefix.String()
err = validateIdForName(name, id)
if err != nil {
return "", err
}
@ -206,7 +208,7 @@ func NewTransactionBuilder(defaults LimitConfigs) (*TransactionBuilder, error) {
// registrationsPerIPAddressTransaction returns a Transaction for the
// NewRegistrationsPerIPAddress limit for the provided IP address.
func (builder *TransactionBuilder) registrationsPerIPAddressTransaction(ip net.IP) (Transaction, error) {
func (builder *TransactionBuilder) registrationsPerIPAddressTransaction(ip netip.Addr) (Transaction, error) {
bucketKey, err := newIPAddressBucketKey(NewRegistrationsPerIPAddress, ip)
if err != nil {
return Transaction{}, err
@ -224,7 +226,7 @@ func (builder *TransactionBuilder) registrationsPerIPAddressTransaction(ip net.I
// registrationsPerIPv6RangeTransaction returns a Transaction for the
// NewRegistrationsPerIPv6Range limit for the /48 IPv6 range which contains the
// provided IPv6 address.
func (builder *TransactionBuilder) registrationsPerIPv6RangeTransaction(ip net.IP) (Transaction, error) {
func (builder *TransactionBuilder) registrationsPerIPv6RangeTransaction(ip netip.Addr) (Transaction, error) {
bucketKey, err := newIPv6RangeCIDRBucketKey(NewRegistrationsPerIPv6Range, ip)
if err != nil {
return Transaction{}, err
@ -617,7 +619,7 @@ func (builder *TransactionBuilder) NewOrderLimitTransactions(regId int64, names
// NewAccountLimitTransactions takes in an IP address from a new-account request
// and returns the set of rate limit transactions that should be evaluated
// before allowing the request to proceed.
func (builder *TransactionBuilder) NewAccountLimitTransactions(ip net.IP) ([]Transaction, error) {
func (builder *TransactionBuilder) NewAccountLimitTransactions(ip netip.Addr) ([]Transaction, error) {
makeTxnError := func(err error, limit Name) error {
return fmt.Errorf("error constructing rate limit transaction for %s rate limit: %w", limit, err)
}
@ -629,7 +631,7 @@ func (builder *TransactionBuilder) NewAccountLimitTransactions(ip net.IP) ([]Tra
}
transactions = append(transactions, txn)
if ip.To4() != nil {
if ip.Is4() {
// This request was made from an IPv4 address.
return transactions, nil
}

View File

@ -2,7 +2,7 @@ package ratelimits
import (
"fmt"
"net"
"net/netip"
"sort"
"testing"
"time"
@ -34,7 +34,7 @@ func TestNewRegistrationsPerIPAddressTransactions(t *testing.T) {
test.AssertNotError(t, err, "creating TransactionBuilder")
// A check-and-spend transaction for the global limit.
txn, err := tb.registrationsPerIPAddressTransaction(net.ParseIP("1.2.3.4"))
txn, err := tb.registrationsPerIPAddressTransaction(netip.MustParseAddr("1.2.3.4"))
test.AssertNotError(t, err, "creating transaction")
test.AssertEquals(t, txn.bucketKey, "1:1.2.3.4")
test.Assert(t, txn.check && txn.spend, "should be check-and-spend")
@ -47,7 +47,7 @@ func TestNewRegistrationsPerIPv6AddressTransactions(t *testing.T) {
test.AssertNotError(t, err, "creating TransactionBuilder")
// A check-and-spend transaction for the global limit.
txn, err := tb.registrationsPerIPv6RangeTransaction(net.ParseIP("2001:db8::1"))
txn, err := tb.registrationsPerIPv6RangeTransaction(netip.MustParseAddr("2001:db8::1"))
test.AssertNotError(t, err, "creating transaction")
test.AssertEquals(t, txn.bucketKey, "2:2001:db8::/48")
test.Assert(t, txn.check && txn.spend, "should be check-and-spend")

View File

@ -17,7 +17,6 @@ import (
"math/big"
"math/bits"
mrand "math/rand/v2"
"net"
"net/netip"
"os"
"reflect"
@ -2221,7 +2220,7 @@ func TestFinalizeAuthorization2(t *testing.T) {
authzID := createPendingAuthorization(t, sa, identifier.NewDNS("aaa"), fc.Now().Add(time.Hour))
expires := fc.Now().Add(time.Hour * 2).UTC()
attemptedAt := fc.Now()
ip, _ := net.ParseIP("1.1.1.1").MarshalText()
ip, _ := netip.MustParseAddr("1.1.1.1").MarshalText()
_, err := sa.FinalizeAuthorization2(context.Background(), &sapb.FinalizeAuthorizationRequest{
Id: authzID,
@ -2292,7 +2291,7 @@ func TestRehydrateHostPort(t *testing.T) {
expires := fc.Now().Add(time.Hour * 2).UTC()
attemptedAt := fc.Now()
ip, _ := net.ParseIP("1.1.1.1").MarshalText()
ip, _ := netip.MustParseAddr("1.1.1.1").MarshalText()
// Implicit good port with good scheme
authzID := createPendingAuthorization(t, sa, identifier.NewDNS("aaa"), fc.Now().Add(time.Hour))

View File

@ -5,7 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/netip"
"regexp"
"slices"
@ -35,9 +34,8 @@ func (mock caaMockDNS) LookupTXT(_ context.Context, hostname string) ([]string,
return nil, bdns.ResolverAddrs{"caaMockDNS"}, nil
}
func (mock caaMockDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
ip := net.ParseIP("127.0.0.1")
return []net.IP{ip}, bdns.ResolverAddrs{"caaMockDNS"}, nil
func (mock caaMockDNS) LookupHost(_ context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) {
return []netip.Addr{netip.MustParseAddr("127.0.0.1")}, bdns.ResolverAddrs{"caaMockDNS"}, nil
}
func (mock caaMockDNS) LookupCAA(_ context.Context, domain string) ([]*dns.CAA, string, bdns.ResolverAddrs, error) {
@ -601,7 +599,7 @@ func (b caaBrokenDNS) LookupTXT(_ context.Context, hostname string) ([]string, b
return nil, bdns.ResolverAddrs{"caaBrokenDNS"}, errCAABrokenDNSClient
}
func (b caaBrokenDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
func (b caaBrokenDNS) LookupHost(_ context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) {
return nil, bdns.ResolverAddrs{"caaBrokenDNS"}, errCAABrokenDNSClient
}
@ -619,9 +617,8 @@ func (h caaHijackedDNS) LookupTXT(_ context.Context, hostname string) ([]string,
return nil, bdns.ResolverAddrs{"caaHijackedDNS"}, nil
}
func (h caaHijackedDNS) LookupHost(_ context.Context, hostname string) ([]net.IP, bdns.ResolverAddrs, error) {
ip := net.ParseIP("127.0.0.1")
return []net.IP{ip}, bdns.ResolverAddrs{"caaHijackedDNS"}, nil
func (h caaHijackedDNS) LookupHost(_ context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) {
return []netip.Addr{netip.MustParseAddr("127.0.0.1")}, bdns.ResolverAddrs{"caaHijackedDNS"}, nil
}
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

View File

@ -6,7 +6,7 @@ import (
"crypto/subtle"
"encoding/base64"
"fmt"
"net"
"net/netip"
"github.com/letsencrypt/boulder/bdns"
"github.com/letsencrypt/boulder/core"
@ -15,12 +15,12 @@ import (
)
// getAddr will query for all A/AAAA records associated with hostname and return
// the preferred address, the first net.IP in the addrs slice, and all addresses
// resolved. This is the same choice made by the Go internal resolution library
// 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, bdns.ResolverAddrs, error) {
// the preferred address, the first netip.Addr in the addrs slice, and all
// addresses resolved. This is the same choice made by the Go internal
// resolution library 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 netip.Addr slice.
func (va ValidationAuthorityImpl) getAddrs(ctx context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) {
addrs, resolvers, err := va.dnsClient.LookupHost(ctx, hostname)
if err != nil {
return nil, resolvers, berrors.DNSError("%v", err)
@ -37,9 +37,9 @@ func (va ValidationAuthorityImpl) getAddrs(ctx context.Context, hostname string)
// availableAddresses takes a ValidationRecord and splits the AddressesResolved
// into a list of IPv4 and IPv6 addresses.
func availableAddresses(allAddrs []net.IP) (v4 []net.IP, v6 []net.IP) {
func availableAddresses(allAddrs []netip.Addr) (v4 []netip.Addr, v6 []netip.Addr) {
for _, addr := range allAddrs {
if addr.To4() != nil {
if addr.Is4() {
v4 = append(v4, addr)
} else {
v6 = append(v6, addr)

View File

@ -3,7 +3,6 @@ package va
import (
"context"
"fmt"
"net"
"net/netip"
"testing"
"time"
@ -127,51 +126,51 @@ func TestDNSValidationNoAuthorityOK(t *testing.T) {
}
func TestAvailableAddresses(t *testing.T) {
v6a := net.ParseIP("::1")
v6b := net.ParseIP("2001:db8::2:1") // 2001:DB8 is reserved for docs (RFC 3849)
v4a := net.ParseIP("127.0.0.1")
v4b := net.ParseIP("192.0.2.1") // 192.0.2.0/24 is reserved for docs (RFC 5737)
v6a := netip.MustParseAddr("::1")
v6b := netip.MustParseAddr("2001:db8::2:1") // 2001:DB8 is reserved for docs (RFC 3849)
v4a := netip.MustParseAddr("127.0.0.1")
v4b := netip.MustParseAddr("192.0.2.1") // 192.0.2.0/24 is reserved for docs (RFC 5737)
testcases := []struct {
input []net.IP
v4 []net.IP
v6 []net.IP
input []netip.Addr
v4 []netip.Addr
v6 []netip.Addr
}{
// An empty validation record
{
[]net.IP{},
[]net.IP{},
[]net.IP{},
[]netip.Addr{},
[]netip.Addr{},
[]netip.Addr{},
},
// A validation record with one IPv4 address
{
[]net.IP{v4a},
[]net.IP{v4a},
[]net.IP{},
[]netip.Addr{v4a},
[]netip.Addr{v4a},
[]netip.Addr{},
},
// A dual homed record with an IPv4 and IPv6 address
{
[]net.IP{v4a, v6a},
[]net.IP{v4a},
[]net.IP{v6a},
[]netip.Addr{v4a, v6a},
[]netip.Addr{v4a},
[]netip.Addr{v6a},
},
// The same as above but with the v4/v6 order flipped
{
[]net.IP{v6a, v4a},
[]net.IP{v4a},
[]net.IP{v6a},
[]netip.Addr{v6a, v4a},
[]netip.Addr{v4a},
[]netip.Addr{v6a},
},
// A validation record with just IPv6 addresses
{
[]net.IP{v6a, v6b},
[]net.IP{},
[]net.IP{v6a, v6b},
[]netip.Addr{v6a, v6b},
[]netip.Addr{},
[]netip.Addr{v6a, v6b},
},
// A validation record with interleaved IPv4/IPv6 records
{
[]net.IP{v6a, v4a, v6b, v4b},
[]net.IP{v4a, v4b},
[]net.IP{v6a, v6b},
[]netip.Addr{v6a, v4a, v6b, v4b},
[]netip.Addr{v4a, v4b},
[]netip.Addr{v6a, v6b},
},
}

View File

@ -42,7 +42,7 @@ const (
// The hostname of the preresolvedDialer is used to ensure the dial only completes
// using the pre-resolved IP/port when used for the correct host.
type preresolvedDialer struct {
ip net.IP
ip netip.Addr
port int
hostname string
timeout time.Duration
@ -170,14 +170,14 @@ type httpValidationTarget struct {
// following redirects)
query string
// all of the IP addresses available for the host
available []net.IP
available []netip.Addr
// the IP addresses that were tried for validation previously that were cycled
// out of cur by calls to nextIP()
tried []net.IP
tried []netip.Addr
// the IP addresses that will be drawn from by calls to nextIP() to set curIP
next []net.IP
next []netip.Addr
// the current IP address being used for validation (if any)
cur net.IP
cur netip.Addr
// the DNS resolver(s) that will attempt to fulfill the validation request
resolvers bdns.ResolverAddrs
}
@ -208,7 +208,7 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
port int,
path string,
query string) (*httpValidationTarget, error) {
var addrs []net.IP
var addrs []netip.Addr
var resolvers bdns.ResolverAddrs
switch ident.Type {
case identifier.TypeDNS:
@ -219,7 +219,11 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
}
addrs, resolvers = dnsAddrs, dnsResolvers
case identifier.TypeIP:
addrs = []net.IP{net.ParseIP(ident.Value)}
netIP, err := netip.ParseAddr(ident.Value)
if err != nil {
return nil, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err)
}
addrs = []netip.Addr{netIP}
default:
return nil, fmt.Errorf("unknown identifier type: %s", ident.Type)
}
@ -245,15 +249,15 @@ func (va *ValidationAuthorityImpl) newHTTPValidationTarget(
} else if !hasV6Addrs && hasV4Addrs {
// If there are no v6 addrs and there are v4 addrs then use the first v4
// address. There's no fallback address.
target.next = []net.IP{v4Addrs[0]}
target.next = []netip.Addr{v4Addrs[0]}
} else if hasV6Addrs && hasV4Addrs {
// If there are both v6 addrs and v4 addrs then use the first v6 address and
// fallback with the first v4 address.
target.next = []net.IP{v6Addrs[0], v4Addrs[0]}
target.next = []netip.Addr{v6Addrs[0], v4Addrs[0]}
} else if hasV6Addrs && !hasV4Addrs {
// If there are just v6 addrs then use the first v6 address. There's no
// fallback address.
target.next = []net.IP{v6Addrs[0]}
target.next = []netip.Addr{v6Addrs[0]}
}
// Advance the target using nextIP to populate the cur IP before returning
@ -380,7 +384,7 @@ func (va *ValidationAuthorityImpl) setupHTTPValidation(
// Get the target IP to build a preresolved dialer with
targetIP := target.cur
if targetIP == nil {
if (targetIP == netip.Addr{}) {
return nil,
record,
fmt.Errorf(

View File

@ -35,7 +35,7 @@ import (
// a dial to another host produces the expected dialerMismatchError.
func TestDialerMismatchError(t *testing.T) {
d := preresolvedDialer{
ip: net.ParseIP("127.0.0.1"),
ip: netip.MustParseAddr("127.0.0.1"),
port: 1337,
hostname: "letsencrypt.org",
}
@ -61,8 +61,8 @@ type dnsMockReturnsUnroutable struct {
*bdns.MockClient
}
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
func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) {
return []netip.Addr{netip.MustParseAddr("198.51.100.1")}, bdns.ResolverAddrs{"dnsMockReturnsUnroutable"}, nil
}
// TestDialerTimeout tests that the preresolvedDialer's DialContext
@ -199,7 +199,7 @@ func TestHTTPValidationTarget(t *testing.T) {
// order.
for i, expectedIP := range tc.ExpectedIPs {
gotIP := target.cur
if gotIP == nil {
if (gotIP == netip.Addr{}) {
t.Errorf("Expected IP %d to be %s got nil", i, expectedIP)
} else {
test.AssertEquals(t, gotIP.String(), expectedIP)
@ -531,12 +531,12 @@ func TestSetupHTTPValidation(t *testing.T) {
DnsName: "ipv4.and.ipv6.localhost",
Port: strconv.Itoa(va.httpPort),
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"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("::1"),
ResolverAddrs: []string{"MockClient"},
},
ExpectedDialer: &preresolvedDialer{
ip: net.ParseIP("::1"),
ip: netip.MustParseAddr("::1"),
port: va.httpPort,
timeout: va.singleDialTimeout,
},
@ -549,12 +549,12 @@ func TestSetupHTTPValidation(t *testing.T) {
DnsName: "ipv4.and.ipv6.localhost",
Port: strconv.Itoa(va.httpsPort),
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"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("::1"),
ResolverAddrs: []string{"MockClient"},
},
ExpectedDialer: &preresolvedDialer{
ip: net.ParseIP("::1"),
ip: netip.MustParseAddr("::1"),
port: va.httpsPort,
timeout: va.singleDialTimeout,
},
@ -886,8 +886,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: url,
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
})
}
@ -907,8 +907,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: url,
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
})
}
@ -949,8 +949,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/timeout",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -984,8 +984,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/redir-bad-proto",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1002,8 +1002,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/redir-bad-port",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1018,16 +1018,16 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/redir-bare-ipv4",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
{
DnsName: "127.0.0.1",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://127.0.0.1/ok",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
},
},
}, {
@ -1041,16 +1041,16 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "ipv6.localhost",
Port: strconv.Itoa(httpPortIPv6),
URL: "http://ipv6.localhost/redir-bare-ipv6",
AddressesResolved: []net.IP{net.ParseIP("::1")},
AddressUsed: net.ParseIP("::1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")},
AddressUsed: netip.MustParseAddr("::1"),
ResolverAddrs: []string{"MockClient"},
},
{
DnsName: "::1",
Port: strconv.Itoa(httpPortIPv6),
URL: "http://[::1]/ok",
AddressesResolved: []net.IP{net.ParseIP("::1")},
AddressUsed: net.ParseIP("::1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")},
AddressUsed: netip.MustParseAddr("::1"),
},
},
},
@ -1065,8 +1065,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/redir-path-too-long",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1082,8 +1082,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/bad-status-code",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1099,8 +1099,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/303-see-other",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1117,8 +1117,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/resp-too-big",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1134,8 +1134,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "ipv6.localhost",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://ipv6.localhost/ok",
AddressesResolved: []net.IP{net.ParseIP("::1")},
AddressUsed: net.ParseIP("::1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")},
AddressUsed: netip.MustParseAddr("::1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1150,18 +1150,18 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "ipv4.and.ipv6.localhost",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://ipv4.and.ipv6.localhost/ok",
AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
// The first validation record should have used the IPv6 addr
AddressUsed: net.ParseIP("::1"),
AddressUsed: netip.MustParseAddr("::1"),
ResolverAddrs: []string{"MockClient"},
},
{
DnsName: "ipv4.and.ipv6.localhost",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://ipv4.and.ipv6.localhost/ok",
AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")},
AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("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: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1176,8 +1176,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/ok",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1192,16 +1192,16 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/redir-uppercase-publicsuffix",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
{
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/ok",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},
@ -1221,8 +1221,8 @@ func TestFetchHTTP(t *testing.T) {
DnsName: "example.com",
Port: strconv.Itoa(httpPortIPv4),
URL: "http://example.com/printf-verbs",
AddressesResolved: []net.IP{net.ParseIP("127.0.0.1")},
AddressUsed: net.ParseIP("127.0.0.1"),
AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
AddressUsed: netip.MustParseAddr("127.0.0.1"),
ResolverAddrs: []string{"MockClient"},
},
},

View File

@ -68,7 +68,7 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(
Port: strconv.Itoa(va.tlsPort),
}
var addrs []net.IP
var addrs []netip.Addr
switch ident.Type {
case identifier.TypeDNS:
// Resolve IP addresses for the identifier
@ -79,7 +79,11 @@ func (va *ValidationAuthorityImpl) tryGetChallengeCert(
addrs, validationRecord.ResolverAddrs = dnsAddrs, dnsResolvers
validationRecord.AddressesResolved = addrs
case identifier.TypeIP:
addrs = []net.IP{net.ParseIP(ident.Value)}
netIP, err := netip.ParseAddr(ident.Value)
if err != nil {
return nil, nil, validationRecord, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err)
}
addrs = []netip.Addr{netIP}
default:
// This should never happen. The calling function should check the
// identifier type.
@ -172,11 +176,12 @@ func (va *ValidationAuthorityImpl) getChallengeCert(
if err != nil {
va.log.Infof("%s connection failure for %s. err=[%#v] errStr=[%s]", core.ChallengeTypeTLSALPN01, ident, err, err)
host, _, splitErr := net.SplitHostPort(hostPort)
if splitErr == nil && net.ParseIP(host) != nil {
netIP, ipErr := netip.ParseAddr(host)
if splitErr == nil && ipErr == nil {
// Wrap the validation error and the IP of the remote host in an
// IPError so we can display the IP in the problem details returned
// to the client.
return nil, nil, ipError{net.ParseIP(host), err}
return nil, nil, ipError{netIP, err}
}
return nil, nil, err
}

View File

@ -301,13 +301,13 @@ func maxAllowedFailures(perspectiveCount int) int {
// ipError is an error type used to pass though the IP address of the remote
// host when an error occurs during HTTP-01 and TLS-ALPN domain validation.
type ipError struct {
ip net.IP
ip netip.Addr
err error
}
// newIPError wraps an error and the IP of the remote host in an ipError so we
// can display the IP in the problem details returned to the client.
func newIPError(ip net.IP, err error) error {
func newIPError(ip netip.Addr, err error) error {
return ipError{ip: ip, err: err}
}
@ -330,7 +330,7 @@ func detailedError(err error) *probs.ProblemDetails {
var ipErr ipError
if errors.As(err, &ipErr) {
detailedErr := detailedError(ipErr.err)
if ipErr.ip == nil {
if (ipErr.ip == netip.Addr{}) {
// This should never happen.
return detailedErr
}

View File

@ -723,12 +723,12 @@ func TestMultiVALogging(t *testing.T) {
func TestDetailedError(t *testing.T) {
cases := []struct {
err error
ip net.IP
ip netip.Addr
expected string
}{
{
err: ipError{
ip: net.ParseIP("192.168.1.1"),
ip: netip.MustParseAddr("192.168.1.1"),
err: &net.OpError{
Op: "dial",
Net: "tcp",
@ -760,7 +760,7 @@ func TestDetailedError(t *testing.T) {
Err: syscall.ECONNRESET,
},
},
ip: nil,
ip: netip.Addr{},
expected: "Connection reset by peer",
},
}

View File

@ -7,8 +7,8 @@ import (
"crypto/rsa"
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"strings"
"time"
@ -136,7 +136,8 @@ func (r *responseWriterWithStatus) WriteHeader(code int) {
func (th *TopHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Check that this header is well-formed, since we assume it is when logging.
realIP := r.Header.Get("X-Real-IP")
if net.ParseIP(realIP) == nil {
_, err := netip.ParseAddr(realIP)
if err != nil {
realIP = "0.0.0.0"
}

View File

@ -13,6 +13,7 @@ import (
"math/rand/v2"
"net"
"net/http"
"net/netip"
"strconv"
"strings"
"time"
@ -660,7 +661,7 @@ func contactsToEmails(contacts *[]string) []string {
// function is returned that can be called to refund the quota if the account
// creation fails, the func will be nil if any error was encountered during the
// check.
func (wfe *WebFrontEndImpl) checkNewAccountLimits(ctx context.Context, ip net.IP) (func(), error) {
func (wfe *WebFrontEndImpl) checkNewAccountLimits(ctx context.Context, ip netip.Addr) (func(), error) {
txns, err := wfe.txnBuilder.NewAccountLimitTransactions(ip)
if err != nil {
return nil, fmt.Errorf("building new account limit transactions: %w", err)
@ -2701,16 +2702,16 @@ func (wfe *WebFrontEndImpl) RenewalInfo(ctx context.Context, logEvent *web.Reque
}
}
func extractRequesterIP(req *http.Request) (net.IP, error) {
ip := net.ParseIP(req.Header.Get("X-Real-IP"))
if ip != nil {
func extractRequesterIP(req *http.Request) (netip.Addr, error) {
ip, err := netip.ParseAddr(req.Header.Get("X-Real-IP"))
if err == nil {
return ip, nil
}
host, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return nil, err
return netip.Addr{}, err
}
return net.ParseIP(host), nil
return netip.ParseAddr(host)
}
func urlForAuthz(authz core.Authorization, request *http.Request) string {