Skip to content

Commit

Permalink
support IPv6 IP Pods during AZ detection (#2375)
Browse files Browse the repository at this point in the history
  • Loading branch information
M00nF1sh authored Nov 25, 2021
1 parent 837ce7b commit e87a5e9
Show file tree
Hide file tree
Showing 9 changed files with 780 additions and 211 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6
gomodules.xyz/jsonpatch/v2 v2.2.0
helm.sh/helm/v3 v3.6.1
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 // indirect
k8s.io/api v0.21.2
k8s.io/apimachinery v0.21.2
k8s.io/cli-runtime v0.21.2
Expand Down
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNE
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
Expand Down Expand Up @@ -850,6 +851,10 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37 h1:Tx9kY6yUkLge/pFG7IEMwDZy6CS2ajFc9TvQdPCW0uA=
go4.org/unsafe/assume-no-moving-gc v0.0.0-20211027215541-db492cf91b37/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down Expand Up @@ -1162,6 +1167,8 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6 h1:acCzuUSQ79tGsM/O50VRFySfMm19IoMKL+sZztZkCxw=
inet.af/netaddr v0.0.0-20211027220019-c74959edd3b6/go.mod h1:y3MGhcFMlh0KZPMuXXow8mpjxxAk3yoDNsp4cQz54i8=
k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
Expand Down
28 changes: 28 additions & 0 deletions pkg/networking/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package networking

import "inet.af/netaddr"

// TODO: replace netaddr package with built-in netip package once golang 1.18 released: https://pkg.go.dev/net/netip@master#Prefix

// ParseCIDRs will parse CIDRs in string format into parsed IPPrefix
func ParseCIDRs(cidrs []string) ([]netaddr.IPPrefix, error) {
var ipPrefixes []netaddr.IPPrefix
for _, cidr := range cidrs {
ipPrefix, err := netaddr.ParseIPPrefix(cidr)
if err != nil {
return nil, err
}
ipPrefixes = append(ipPrefixes, ipPrefix)
}
return ipPrefixes, nil
}

// IsIPWithinCIDRs checks whether specific IP is in IPv4 CIDR or IPv6 CIDRs.
func IsIPWithinCIDRs(ip netaddr.IP, cidrs []netaddr.IPPrefix) bool {
for _, cidr := range cidrs {
if cidr.Contains(ip) {
return true
}
}
return false
}
139 changes: 139 additions & 0 deletions pkg/networking/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package networking

import (
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"inet.af/netaddr"
"testing"
)

func TestParseCIDRs(t *testing.T) {
type args struct {
cidrs []string
}
tests := []struct {
name string
args args
want []netaddr.IPPrefix
wantErr error
}{
{
name: "has one valid CIDR",
args: args{
cidrs: []string{"192.168.5.100/16"},
},
want: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("192.168.5.100/16"),
},
},
{
name: "has multiple valid CIDRs",
args: args{
cidrs: []string{"192.168.5.100/16", "10.100.0.0/16"},
},
want: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("192.168.5.100/16"),
netaddr.MustParseIPPrefix("10.100.0.0/16"),
},
},
{
name: "has one invalid CIDR",
args: args{
cidrs: []string{"192.168.5.100/16", "10.100.0.0"},
},
wantErr: errors.New("netaddr.ParseIPPrefix(\"10.100.0.0\"): no '/'"),
},
{
name: "empty CIDRs",
args: args{
cidrs: []string{},
},
want: nil,
},
{
name: "nil CIDRs",
args: args{
cidrs: nil,
},
want: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseCIDRs(tt.args.cidrs)
if tt.wantErr != nil {
assert.EqualError(t, err, tt.wantErr.Error())
} else {
assert.NoError(t, err)
assert.Equal(t, tt.want, got)
}
})
}
}

