Skip to content

Commit 226082b

Browse files
juwon8891zirain
andauthored
xds: fix ipFamily always nil (envoyproxy#4782)
* chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> * chore: fix ipFamily always nil Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> --------- Signed-off-by: Juwon Hwang (Kevin) <juwon8891@gmail.com> Co-authored-by: zirain <zirain2009@gmail.com>
1 parent c5f7fc1 commit 226082b

File tree

13 files changed

+323
-30
lines changed

13 files changed

+323
-30
lines changed

internal/gatewayapi/helpers.go

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -610,20 +610,56 @@ func setIfNil[T any](target **T, value *T) {
610610
}
611611
}
612612

613-
func getIPFamily(envoyProxy *egv1a1.EnvoyProxy) *ir.IPFamily {
613+
// getServiceIPFamily returns the IP family configuration from a Kubernetes Service
614+
// following the dual-stack service configuration scenarios:
615+
// https://kubernetes.io/docs/concepts/services-networking/dual-stack/#dual-stack-service-configuration-scenarios
616+
//
617+
// The IP family is determined in the following order:
618+
// 1. Service.Spec.IPFamilyPolicy == RequireDualStack -> DualStack
619+
// 2. Service.Spec.IPFamilies length > 1 -> DualStack
620+
// 3. Service.Spec.IPFamilies[0] -> IPv4 or IPv6
621+
// 4. nil if not specified
622+
func getServiceIPFamily(service *corev1.Service) *egv1a1.IPFamily {
623+
if service == nil {
624+
return nil
625+
}
626+
627+
// If ipFamilyPolicy is RequireDualStack, return DualStack
628+
if service.Spec.IPFamilyPolicy != nil &&
629+
*service.Spec.IPFamilyPolicy == corev1.IPFamilyPolicyRequireDualStack {
630+
return ptr.To(egv1a1.DualStack)
631+
}
632+
633+
// Check ipFamilies array
634+
if len(service.Spec.IPFamilies) > 0 {
635+
if len(service.Spec.IPFamilies) > 1 {
636+
return ptr.To(egv1a1.DualStack)
637+
}
638+
switch service.Spec.IPFamilies[0] {
639+
case corev1.IPv4Protocol:
640+
return ptr.To(egv1a1.IPv4)
641+
case corev1.IPv6Protocol:
642+
return ptr.To(egv1a1.IPv6)
643+
}
644+
}
645+
646+
return nil
647+
}
648+
649+
// getEnvoyIPFamily returns the IPFamily configuration from EnvoyProxy
650+
func getEnvoyIPFamily(envoyProxy *egv1a1.EnvoyProxy) *egv1a1.IPFamily {
614651
if envoyProxy == nil || envoyProxy.Spec.IPFamily == nil {
615652
return nil
616653
}
617-
var result ir.IPFamily
654+
618655
switch *envoyProxy.Spec.IPFamily {
619656
case egv1a1.IPv4:
620-
result = ir.IPv4
657+
return ptr.To(egv1a1.IPv4)
621658
case egv1a1.IPv6:
622-
result = ir.IPv6
659+
return ptr.To(egv1a1.IPv6)
623660
case egv1a1.DualStack:
624-
result = ir.DualStack
661+
return ptr.To(egv1a1.DualStack)
625662
default:
626663
return nil
627664
}
628-
return &result
629665
}

internal/gatewayapi/helpers_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"testing"
1616

1717
"github.com/stretchr/testify/require"
18+
corev1 "k8s.io/api/core/v1"
1819
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1920
"k8s.io/apimachinery/pkg/runtime/schema"
2021
"k8s.io/apimachinery/pkg/types"
@@ -551,3 +552,67 @@ func TestIsRefToGateway(t *testing.T) {
551552
})
552553
}
553554
}
555+
556+
func TestGetServiceIPFamily(t *testing.T) {
557+
testCases := []struct {
558+
name string
559+
service *corev1.Service
560+
expected *egv1a1.IPFamily
561+
}{
562+
{
563+
name: "nil service",
564+
service: nil,
565+
expected: nil,
566+
},
567+
{
568+
name: "require dual stack",
569+
service: &corev1.Service{
570+
Spec: corev1.ServiceSpec{
571+
IPFamilyPolicy: ptr.To(corev1.IPFamilyPolicyRequireDualStack),
572+
},
573+
},
574+
expected: ptr.To(egv1a1.DualStack),
575+
},
576+
{
577+
name: "multiple ip families",
578+
service: &corev1.Service{
579+
Spec: corev1.ServiceSpec{
580+
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol, corev1.IPv6Protocol},
581+
},
582+
},
583+
expected: ptr.To(egv1a1.DualStack),
584+
},
585+
{
586+
name: "ipv4 only",
587+
service: &corev1.Service{
588+
Spec: corev1.ServiceSpec{
589+
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
590+
},
591+
},
592+
expected: ptr.To(egv1a1.IPv4),
593+
},
594+
{
595+
name: "ipv6 only",
596+
service: &corev1.Service{
597+
Spec: corev1.ServiceSpec{
598+
IPFamilies: []corev1.IPFamily{corev1.IPv6Protocol},
599+
},
600+
},
601+
expected: ptr.To(egv1a1.IPv6),
602+
},
603+
{
604+
name: "no ip family specified",
605+
service: &corev1.Service{
606+
Spec: corev1.ServiceSpec{},
607+
},
608+
expected: nil,
609+
},
610+
}
611+
612+
for _, tc := range testCases {
613+
t.Run(tc.name, func(t *testing.T) {
614+
result := getServiceIPFamily(tc.service)
615+
require.Equal(t, tc.expected, result)
616+
})
617+
}
618+
}

