Skip to content

Commit 1bdf4b4

Browse files
committed
Filter EndpointSlices by IP family for target group registration
The endpoint resolver listed every EndpointSlice for a Service without considering address family, so an IPv6 target group could receive IPv4 pod addresses and get rejected by RegisterTargets. The resolver now filters slices to those matching the TGB's ipAddressType.
1 parent 9b23d51 commit 1bdf4b4

3 files changed

Lines changed: 152 additions & 11 deletions

File tree

pkg/backend/endpoint_resolver.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package backend
33
import (
44
"context"
55
"fmt"
6+
"strings"
67

78
awssdk "github.com/aws/aws-sdk-go-v2/aws"
89
"github.com/go-logr/logr"
@@ -12,6 +13,7 @@ import (
1213
apierrors "k8s.io/apimachinery/pkg/api/errors"
1314
"k8s.io/apimachinery/pkg/types"
1415
"k8s.io/apimachinery/pkg/util/intstr"
16+
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
1517
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
1618
"sigs.k8s.io/controller-runtime/pkg/client"
1719
)
@@ -22,7 +24,9 @@ var ErrNotFound = errors.New("backend not found")
2224
type EndpointResolver interface {
2325
// ResolvePodEndpoints will resolve endpoints backed by pods directly,
2426
// returns resolved podEndpoints.
25-
ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString) ([]PodEndpoint, error)
27+
// targetIPAddressType filters EndpointSlices by AddressType. Pass empty to disable filtering.
28+
ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString,
29+
targetIPAddressType elbv2api.TargetGroupIPAddressType) ([]PodEndpoint, error)
2630

2731
// ResolveNodePortEndpoints will resolve endpoints backed by nodePort.
2832
ResolveNodePortEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString,
@@ -54,12 +58,12 @@ type defaultEndpointResolver struct {
5458
logger logr.Logger
5559
}
5660

