Skip to content

Commit

Permalink
dns: Support link local IPv6 addresses (grpc#7889)
Browse files Browse the repository at this point in the history
  • Loading branch information
arjan-bal authored Dec 3, 2024
1 parent 17d08f7 commit 00272e8
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 49 deletions.
10 changes: 7 additions & 3 deletions balancer/pickfirst/pickfirstleaf/pickfirstleaf.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"errors"
"fmt"
"net"
"net/netip"
"sync"
"time"

Expand Down Expand Up @@ -417,11 +418,14 @@ func addressFamily(address string) ipAddrFamily {
if err != nil {
return ipAddrFamilyUnknown
}
ip := net.ParseIP(host)
ip, err := netip.ParseAddr(host)
if err != nil {
return ipAddrFamilyUnknown
}
switch {
case ip.To4() != nil:
case ip.Is4() || ip.Is4In6():
return ipAddrFamilyV4
case ip.To16() != nil:
case ip.Is6():
return ipAddrFamilyV6
default:
return ipAddrFamilyUnknown
Expand Down
12 changes: 6 additions & 6 deletions balancer/pickfirst/pickfirstleaf/pickfirstleaf_ext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ func (s) TestPickFirstLeaf_InterleavingIPV4Preffered(t *testing.T) {
{Addresses: []resolver.Address{{Addr: "[::FFFF:192.168.0.1]:2222"}}},
{Addresses: []resolver.Address{{Addr: "[0001:0001:0001:0001:0001:0001:0001:0001]:8080"}}},
{Addresses: []resolver.Address{{Addr: "[0002:0002:0002:0002:0002:0002:0002:0002]:8080"}}},
{Addresses: []resolver.Address{{Addr: "[0003:0003:0003:0003:0003:0003:0003:0003]:3333"}}},
{Addresses: []resolver.Address{{Addr: "[fe80::1%eth0]:3333"}}},
{Addresses: []resolver.Address{{Addr: "grpc.io:80"}}}, // not an IP.
},
},
Expand All @@ -1107,7 +1107,7 @@ func (s) TestPickFirstLeaf_InterleavingIPV4Preffered(t *testing.T) {
{Addr: "2.2.2.2:2"},
{Addr: "[0002:0002:0002:0002:0002:0002:0002:0002]:8080"},
{Addr: "3.3.3.3:3"},
{Addr: "[0003:0003:0003:0003:0003:0003:0003:0003]:3333"},
{Addr: "[fe80::1%eth0]:3333"},
{Addr: "[::FFFF:192.168.0.1]:2222"},
}

Expand Down Expand Up @@ -1135,7 +1135,7 @@ func (s) TestPickFirstLeaf_InterleavingIPv6Preffered(t *testing.T) {
{Addresses: []resolver.Address{{Addr: "3.3.3.3:3"}}},
{Addresses: []resolver.Address{{Addr: "[::FFFF:192.168.0.1]:2222"}}},
{Addresses: []resolver.Address{{Addr: "[0002:0002:0002:0002:0002:0002:0002:0002]:2222"}}},
{Addresses: []resolver.Address{{Addr: "[0003:0003:0003:0003:0003:0003:0003:0003]:3333"}}},
{Addresses: []resolver.Address{{Addr: "[fe80::1%eth0]:3333"}}},
{Addresses: []resolver.Address{{Addr: "grpc.io:80"}}}, // not an IP.
},
},
Expand All @@ -1150,7 +1150,7 @@ func (s) TestPickFirstLeaf_InterleavingIPv6Preffered(t *testing.T) {
{Addr: "grpc.io:80"},
{Addr: "[0002:0002:0002:0002:0002:0002:0002:0002]:2222"},
{Addr: "2.2.2.2:2"},
{Addr: "[0003:0003:0003:0003:0003:0003:0003:0003]:3333"},
{Addr: "[fe80::1%eth0]:3333"},
{Addr: "3.3.3.3:3"},
{Addr: "[::FFFF:192.168.0.1]:2222"},
}
Expand Down Expand Up @@ -1180,7 +1180,7 @@ func (s) TestPickFirstLeaf_InterleavingUnknownPreffered(t *testing.T) {
{Addresses: []resolver.Address{{Addr: "[::FFFF:192.168.0.1]:2222"}}},
{Addresses: []resolver.Address{{Addr: "[0001:0001:0001:0001:0001:0001:0001:0001]:8080"}}},
{Addresses: []resolver.Address{{Addr: "[0002:0002:0002:0002:0002:0002:0002:0002]:8080"}}},
{Addresses: []resolver.Address{{Addr: "[0003:0003:0003:0003:0003:0003:0003:0003]:3333"}}},
{Addresses: []resolver.Address{{Addr: "[fe80::1%eth0]:3333"}}},
{Addresses: []resolver.Address{{Addr: "example.com:80"}}}, // not an IP.
},
},
Expand All @@ -1197,7 +1197,7 @@ func (s) TestPickFirstLeaf_InterleavingUnknownPreffered(t *testing.T) {
{Addr: "2.2.2.2:2"},
{Addr: "[0002:0002:0002:0002:0002:0002:0002:0002]:8080"},
{Addr: "3.3.3.3:3"},
{Addr: "[0003:0003:0003:0003:0003:0003:0003:0003]:3333"},
{Addr: "[fe80::1%eth0]:3333"},
{Addr: "[::FFFF:192.168.0.1]:2222"},
}