internal/gatewayapi/listener.go

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
102102
}
103103

104104
address := net.IPv4ListenerAddress
105-
ipFamily := getIPFamily(gateway.envoyProxy)
106-
if ipFamily != nil && (*ipFamily == ir.IPv6 || *ipFamily == ir.DualStack) {
105+
ipFamily := getEnvoyIPFamily(gateway.envoyProxy)
106+
if ipFamily != nil && (*ipFamily == egv1a1.IPv6 || *ipFamily == egv1a1.DualStack) {
107107
address = net.IPv6ListenerAddress
108108
}
109109

@@ -118,17 +118,14 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
118118
Address: address,
119119
Port: uint32(containerPort),
120120
Metadata: buildListenerMetadata(listener, gateway),
121-
IPFamily: getIPFamily(gateway.envoyProxy),
121+
IPFamily: ipFamily,
122122
},
123123
TLS: irTLSConfigs(listener.tlsSecrets...),
124124
Path: ir.PathSettings{
125125
MergeSlashes: true,
126126
EscapedSlashesAction: ir.UnescapeAndRedirect,
127127
},
128128
}
129-
if ipFamily := getIPFamily(gateway.envoyProxy); ipFamily != nil {
130-
irListener.CoreListenerDetails.IPFamily = ipFamily
131-
}
132129
if listener.Hostname != nil {
133130
irListener.Hostnames = append(irListener.Hostnames, string(*listener.Hostname))
134131
} else {
@@ -144,7 +141,7 @@ func (t *Translator) ProcessListeners(gateways []*GatewayContext, xdsIR resource
144141
Name: irListenerName(listener),
145142
Address: address,
146143
Port: uint32(containerPort),
147-
IPFamily: getIPFamily(gateway.envoyProxy),
144+
IPFamily: ipFamily,
148145
},
149146

150147
// Gateway is processed firstly, then ClientTrafficPolicy, then xRoute.

internal/gatewayapi/route.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1232,6 +1232,7 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext,
12321232
addrType *ir.DestinationAddressType
12331233
)
12341234
protocol := inspectAppProtocolByRouteKind(routeType)
1235+
12351236
switch KindDerefOr(backendRef.Kind, resource.KindService) {
12361237
case resource.KindServiceImport:
12371238
serviceImport := resources.GetServiceImport(backendNamespace, string(backendRef.Name))
@@ -1262,9 +1263,9 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext,
12621263
Endpoints: endpoints,
12631264
AddressType: addrType,
12641265
}
1266+
12651267
case resource.KindService:
12661268
ds = t.processServiceDestinationSetting(backendRef.BackendObjectReference, backendNamespace, protocol, resources, envoyProxy)
1267-
12681269
ds.TLS = t.applyBackendTLSSetting(
12691270
backendRef.BackendObjectReference,
12701271
backendNamespace,
@@ -1280,6 +1281,7 @@ func (t *Translator) processDestination(backendRefContext BackendRefContext,
12801281
envoyProxy,
12811282
)
12821283
ds.Filters = t.processDestinationFilters(routeType, backendRefContext, parentRef, route, resources)
1284+
ds.IPFamily = getServiceIPFamily(resources.GetService(backendNamespace, string(backendRef.Name)))
12831285
case egv1a1.KindBackend:
12841286
ds = t.processBackendDestinationSetting(backendRef.BackendObjectReference, backendNamespace, resources)
12851287

internal/ir/xds.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -250,19 +250,12 @@ type CoreListenerDetails struct {
250250
ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"`
251251
// Metadata is used to enrich envoy resource metadata with user and provider-specific information
252252
Metadata *ResourceMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"`
253-
// IPFamily specifies the IP address family for the gateway.
254-
// It can be IPv4, IPv6, or DualStack.
253+
// IPFamily specifies the IP address family used by the Gateway for its listening ports.
255254
IPFamily *IPFamily `json:"ipFamily,omitempty" yaml:"ipFamily,omitempty"`
256255
}
257256

258257
// IPFamily specifies the IP address family used by the Gateway for its listening ports.
259-
type IPFamily string
260-
261-
const (
262-
IPv4 IPFamily = "IPv4"
263-
IPv6 IPFamily = "IPv6"
264-
DualStack IPFamily = "DualStack"
265-
)
258+
type IPFamily = egv1a1.IPFamily
266259

267260
func (l CoreListenerDetails) GetName() string {
268261
return l.Name
@@ -1308,9 +1301,11 @@ type DestinationSetting struct {
13081301
Endpoints []*DestinationEndpoint `json:"endpoints,omitempty" yaml:"endpoints,omitempty"`
13091302
// AddressTypeState specifies the state of DestinationEndpoint address type.
13101303
AddressType *DestinationAddressType `json:"addressType,omitempty" yaml:"addressType,omitempty"`
1311-
1312-
TLS *TLSUpstreamConfig `json:"tls,omitempty" yaml:"tls,omitempty"`
1313-
Filters *DestinationFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
1304+
// IPFamily specifies the IP family (IPv4 or IPv6) to use for this destination's endpoints.
1305+
// This is derived from the backend service and endpoint slice information.
1306+
IPFamily *IPFamily `json:"ipFamily,omitempty" yaml:"ipFamily,omitempty"`
1307+
TLS *TLSUpstreamConfig `json:"tls,omitempty" yaml:"tls,omitempty"`
1308+
Filters *DestinationFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
13141309
}
13151310

13161311
// Validate the fields within the RouteDestination structure
@@ -1686,6 +1681,7 @@ func (t TCPListener) Validate() error {
16861681

16871682
func (t TCPRoute) Validate() error {
16881683
var errs error
1684+
16891685
if t.Name == "" {
16901686
errs = errors.Join(errs, ErrRouteNameEmpty)
16911687
}

internal/ir/zz_generated.deepcopy.go

Lines changed: 6 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/xds/translator/cluster.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,7 @@ type ExtraArgs struct {
698698
metrics *ir.Metrics
699699
http1Settings *ir.HTTP1Settings
700700
http2Settings *ir.HTTP2Settings
701+
ipFamily *egv1a1.IPFamily
701702
}
702703

703704
type clusterArgs interface {
@@ -716,6 +717,7 @@ func (route *UDPRouteTranslator) asClusterArgs(extra *ExtraArgs) *xdsClusterArgs
716717
endpointType: buildEndpointType(route.Destination.Settings),
717718
metrics: extra.metrics,
718719
dns: route.DNS,
720+
ipFamily: extra.ipFamily,
719721
}
720722
}
721723

@@ -737,6 +739,7 @@ func (route *TCPRouteTranslator) asClusterArgs(extra *ExtraArgs) *xdsClusterArgs
737739
metrics: extra.metrics,
738740
backendConnection: route.BackendConnection,
739741
dns: route.DNS,
742+
ipFamily: extra.ipFamily,
740743
}
741744
}
742745

@@ -754,6 +757,7 @@ func (httpRoute *HTTPRouteTranslator) asClusterArgs(extra *ExtraArgs) *xdsCluste
754757
http1Settings: extra.http1Settings,
755758
http2Settings: extra.http2Settings,
756759
useClientProtocol: ptr.Deref(httpRoute.UseClientProtocol, false),
760+
ipFamily: extra.ipFamily,
757761
}
758762