57-
func (r *defaultEndpointResolver) ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString) ([]PodEndpoint, error) {
61+
func (r *defaultEndpointResolver) ResolvePodEndpoints(ctx context.Context, svcKey types.NamespacedName, port intstr.IntOrString, targetIPAddressType elbv2api.TargetGroupIPAddressType) ([]PodEndpoint, error) {
5862
_, svcPort, err := r.findServiceAndServicePort(ctx, svcKey, port)
5963
if err != nil {
6064
return nil, err
6165
}
62-
endpointsDataList, err := r.computeServiceEndpointsData(ctx, svcKey)
66+
endpointsDataList, err := r.computeServiceEndpointsData(ctx, svcKey, targetIPAddressType)
6367
if err != nil {
6468
return nil, err
6569
}
@@ -107,7 +111,7 @@ func (r *defaultEndpointResolver) ResolveNodePortEndpoints(ctx context.Context,
107111
return endpoints, nil
108112
}
109113

110-
func (r *defaultEndpointResolver) computeServiceEndpointsData(ctx context.Context, svcKey types.NamespacedName) ([]EndpointsData, error) {
114+
func (r *defaultEndpointResolver) computeServiceEndpointsData(ctx context.Context, svcKey types.NamespacedName, targetIPAddressType elbv2api.TargetGroupIPAddressType) ([]EndpointsData, error) {
111115
var endpointsDataList []EndpointsData
112116
if r.endpointSliceEnabled {
113117
epSliceList := &discovery.EndpointSliceList{}
@@ -116,7 +120,7 @@ func (r *defaultEndpointResolver) computeServiceEndpointsData(ctx context.Contex
116120
client.MatchingLabels{discovery.LabelServiceName: svcKey.Name}); err != nil {
117121
return nil, err
118122
}
119-
endpointsDataList = buildEndpointsDataFromEndpointSliceList(epSliceList)
123+
endpointsDataList = buildEndpointsDataFromEndpointSliceList(epSliceList, targetIPAddressType)
120124
} else {
121125
eps := &corev1.Endpoints{}
122126
if err := r.k8sClient.Get(ctx, svcKey, eps); err != nil {
@@ -262,9 +266,13 @@ func buildEndpointsDataFromEndpoints(eps *corev1.Endpoints) []EndpointsData {
262266
return endpointsDataList
263267
}
264268

265-
func buildEndpointsDataFromEndpointSliceList(epsList *discovery.EndpointSliceList) []EndpointsData {
269+
func buildEndpointsDataFromEndpointSliceList(epsList *discovery.EndpointSliceList, targetIPAddressType elbv2api.TargetGroupIPAddressType) []EndpointsData {
266270
var endpointsDataList []EndpointsData
267271
for _, epSlice := range epsList.Items {
272+
// TargetGroupIPAddressType is "ipv4"/"ipv6"; EndpointSlice.AddressType is "IPv4"/"IPv6".
273+
if targetIPAddressType != "" && !strings.EqualFold(string(epSlice.AddressType), string(targetIPAddressType)) {
274+
continue
275+
}
268276
endpointsDataList = append(endpointsDataList, EndpointsData{
269277
Ports: epSlice.Ports,
270278
Endpoints: epSlice.Endpoints,

pkg/backend/endpoint_resolver_test.go

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"k8s.io/apimachinery/pkg/types"
1818
"k8s.io/apimachinery/pkg/util/intstr"
1919
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
20+
elbv2api "sigs.k8s.io/aws-load-balancer-controller/apis/elbv2/v1beta1"
2021
"sigs.k8s.io/aws-load-balancer-controller/pkg/equality"
2122
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
2223
ctrl "sigs.k8s.io/controller-runtime"
@@ -1306,7 +1307,7 @@ func Test_defaultEndpointResolver_ResolvePodEndpoints(t *testing.T) {
13061307
endpointSliceEnabled: tt.fields.endpointSliceEnabled,
13071308
logger: logr.New(&log.NullLogSink{}),
13081309
}
1309-
got, err := r.ResolvePodEndpoints(ctx, tt.args.svcKey, tt.args.port)
1310+
got, err := r.ResolvePodEndpoints(ctx, tt.args.svcKey, tt.args.port, "")
13101311
if tt.wantErr != nil {
13111312
assert.EqualError(t, err, tt.wantErr.Error())
13121313
} else {
@@ -1928,7 +1929,7 @@ func Test_defaultEndpointResolver_computeServiceEndpointsData(t *testing.T) {
19281929
k8sClient: k8sClient,
19291930
endpointSliceEnabled: tt.fields.endpointSliceEnabled,
19301931
}
1931-
got, err := r.computeServiceEndpointsData(context.Background(), tt.args.svcKey)
1932+
got, err := r.computeServiceEndpointsData(context.Background(), tt.args.svcKey, "")
19321933
if tt.wantErr != nil {
19331934
assert.EqualError(t, err, tt.wantErr.Error())
19341935
} else {
@@ -2449,7 +2450,8 @@ func Test_buildEndpointsDataFromEndpoints(t *testing.T) {
24492450

24502451
func Test_buildEndpointsDataFromEndpointSliceList(t *testing.T) {
24512452
type args struct {
2452-
epsList *discovery.EndpointSliceList
2453+
epsList *discovery.EndpointSliceList
2454+
targetIPAddressType elbv2api.TargetGroupIPAddressType
24532455
}
24542456
tests := []struct {
24552457
name string
@@ -2554,10 +2556,132 @@ func Test_buildEndpointsDataFromEndpointSliceList(t *testing.T) {
25542556
},
25552557
want: nil,
25562558
},
2559+
{
2560+
name: "ipv6 target group filters out IPv4 slices",
2561+
args: args{
2562+
targetIPAddressType: elbv2api.TargetGroupIPAddressTypeIPv6,
2563+
epsList: &discovery.EndpointSliceList{
2564+
Items: []discovery.EndpointSlice{
2565+
{
2566+
AddressType: discovery.AddressTypeIPv4,
2567+
Ports: []discovery.EndpointPort{
2568+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2569+
},
2570+
Endpoints: []discovery.Endpoint{
2571+
{Addresses: []string{"10.0.0.1"}},
2572+
},
2573+
},
2574+
{
2575+
AddressType: discovery.AddressTypeIPv6,
2576+
Ports: []discovery.EndpointPort{
2577+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2578+
},
2579+
Endpoints: []discovery.Endpoint{
2580+
{Addresses: []string{"2600::1"}},
2581+
},
2582+
},
2583+
},
2584+
},
2585+
},
2586+
want: []EndpointsData{
2587+
{
2588+
Ports: []discovery.EndpointPort{
2589+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2590+
},
2591+
Endpoints: []discovery.Endpoint{
2592+
{Addresses: []string{"2600::1"}},
2593+
},
2594+
},
2595+
},
2596+
},
2597+
{
2598+
name: "ipv4 target group filters out IPv6 slices",
2599+
args: args{
2600+
targetIPAddressType: elbv2api.TargetGroupIPAddressTypeIPv4,
2601+
epsList: &discovery.EndpointSliceList{
2602+
Items: []discovery.EndpointSlice{
2603+
{
2604+
AddressType: discovery.AddressTypeIPv4,
2605+
Ports: []discovery.EndpointPort{
2606+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2607+
},
2608+
Endpoints: []discovery.Endpoint{
2609+
{Addresses: []string{"10.0.0.1"}},
2610+
},
2611+
},
2612+
{
2613+
AddressType: discovery.AddressTypeIPv6,
2614+
Ports: []discovery.EndpointPort{
2615+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2616+
},
2617+
Endpoints: []discovery.Endpoint{
2618+
{Addresses: []string{"2600::1"}},
2619+
},
2620+
},
2621+
},
2622+
},
2623+
},
2624+
want: []EndpointsData{
2625+
{
2626+
Ports: []discovery.EndpointPort{
2627+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2628+
},
2629+
Endpoints: []discovery.Endpoint{
2630+
{Addresses: []string{"10.0.0.1"}},
2631+
},
2632+
},
2633+
},
2634+
},
2635+
{
2636+
name: "empty target ip address type preserves legacy unfiltered behavior",
2637+
args: args{
2638+
targetIPAddressType: "",
2639+
epsList: &discovery.EndpointSliceList{
2640+
Items: []discovery.EndpointSlice{
2641+
{
2642+
AddressType: discovery.AddressTypeIPv4,
2643+
Ports: []discovery.EndpointPort{
2644+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2645+
},
2646+
Endpoints: []discovery.Endpoint{
2647+
{Addresses: []string{"10.0.0.1"}},
2648+
},
2649+
},
2650+
{
2651+
AddressType: discovery.AddressTypeIPv6,
2652+
Ports: []discovery.EndpointPort{
2653+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2654+
},
2655+
Endpoints: []discovery.Endpoint{
2656+
{Addresses: []string{"2600::1"}},
2657+
},
2658+
},
2659+
},
2660+
},
2661+
},
2662+
want: []EndpointsData{
2663+
{
2664+
Ports: []discovery.EndpointPort{
2665+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2666+
},
2667+
Endpoints: []discovery.Endpoint{
2668+
{Addresses: []string{"10.0.0.1"}},
2669+
},
2670+
},
2671+
{
2672+
Ports: []discovery.EndpointPort{
2673+
{Name: awssdk.String("http"), Port: awssdk.Int32(80)},
2674+
},
2675+
Endpoints: []discovery.Endpoint{
2676+
{Addresses: []string{"2600::1"}},
2677+
},
2678+
},
2679+
},
2680+
},
25572681
}
25582682
for _, tt := range tests {
25592683
t.Run(tt.name, func(t *testing.T) {
2560-
got := buildEndpointsDataFromEndpointSliceList(tt.args.epsList)
2684+
got := buildEndpointsDataFromEndpointSliceList(tt.args.epsList, tt.args.targetIPAddressType)
25612685
assert.Equal(t, tt.want, got)
25622686
})
25632687
}

pkg/targetgroupbinding/resource_manager.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ func (m *defaultResourceManager) reconcileWithIPTargetType(ctx context.Context,
172172

173173
oldCheckPoint := GetTGBReconcileCheckpoint(tgb)
174174

175-
endpoints, err = m.endpointResolver.ResolvePodEndpoints(ctx, svcKey, tgb.Spec.ServiceRef.Port)
175+
endpoints, err = m.endpointResolver.ResolvePodEndpoints(ctx, svcKey, tgb.Spec.ServiceRef.Port,
176+
tgbTargetIPAddressType(tgb))
176177

177178
if err != nil {
178179
if errors.Is(err, backend.ErrNotFound) {
@@ -946,3 +947,11 @@ func (m *defaultResourceManager) getPodAvailabilityZone(ctx context.Context, pod
946947

947948
return &az, nil
948949
}
950+
951+
// tgbTargetIPAddressType returns the TGB's IP address type, defaulting to IPv4 when unset.
952+
func tgbTargetIPAddressType(tgb *elbv2api.TargetGroupBinding) elbv2api.TargetGroupIPAddressType {
953+
if tgb.Spec.IPAddressType == nil {
954+
return elbv2api.TargetGroupIPAddressTypeIPv4
955+
}
956+
return *tgb.Spec.IPAddressType
957+
}

0 commit comments

Comments
 (0)