func TestIsIPWithinCIDRs(t *testing.T) {
type args struct {
ip netaddr.IP
cidrs []netaddr.IPPrefix
}
tests := []struct {
name string
args args
want bool
}{
{
name: "ipv4 address within CIDRs",
args: args{
ip: netaddr.MustParseIP("192.168.1.42"),
cidrs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.100.0.0/16"),
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("2600:1f14:f8c:2700::/56"),
},
},
want: true,
},
{
name: "ipv4 address not within CIDRs",
args: args{
ip: netaddr.MustParseIP("172.16.1.42"),
cidrs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.100.0.0/16"),
netaddr.MustParseIPPrefix("192.168.0.0/16"),
netaddr.MustParseIPPrefix("2600:1f14:f8c:2700::/56"),
},
},
want: false,
},
{
name: "ipv6 address within CIDRs",
args: args{
ip: netaddr.MustParseIP("2600:1f14:f8c:2701:a740::"),
cidrs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.100.0.0/16"),
netaddr.MustParseIPPrefix("2700:1f14:f8c:2700::/56"),
netaddr.MustParseIPPrefix("2600:1f14:f8c:2700::/56"),
},
},
want: true,
},
{
name: "ipv6 address not within CIDRs",
args: args{
ip: netaddr.MustParseIP("2800:1f14:f8c:2701:a740::"),
cidrs: []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.100.0.0/16"),
netaddr.MustParseIPPrefix("2700:1f14:f8c:2700::/56"),
netaddr.MustParseIPPrefix("2600:1f14:f8c:2700::/56"),
},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsIPWithinCIDRs(tt.args.ip, tt.args.cidrs)
assert.Equal(t, tt.want, got)
})
}
}
90 changes: 72 additions & 18 deletions pkg/networking/vpc_info_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,56 @@ import (

const defaultVPCInfoCacheTTL = 10 * time.Minute

type VPCInfo ec2sdk.Vpc

// AssociatedIPv4CIDRs computes associated IPv4CIDRs for VPC.
func (vpc *VPCInfo) AssociatedIPv4CIDRs() []string {
var ipv4CIDRs []string
for _, cidr := range vpc.CidrBlockAssociationSet {
if awssdk.StringValue(cidr.CidrBlockState.State) != ec2sdk.VpcCidrBlockStateCodeAssociated {
continue
}
ipv4CIDRs = append(ipv4CIDRs, awssdk.StringValue(cidr.CidrBlock))
}
return ipv4CIDRs
}

// AssociatedIPv6CIDRs computes associated IPv6CIDRs for VPC.
func (vpc *VPCInfo) AssociatedIPv6CIDRs() []string {
var ipv6CIDRs []string
for _, cidr := range vpc.Ipv6CidrBlockAssociationSet {
if awssdk.StringValue(cidr.Ipv6CidrBlockState.State) != ec2sdk.VpcCidrBlockStateCodeAssociated {
continue
}
ipv6CIDRs = append(ipv6CIDRs, awssdk.StringValue(cidr.Ipv6CidrBlock))
}
return ipv6CIDRs
}

type FetchVPCInfoOptions struct {
// whether to ignore cache and reload VPC Info from AWS directly.
ReloadIgnoringCache bool
}

// ApplyOptions applies FetchVPCInfoOption options
func (opts *FetchVPCInfoOptions) ApplyOptions(options ...FetchVPCInfoOption) {
for _, option := range options {
option(opts)
}
}

type FetchVPCInfoOption func(opts *FetchVPCInfoOptions)

// FetchVPCInfoWithoutCache is an option that sets the ReloadIgnoringCache to true.
func FetchVPCInfoWithoutCache() FetchVPCInfoOption {
return func(opts *FetchVPCInfoOptions) {
opts.ReloadIgnoringCache = true
}
}

// VPCInfoProvider is responsible for providing VPC info.
type VPCInfoProvider interface {
FetchVPCInfo(ctx context.Context, vpcID string) (*ec2sdk.Vpc, error)
FetchVPCInfo(ctx context.Context, vpcID string, opts ...FetchVPCInfoOption) (VPCInfo, error)
}

// NewDefaultVPCInfoProvider constructs new defaultVPCInfoProvider.
Expand All @@ -42,48 +89,55 @@ type defaultVPCInfoProvider struct {
logger logr.Logger
}

func (p *defaultVPCInfoProvider) FetchVPCInfo(ctx context.Context, vpcID string) (*ec2sdk.Vpc, error) {
if vpcInfo := p.fetchVPCInfoFromCache(); vpcInfo != nil {
return vpcInfo, nil
// FetchVPCInfo fetches VPC info for vpcID.
func (p *defaultVPCInfoProvider) FetchVPCInfo(ctx context.Context, vpcID string, opts ...FetchVPCInfoOption) (VPCInfo, error) {
fetchOpts := FetchVPCInfoOptions{
ReloadIgnoringCache: false,
}
fetchOpts.ApplyOptions(opts...)

if !fetchOpts.ReloadIgnoringCache {
if vpcInfo, exists := p.fetchVPCInfoFromCache(vpcID); exists {
return vpcInfo, nil
}
}

// Fetch VPC info from the AWS API and cache response before returning.
vpcInfo, err := p.fetchVPCInfoFromAWS(ctx, vpcID)
if err != nil {
return nil, err
return VPCInfo{}, err
}
p.saveVPCInfoToCache(vpcInfo)

p.saveVPCInfoToCache(vpcID, vpcInfo)
return vpcInfo, nil
}

func (p *defaultVPCInfoProvider) fetchVPCInfoFromCache() *ec2sdk.Vpc {
// fetchVPCInfoFromCache fetches VPC info for vpcID from cache.
func (p *defaultVPCInfoProvider) fetchVPCInfoFromCache(vpcID string) (VPCInfo, bool) {
p.vpcInfoCacheMutex.RLock()
defer p.vpcInfoCacheMutex.RUnlock()

if rawCacheItem, exists := p.vpcInfoCache.Get("vpcInfo"); exists {
return rawCacheItem.(*ec2sdk.Vpc)
if rawCacheItem, exists := p.vpcInfoCache.Get(vpcID); exists {
return rawCacheItem.(VPCInfo), true
}

return nil
return VPCInfo{}, false
}

func (p *defaultVPCInfoProvider) saveVPCInfoToCache(vpcInfo *ec2sdk.Vpc) {
// saveVPCInfoToCache saves VPC info for vpcID into cache.
func (p *defaultVPCInfoProvider) saveVPCInfoToCache(vpcID string, vpcInfo VPCInfo) {
p.vpcInfoCacheMutex.Lock()
defer p.vpcInfoCacheMutex.Unlock()

p.vpcInfoCache.Set("vpcInfo", vpcInfo, p.vpcInfoCacheTTL)
p.vpcInfoCache.Set(vpcID, vpcInfo, p.vpcInfoCacheTTL)
}

// fetchVPCInfoFromAWS will fetch VPC info from the AWS API.
func (p *defaultVPCInfoProvider) fetchVPCInfoFromAWS(ctx context.Context, vpcID string) (*ec2sdk.Vpc, error) {
func (p *defaultVPCInfoProvider) fetchVPCInfoFromAWS(ctx context.Context, vpcID string) (VPCInfo, error) {
req := &ec2sdk.DescribeVpcsInput{
VpcIds: []*string{awssdk.String(vpcID)},
}
resp, err := p.ec2Client.DescribeVpcsWithContext(ctx, req)
if err != nil {
return nil, err
return VPCInfo{}, err
}

return resp.Vpcs[0], nil
return VPCInfo(*resp.Vpcs[0]), nil
}
31 changes: 18 additions & 13 deletions pkg/networking/vpc_info_provider_mocks.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit e87a5e9

Please sign in to comment.