759763
// Populate traffic features.

internal/xds/translator/listener.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func buildXdsTCPListener(
180180
},
181181
}
182182

183-
if ipFamily != nil && *ipFamily == ir.DualStack {
183+
if ipFamily != nil && *ipFamily == egv1a1.DualStack {
184184
socketAddress := listener.Address.GetSocketAddress()
185185
socketAddress.Ipv4Compat = true
186186
}
@@ -224,7 +224,7 @@ func buildXdsQuicListener(name, address string, port uint32, ipFamily *ir.IPFami
224224
DrainType: listenerv3.Listener_MODIFY_ONLY,
225225
}
226226

227-
if ipFamily != nil && *ipFamily == ir.DualStack {
227+
if ipFamily != nil && *ipFamily == egv1a1.DualStack {
228228
socketAddress := xdsListener.Address.GetSocketAddress()
229229
socketAddress.Ipv4Compat = true
230230
}
@@ -869,7 +869,7 @@ func buildXdsUDPListener(clusterName string, udpListener *ir.UDPListener, access
869869
}},
870870
}
871871

872-
if udpListener.IPFamily != nil && *udpListener.IPFamily == ir.DualStack {
872+
if udpListener.IPFamily != nil && *udpListener.IPFamily == egv1a1.DualStack {
873873
socketAddress := xdsListener.Address.GetSocketAddress()
874874
socketAddress.Ipv4Compat = true
875875
}

internal/xds/translator/translator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,7 @@ func (t *Translator) addRouteToRouteConfig(
464464
ea := &ExtraArgs{
465465
metrics: metrics,
466466
http1Settings: httpListener.HTTP1,
467+
ipFamily: determineIPFamily(httpRoute.Destination.Settings),
467468
}
468469

469470
if httpRoute.Traffic != nil && httpRoute.Traffic.HTTP2 != nil {

internal/xds/translator/utils.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,43 @@ func addClusterFromURL(url string, tCtx *types.ResourceVersionTable) error {
196196

197197
return addXdsCluster(tCtx, clusterArgs)
198198
}
199+
200+
// determineIPFamily determines the IP family based on multiple destination settings
201+
func determineIPFamily(settings []*ir.DestinationSetting) *egv1a1.IPFamily {
202+
// If there's only one setting, return its IPFamily directly
203+
if len(settings) == 1 {
204+
return settings[0].IPFamily
205+
}
206+
207+
hasIPv4 := false
208+
hasIPv6 := false
209+
hasDualStack := false
210+
211+
for _, setting := range settings {
212+
if setting.IPFamily == nil {
213+
continue
214+
}
215+
216+
switch *setting.IPFamily {
217+
case egv1a1.IPv4:
218+
hasIPv4 = true
219+
case egv1a1.IPv6:
220+
hasIPv6 = true
221+
case egv1a1.DualStack:
222+
hasDualStack = true
223+
}
224+
}
225+
226+
switch {
227+
case hasDualStack:
228+
return ptr.To(egv1a1.DualStack)
229+
case hasIPv4 && hasIPv6:
230+
return ptr.To(egv1a1.DualStack)
231+
case hasIPv4:
232+
return ptr.To(egv1a1.IPv4)
233+
case hasIPv6:
234+
return ptr.To(egv1a1.IPv6)
235+
default:
236+
return nil
237+
}
238+
}

0 commit comments

Comments
 (0)