From 508b66cd3270440e57d158df70d2233fcf5589a1 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Thu, 23 Jan 2025 20:02:04 -0800 Subject: [PATCH 1/2] OCPBUGS-48089: validate hostPrefix to be the same when multiple clusternetwork CIDRs are present When multiple clusternetwork CIDRs are present, hostPrefix fields must be specified with the same value. If not, it can impact traffic between pods in different subnets. The patch applies validation for IPv4 CIDR. For IPv6, the only option for hostPrefix is 64, thus naturally satisfying the requirement. References: [0] https://issues.redhat.com/browse/OCPBUGS-46514 (debug steps and explanation for root cause) [1] https://access.redhat.com/solutions/7100460 (temporary solution for existing cluster) --- pkg/types/validation/installconfig.go | 17 +++++++++--- pkg/types/validation/installconfig_test.go | 31 ++++++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index 674c1891b9..e1f7279058 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -502,13 +502,22 @@ func validateClusterNetwork(n *types.Networking, cn *types.ClusterNetworkEntry, allErrs = append(allErrs, field.Invalid(fldPath.Child("cidr"), cn.CIDR.String(), fmt.Sprintf("cluster network must not overlap with cluster network %d", i))) } } - if cn.HostPrefix < 0 { - allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPrefix"), cn.HostPrefix, "hostPrefix must be positive")) - } + // ignore hostPrefix if the plugin does not use it and has it unset if pluginsUsingHostPrefix.Has(n.NetworkType) || (cn.HostPrefix != 0) { - if ones, bits := cn.CIDR.Mask.Size(); cn.HostPrefix < int32(ones) { + if cn.HostPrefix < 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPrefix"), cn.HostPrefix, "hostPrefix must be positive")) + } else if ones, bits := cn.CIDR.Mask.Size(); cn.HostPrefix < int32(ones) { allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPrefix"), cn.HostPrefix, "cluster network host subnetwork prefix must not be larger size than CIDR "+cn.CIDR.String())) + } else if bits == 32 { + // setting different value for clusternetwork CIDR host prefix is not allowed + // we only need to check IPv4 as IPv6 prefix must be 64 + for _, acn := range n.ClusterNetwork[0:idx] { + if acn.CIDR.IP.To4() != nil && cn.HostPrefix != acn.HostPrefix { + allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPrefix"), cn.HostPrefix, "cluster network host subnetwork prefix must be the same value for IPv4 networks")) + break + } + } } else if bits == 128 && cn.HostPrefix != 64 { allErrs = append(allErrs, field.Invalid(fldPath.Child("hostPrefix"), cn.HostPrefix, "cluster network host subnetwork prefix must be 64 for IPv6 networks")) } diff --git a/pkg/types/validation/installconfig_test.go b/pkg/types/validation/installconfig_test.go index 276c4b6e79..13893667b3 100644 --- a/pkg/types/validation/installconfig_test.go +++ b/pkg/types/validation/installconfig_test.go @@ -516,8 +516,8 @@ func TestValidateInstallConfig(t *testing.T) { installConfig: func() *types.InstallConfig { c := validInstallConfig() c.Networking.ClusterNetwork = []types.ClusterNetworkEntry{ - {CIDR: *ipnet.MustParseCIDR("12.0.0.0/16"), HostPrefix: 23}, - {CIDR: *ipnet.MustParseCIDR("12.0.3.0/24"), HostPrefix: 25}, + {CIDR: *ipnet.MustParseCIDR("12.0.0.0/16"), HostPrefix: 28}, + {CIDR: *ipnet.MustParseCIDR("12.0.3.0/24"), HostPrefix: 28}, } return c }(), @@ -554,6 +554,33 @@ func TestValidateInstallConfig(t *testing.T) { }(), expectedError: ``, }, + { + name: "cluster network host prefix negative", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.Networking.ClusterNetwork[0].HostPrefix = -23 + return c + }(), + expectedError: `^networking\.clusterNetwork\[0]\.hostPrefix: Invalid value: -23: hostPrefix must be positive$`, + }, + { + name: "multiple cluster network host prefix different", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.Networking.ClusterNetwork = append(c.Networking.ClusterNetwork, + types.ClusterNetworkEntry{ + CIDR: *ipnet.MustParseCIDR("192.168.2.0/24"), + HostPrefix: 30, + }, + types.ClusterNetworkEntry{ + CIDR: *ipnet.MustParseCIDR("ffd2::/48"), + HostPrefix: 64, + }, + ) + return c + }(), + expectedError: `^networking\.clusterNetwork\[1]\.hostPrefix: Invalid value: 30: cluster network host subnetwork prefix must be the same value for IPv4 networks$`, + }, { name: "networking clusterNetworkMTU - valid high limit ovn", installConfig: func() *types.InstallConfig { From 4b12df938b0fb4132b615a6e779a86ec591059f9 Mon Sep 17 00:00:00 2001 From: Thuan Vo Date: Thu, 23 Jan 2025 20:03:32 -0800 Subject: [PATCH 2/2] docs: mention the restriction for hostPrefix in InstallConfig CRD --- data/data/install.openshift.io_installconfigs.yaml | 4 ++++ docs/user/customization.md | 2 +- pkg/types/installconfig.go | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index f4aaf9eba5..63315f910c 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -2378,6 +2378,8 @@ spec: HostPrefix is the prefix size to allocate to each node from the CIDR. For example, 24 would allocate 2^8=256 adresses to each node. If this field is not used by the plugin, it can be left unset. + When multiple CIDRs of the same family (i.e. IPv4/IPv6) are present, + their HostPrefix value must be the same. format: int32 type: integer hostSubnetLength: @@ -2413,6 +2415,8 @@ spec: HostPrefix is the prefix size to allocate to each node from the CIDR. For example, 24 would allocate 2^8=256 adresses to each node. If this field is not used by the plugin, it can be left unset. + When multiple CIDRs of the same family (i.e. IPv4/IPv6) are present, + their HostPrefix value must be the same. format: int32 type: integer hostSubnetLength: diff --git a/docs/user/customization.md b/docs/user/customization.md index 8d7ca6570b..c2971d3ef0 100644 --- a/docs/user/customization.md +++ b/docs/user/customization.md @@ -45,7 +45,7 @@ feature set. The default is 10.128.0.0/14 with a host prefix of /23. * `cidr` (required [IP network](#ip-networks)): The IP block address pool. * `hostPrefix` (required integer): The prefix size to allocate to each node from the CIDR. - For example, 24 would allocate 2^8=256 addresses to each node. If this field is not used by the plugin, it can be left unset. + For example, 24 would allocate 2^8=256 addresses to each node. If this field is not used by the plugin, it can be left unset. When multiple CIDRs of the same family (i.e. IPv4/IPv6) are present, their `hostPrefix` value must be the same. * `machineNetwork` (optional array of objects): The IP address pools for machines. * `cidr` (required [IP network](#ip-networks)): The IP block address pool. The default is 10.0.0.0/16 for all platforms other than libvirt. diff --git a/pkg/types/installconfig.go b/pkg/types/installconfig.go index 6f743b2a76..2291c42bfc 100644 --- a/pkg/types/installconfig.go +++ b/pkg/types/installconfig.go @@ -441,6 +441,8 @@ type ClusterNetworkEntry struct { // HostPrefix is the prefix size to allocate to each node from the CIDR. // For example, 24 would allocate 2^8=256 adresses to each node. If this // field is not used by the plugin, it can be left unset. + // When multiple CIDRs of the same family (i.e. IPv4/IPv6) are present, + // their HostPrefix value must be the same. // +optional HostPrefix int32 `json:"hostPrefix,omitempty"`