Expand Down
11 changes: 6 additions & 5 deletions channelz/service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"fmt"
"net"
"net/netip"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -605,8 +606,8 @@ func (s) TestGetSocket(t *testing.T) {
lastMessageReceivedTimestamp: time.Unix(3, 0),
localFlowControlWindow: 65536,
remoteFlowControlWindow: 1024,
localAddr: &net.TCPAddr{IP: net.ParseIP("1.0.0.1"), Port: 10001},
remoteAddr: &net.TCPAddr{IP: net.ParseIP("12.0.0.1"), Port: 10002},
localAddr: &net.TCPAddr{IP: netip.MustParseAddr("1.0.0.1").AsSlice(), Port: 10001},
remoteAddr: &net.TCPAddr{IP: netip.MustParseAddr("12.0.0.1").AsSlice(), Port: 10002},
remoteName: "remote.remote",
}), newSocket(czSocket{
streamsStarted: 10,
Expand Down Expand Up @@ -637,11 +638,11 @@ func (s) TestGetSocket(t *testing.T) {
lastMessageReceivedTimestamp: time.Unix(0, 0),
localFlowControlWindow: 65536,
remoteFlowControlWindow: 10240,
localAddr: &net.IPAddr{IP: net.ParseIP("1.0.0.1")},
remoteAddr: &net.IPAddr{IP: net.ParseIP("9.0.0.1")},
localAddr: &net.IPAddr{IP: netip.MustParseAddr("1.0.0.1").AsSlice()},
remoteAddr: &net.IPAddr{IP: netip.MustParseAddr("9.0.0.1").AsSlice()},
remoteName: "",
}), newSocket(czSocket{
localAddr: &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 10001},
localAddr: &net.TCPAddr{IP: netip.MustParseAddr("127.0.0.1").AsSlice(), Port: 10001},
}), newSocket(czSocket{
security: &credentials.TLSChannelzSecurityValue{
StandardName: "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
Expand Down
15 changes: 11 additions & 4 deletions internal/credentials/xds/handshake_info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package xds
import (
"crypto/x509"
"net"
"net/netip"
"net/url"
"regexp"
"testing"
Expand Down Expand Up @@ -142,8 +143,11 @@ func TestMatchingSANExists_FailureCases(t *testing.T) {
inputCert := &x509.Certificate{
DNSNames: []string{"foo.bar.example.com", "bar.baz.test.com", "*.example.com"},
EmailAddresses: []string{"[email protected]", "[email protected]"},
IPAddresses: []net.IP{net.ParseIP("192.0.0.1"), net.ParseIP("2001:db8::68")},
URIs: []*url.URL{url1, url2},
IPAddresses: []net.IP{
netip.MustParseAddr("192.0.0.1").AsSlice(),
netip.MustParseAddr("2001:db8::68").AsSlice(),
},
URIs: []*url.URL{url1, url2},
}

tests := []struct {
Expand Down Expand Up @@ -214,8 +218,11 @@ func TestMatchingSANExists_Success(t *testing.T) {
inputCert := &x509.Certificate{
DNSNames: []string{"baz.test.com", "*.example.com"},
EmailAddresses: []string{"[email protected]", "[email protected]"},
IPAddresses: []net.IP{net.ParseIP("192.0.0.1"), net.ParseIP("2001:db8::68")},
URIs: []*url.URL{url1, url2},
IPAddresses: []net.IP{
netip.MustParseAddr("192.0.0.1").AsSlice(),
netip.MustParseAddr("2001:db8::68").AsSlice(),
},
URIs: []*url.URL{url1, url2},
}

tests := []struct {
Expand Down
37 changes: 19 additions & 18 deletions internal/resolver/dns/dns_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"fmt"
rand "math/rand/v2"
"net"
"net/netip"
"os"
"strconv"
"strings"
Expand Down Expand Up @@ -122,7 +123,7 @@ func (b *dnsBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts
}

// IP address.
if ipAddr, ok := formatIP(host); ok {
if ipAddr, err := formatIP(host); err == nil {
addr := []resolver.Address{{Addr: ipAddr + ":" + port}}
cc.UpdateState(resolver.State{Addresses: addr})
return deadResolver{}, nil
Expand Down Expand Up @@ -260,9 +261,9 @@ func (d *dnsResolver) lookupSRV(ctx context.Context) ([]resolver.Address, error)
return nil, err
}
for _, a := range lbAddrs {
ip, ok := formatIP(a)
if !ok {
return nil, fmt.Errorf("dns: error parsing A record IP address %v", a)
ip, err := formatIP(a)
if err != nil {
return nil, fmt.Errorf("dns: error parsing A record IP address %v: %v", a, err)
}
addr := ip + ":" + strconv.Itoa(int(s.Port))
newAddrs = append(newAddrs, resolver.Address{Addr: addr, ServerName: s.Target})
Expand Down Expand Up @@ -322,9 +323,9 @@ func (d *dnsResolver) lookupHost(ctx context.Context) ([]resolver.Address, error
}
newAddrs := make([]resolver.Address, 0, len(addrs))
for _, a := range addrs {
ip, ok := formatIP(a)
if !ok {
return nil, fmt.Errorf("dns: error parsing A record IP address %v", a)
ip, err := formatIP(a)
if err != nil {
return nil, fmt.Errorf("dns: error parsing A record IP address %v: %v", a, err)
}
addr := ip + ":" + d.port
newAddrs = append(newAddrs, resolver.Address{Addr: addr})
Expand All @@ -351,19 +352,19 @@ func (d *dnsResolver) lookup() (*resolver.State, error) {
return &state, nil
}

// formatIP returns ok = false if addr is not a valid textual representation of
// an IP address. If addr is an IPv4 address, return the addr and ok = true.
// formatIP returns an error if addr is not a valid textual representation of
// an IP address. If addr is an IPv4 address, return the addr and error = nil.
// 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 {
return "", false
// error = nil.
func formatIP(addr string) (string, error) {
ip, err := netip.ParseAddr(addr)
if err != nil {
return "", err
}
if ip.To4() != nil {
return addr, true
if ip.Is4() {
return addr, nil
}
return "[" + addr + "]", true
return "[" + addr + "]", nil
}

// parseTarget takes the user input target string and default port, returns
Expand All @@ -379,7 +380,7 @@ func parseTarget(target, defaultPort string) (host, port string, err error) {
if target == "" {
return "", "", internal.ErrMissingAddr
}
if ip := net.ParseIP(target); ip != nil {
if _, err := netip.ParseAddr(target); err == nil {
// target is an IPv4 or IPv6(without brackets) address
return target, defaultPort, nil
}
Expand Down
11 changes: 10 additions & 1 deletion internal/resolver/dns/dns_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,16 @@ func (s) TestIPResolver(t *testing.T) {
target: "[2001:db8::1]:http",
wantAddr: []resolver.Address{{Addr: "[2001:db8::1]:http"}},
},
// TODO(yuxuanli): zone support?
{
name: "ipv6 with zone and port",
target: "[fe80::1%25eth0]:1234",
wantAddr: []resolver.Address{{Addr: "[fe80::1%eth0]:1234"}},
},
{
name: "ipv6 with zone and default port",
target: "fe80::1%25eth0",
wantAddr: []resolver.Address{{Addr: "[fe80::1%eth0]:443"}},
},
}

for _, test := range tests {
Expand Down
9 changes: 5 additions & 4 deletions internal/testutils/fakegrpclb/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"fmt"
"io"
"net"
"net/netip"
"strconv"
"sync"
"time"
Expand Down Expand Up @@ -85,17 +86,17 @@ func NewServer(params ServerParams) (*Server, error) {
if err != nil {
return nil, fmt.Errorf("failed to parse list of backend address %q: %v", addr, err)
}
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, fmt.Errorf("failed to parse ip: %q", ipStr)
ip, err := netip.ParseAddr(ipStr)
if err != nil {
return nil, fmt.Errorf("failed to parse ip %q: %v", ipStr, err)
}
port, err := strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("failed to convert port %q to int", portStr)
}
logger.Infof("Adding backend ip: %q, port: %d to server list", ip.String(), port)
servers = append(servers, &lbpb.Server{
IpAddress: ip,
IpAddress: ip.AsSlice(),
Port: int32(port),
})
}
Expand Down
7 changes: 5 additions & 2 deletions internal/xds/rbac/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"net"
"net/netip"
"regexp"

v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
Expand Down Expand Up @@ -344,7 +345,8 @@ func newRemoteIPMatcher(cidrRange *v3corepb.CidrRange) (*remoteIPMatcher, error)
}

func (sim *remoteIPMatcher) match(data *rpcData) bool {
return sim.ipNet.Contains(net.IP(net.ParseIP(data.peerInfo.Addr.String())))
ip, _ := netip.ParseAddr(data.peerInfo.Addr.String())
return sim.ipNet.Contains(net.IP(ip.AsSlice()))
}

type localIPMatcher struct {
Expand All @@ -361,7 +363,8 @@ func newLocalIPMatcher(cidrRange *v3corepb.CidrRange) (*localIPMatcher, error) {
}

func (dim *localIPMatcher) match(data *rpcData) bool {
return dim.ipNet.Contains(net.IP(net.ParseIP(data.localAddr.String())))
ip, _ := netip.ParseAddr(data.localAddr.String())
return dim.ipNet.Contains(net.IP(ip.AsSlice()))
}

// portMatcher matches on whether the destination port of the RPC matches the
Expand Down
4 changes: 4 additions & 0 deletions scripts/vet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ git grep '"github.com/envoyproxy/go-control-plane/envoy' -- '*.go' ':(exclude)*.
git grep -e 'context.Background()' --or -e 'context.TODO()' -- "*_test.go" | grep -v "benchmark/primitives/context_test.go" | grep -v "credential
s/google" | grep -v "internal/transport/" | grep -v "xds/internal/" | grep -v "security/advancedtls" | grep -v 'context.WithTimeout(' | not grep -v 'context.WithCancel('

# Disallow usage of net.ParseIP in favour of netip.ParseAddr as the former
# can't parse link local IPv6 addresses.
not git grep 'net.ParseIP' -- '*.go'

misspell -error .

# - gofmt, goimports, go vet, go mod tidy.
Expand Down
3 changes: 2 additions & 1 deletion security/advancedtls/crl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"fmt"
"math/big"
"net"
"net/netip"
"os"
"path"
"strings"
Expand Down Expand Up @@ -463,7 +464,7 @@ func setupTLSConn(t *testing.T) (net.Listener, *x509.Certificate, *ecdsa.Private
Subject: pkix.Name{CommonName: "test-cert"},
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
IPAddresses: []net.IP{net.ParseIP("::1")},
IPAddresses: []net.IP{netip.MustParseAddr("::1").AsSlice()},
CRLDistributionPoints: []string{"http://static.corp.google.com/crl/campus-sln/borg"},
}

Expand Down
5 changes: 3 additions & 2 deletions test/local_creds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"context"
"fmt"
"net"
"net/netip"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -181,11 +182,11 @@ func testLocalCredsE2EFail(dopts []grpc.DialOption) error {

var fakeClientAddr, fakeServerAddr net.Addr
fakeClientAddr = &net.IPAddr{
IP: net.ParseIP("10.8.9.10"),
IP: netip.MustParseAddr("10.8.9.10").AsSlice(),
Zone: "",
}
fakeServerAddr = &net.IPAddr{
IP: net.ParseIP("10.8.9.11"),
IP: netip.MustParseAddr("10.8.9.11").AsSlice(),
Zone: "",
}

Expand Down
7 changes: 4 additions & 3 deletions xds/internal/xdsclient/xdsresource/filter_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"errors"
"fmt"
"net"
"net/netip"
"strings"
"testing"

Expand Down Expand Up @@ -2438,7 +2439,7 @@ func (s) TestLookup_Successes(t *testing.T) {
lis: lisWithoutDefaultChain,
params: FilterChainLookupParams{
IsUnspecifiedListener: true,
DestAddr: net.ParseIP("2001:68::db8"),
DestAddr: netip.MustParseAddr("2001:68::db8").AsSlice(),
SourceAddr: net.IPv4(10, 1, 1, 1),
SourcePort: 1,
},
Expand Down Expand Up @@ -2468,8 +2469,8 @@ func (s) TestLookup_Successes(t *testing.T) {
lis: lisWithoutDefaultChain,
params: FilterChainLookupParams{
IsUnspecifiedListener: true,
DestAddr: net.ParseIP("2001:68::1"),
SourceAddr: net.ParseIP("2001:68::2"),
DestAddr: netip.MustParseAddr("2001:68::1").AsSlice(),
SourceAddr: netip.MustParseAddr("2001:68::2").AsSlice(),
SourcePort: 1,
},
wantFC: &FilterChain{
Expand Down

0 comments on commit 00272e8

Please sign in to comment.