k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/proxy/apis/config/validation/validation_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package validation
    18  
    19  import (
    20  	"runtime"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	componentbaseconfig "k8s.io/component-base/config"
    29  	logsapi "k8s.io/component-base/logs/api/v1"
    30  	kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
    31  	"k8s.io/utils/ptr"
    32  )
    33  
    34  func TestValidateKubeProxyConfiguration(t *testing.T) {
    35  	baseConfig := &kubeproxyconfig.KubeProxyConfiguration{
    36  		BindAddress:        "192.168.59.103",
    37  		HealthzBindAddress: "0.0.0.0:10256",
    38  		MetricsBindAddress: "127.0.0.1:10249",
    39  		ClusterCIDR:        "192.168.59.0/24",
    40  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
    41  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
    42  			MasqueradeAll: true,
    43  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
    44  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
    45  		},
    46  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
    47  			MaxPerCore:            ptr.To[int32](1),
    48  			Min:                   ptr.To[int32](1),
    49  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
    50  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
    51  		},
    52  		Logging: logsapi.LoggingConfiguration{
    53  			Format: "text",
    54  		},
    55  	}
    56  	newPath := field.NewPath("KubeProxyConfiguration")
    57  
    58  	for name, testCase := range map[string]struct {
    59  		mutateConfigFunc func(*kubeproxyconfig.KubeProxyConfiguration)
    60  		expectedErrs     field.ErrorList
    61  	}{
    62  		"basic config, unspecified Mode": {
    63  			mutateConfigFunc: func(_ *kubeproxyconfig.KubeProxyConfiguration) {},
    64  		},
    65  		"Mode specified, extra mode-specific configs": {
    66  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
    67  				if runtime.GOOS == "windows" {
    68  					config.Mode = kubeproxyconfig.ProxyModeKernelspace
    69  				} else {
    70  					config.Mode = kubeproxyconfig.ProxyModeIPVS
    71  					config.IPVS = kubeproxyconfig.KubeProxyIPVSConfiguration{
    72  						SyncPeriod:    metav1.Duration{Duration: 10 * time.Second},
    73  						MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
    74  					}
    75  				}
    76  			},
    77  		},
    78  		"empty HealthzBindAddress": {
    79  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
    80  				config.HealthzBindAddress = ""
    81  			},
    82  		},
    83  		"IPv6": {
    84  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
    85  				config.BindAddress = "fd00:192:168:59::103"
    86  				config.HealthzBindAddress = ""
    87  				config.MetricsBindAddress = "[::1]:10249"
    88  				config.ClusterCIDR = "fd00:192:168:59::/64"
    89  			},
    90  		},
    91  		"alternate healthz port": {
    92  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
    93  				config.HealthzBindAddress = "0.0.0.0:12345"
    94  			},
    95  		},
    96  		"ClusterCIDR is wrong IP family": {
    97  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
    98  				config.ClusterCIDR = "fd00:192:168::/64"
    99  			},
   100  		},
   101  		"ClusterCIDR is dual-stack": {
   102  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   103  				config.ClusterCIDR = "192.168.59.0/24,fd00:192:168::/64"
   104  			},
   105  		},
   106  		"LocalModeInterfaceNamePrefix": {
   107  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   108  				config.DetectLocalMode = kubeproxyconfig.LocalModeInterfaceNamePrefix
   109  				config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{
   110  					InterfaceNamePrefix: "vethabcde",
   111  				}
   112  			},
   113  		},
   114  		"LocalModeBridgeInterface": {
   115  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   116  				config.DetectLocalMode = kubeproxyconfig.LocalModeBridgeInterface
   117  				config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{
   118  					BridgeInterface: "avz",
   119  				}
   120  			},
   121  		},
   122  		"invalid BindAddress": {
   123  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   124  				config.BindAddress = "10.10.12.11:2000"
   125  			},
   126  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("BindAddress"), "10.10.12.11:2000", "not a valid textual representation of an IP address")},
   127  		},
   128  		"invalid HealthzBindAddress": {
   129  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   130  				config.HealthzBindAddress = "0.0.0.0"
   131  			},
   132  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0.0.0.0", "must be IP:port")},
   133  		},
   134  		"invalid MetricsBindAddress": {
   135  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   136  				config.MetricsBindAddress = "127.0.0.1"
   137  			},
   138  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("MetricsBindAddress"), "127.0.0.1", "must be IP:port")},
   139  		},
   140  		"ClusterCIDR missing subset range": {
   141  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   142  				config.ClusterCIDR = "192.168.59.0"
   143  			},
   144  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0", "must be a valid CIDR block (e.g. 10.100.0.0/16 or fde4:8dba:82e1::/48)")},
   145  		},
   146  		"Invalid number of ClusterCIDRs": {
   147  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   148  				config.ClusterCIDR = "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16"
   149  			},
   150  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ClusterCIDR"), "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16", "only one CIDR allowed or a valid DualStack CIDR (e.g. 10.100.0.0/16,fde4:8dba:82e1::/48)")},
   151  		},
   152  		"ConfigSyncPeriod must be > 0": {
   153  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   154  				config.ConfigSyncPeriod = metav1.Duration{Duration: -1 * time.Second}
   155  			},
   156  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ConfigSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than 0")},
   157  		},
   158  		"IPVS mode selected without providing required SyncPeriod": {
   159  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   160  				config.Mode = kubeproxyconfig.ProxyModeIPVS
   161  			},
   162  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeProxyIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0")},
   163  		},
   164  		"interfacePrefix is empty": {
   165  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   166  				config.DetectLocalMode = kubeproxyconfig.LocalModeInterfaceNamePrefix
   167  				config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{
   168  					InterfaceNamePrefix: "",
   169  				}
   170  			},
   171  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfacePrefix"), "", "must not be empty")},
   172  		},
   173  		"bridgeInterfaceName is empty": {
   174  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   175  				config.DetectLocalMode = kubeproxyconfig.LocalModeBridgeInterface
   176  				config.DetectLocal = kubeproxyconfig.DetectLocalConfiguration{
   177  					InterfaceNamePrefix: "eth0", // we won't care about prefix since mode is not prefix
   178  				}
   179  			},
   180  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfaceName"), "", "must not be empty")},
   181  		},
   182  		"invalid DetectLocalMode": {
   183  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   184  				config.DetectLocalMode = "Guess"
   185  			},
   186  			expectedErrs: field.ErrorList{field.NotSupported(newPath.Child("DetectLocalMode"), "Guess", []string{"ClusterCIDR", "NodeCIDR", "BridgeInterface", "InterfaceNamePrefix", ""})},
   187  		},
   188  		"invalid logging format": {
   189  			mutateConfigFunc: func(config *kubeproxyconfig.KubeProxyConfiguration) {
   190  				config.Logging = logsapi.LoggingConfiguration{
   191  					Format: "unsupported format",
   192  				}
   193  			},
   194  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("logging.format"), "unsupported format", "Unsupported log format")},
   195  		},
   196  	} {
   197  		t.Run(name, func(t *testing.T) {
   198  			config := baseConfig.DeepCopy()
   199  			testCase.mutateConfigFunc(config)
   200  			errs := Validate(config)
   201  			if len(testCase.expectedErrs) == 0 {
   202  				assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors")
   203  			} else {
   204  				assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   205  			}
   206  		})
   207  	}
   208  }
   209  
   210  func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) {
   211  	newPath := field.NewPath("KubeProxyConfiguration")
   212  
   213  	for name, testCase := range map[string]struct {
   214  		config       kubeproxyconfig.KubeProxyIPTablesConfiguration
   215  		expectedErrs field.ErrorList
   216  	}{
   217  		"valid iptables config": {
   218  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   219  				MasqueradeAll: true,
   220  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   221  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   222  			},
   223  			expectedErrs: field.ErrorList{},
   224  		},
   225  		"valid custom MasqueradeBit": {
   226  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   227  				MasqueradeBit: ptr.To[int32](5),
   228  				MasqueradeAll: true,
   229  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   230  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   231  			},
   232  			expectedErrs: field.ErrorList{},
   233  		},
   234  		"SyncPeriod must be > 0": {
   235  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   236  				MasqueradeAll: true,
   237  				SyncPeriod:    metav1.Duration{Duration: -5 * time.Second},
   238  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   239  			},
   240  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"),
   241  				field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")},
   242  		},
   243  		"MinSyncPeriod must be > 0": {
   244  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   245  				MasqueradeBit: ptr.To[int32](5),
   246  				MasqueradeAll: true,
   247  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   248  				MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
   249  			},
   250  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
   251  		},
   252  		"MasqueradeBit cannot be < 0": {
   253  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   254  				MasqueradeBit: ptr.To[int32](-10),
   255  				MasqueradeAll: true,
   256  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   257  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   258  			},
   259  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MasqueradeBit"), ptr.To[int32](-10), "must be within the range [0, 31]")},
   260  		},
   261  		"SyncPeriod must be >= MinSyncPeriod": {
   262  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   263  				MasqueradeBit: ptr.To[int32](5),
   264  				MasqueradeAll: true,
   265  				SyncPeriod:    metav1.Duration{Duration: 1 * time.Second},
   266  				MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
   267  			},
   268  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 5 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")},
   269  		},
   270  	} {
   271  		t.Run(name, func(t *testing.T) {
   272  			errs := validateKubeProxyIPTablesConfiguration(testCase.config, newPath.Child("KubeIPTablesConfiguration"))
   273  			assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   274  		})
   275  	}
   276  }
   277  
   278  func TestValidateKubeProxyIPVSConfiguration(t *testing.T) {
   279  	newPath := field.NewPath("KubeProxyConfiguration")
   280  	for name, testCase := range map[string]struct {
   281  		config       kubeproxyconfig.KubeProxyIPVSConfiguration
   282  		expectedErrs field.ErrorList
   283  	}{
   284  		"SyncPeriod is not greater than 0": {
   285  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   286  				SyncPeriod:    metav1.Duration{Duration: -5 * time.Second},
   287  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   288  			},
   289  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"),
   290  				field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
   291  		},
   292  		"SyncPeriod cannot be 0": {
   293  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   294  				SyncPeriod:    metav1.Duration{Duration: 0 * time.Second},
   295  				MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
   296  			},
   297  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0"),
   298  				field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 10 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
   299  		},
   300  		"MinSyncPeriod cannot be less than 0": {
   301  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   302  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   303  				MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
   304  			},
   305  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
   306  		},
   307  		"SyncPeriod must be greater than MinSyncPeriod": {
   308  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   309  				SyncPeriod:    metav1.Duration{Duration: 1 * time.Second},
   310  				MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
   311  			},
   312  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 5 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
   313  		},
   314  		"SyncPeriod == MinSyncPeriod": {
   315  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   316  				SyncPeriod:    metav1.Duration{Duration: 10 * time.Second},
   317  				MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
   318  			},
   319  			expectedErrs: field.ErrorList{},
   320  		},
   321  		"SyncPeriod should be > MinSyncPeriod": {
   322  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   323  				SyncPeriod:    metav1.Duration{Duration: 10 * time.Second},
   324  				MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
   325  			},
   326  			expectedErrs: field.ErrorList{},
   327  		},
   328  		"MinSyncPeriod can be 0": {
   329  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   330  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   331  				MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second},
   332  			},
   333  			expectedErrs: field.ErrorList{},
   334  		},
   335  		"IPVS Timeout can be 0": {
   336  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   337  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   338  				TCPTimeout:    metav1.Duration{Duration: 0 * time.Second},
   339  				TCPFinTimeout: metav1.Duration{Duration: 0 * time.Second},
   340  				UDPTimeout:    metav1.Duration{Duration: 0 * time.Second},
   341  			},
   342  			expectedErrs: field.ErrorList{},
   343  		},
   344  		"IPVS Timeout > 0": {
   345  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   346  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   347  				TCPTimeout:    metav1.Duration{Duration: 1 * time.Second},
   348  				TCPFinTimeout: metav1.Duration{Duration: 2 * time.Second},
   349  				UDPTimeout:    metav1.Duration{Duration: 3 * time.Second},
   350  			},
   351  			expectedErrs: field.ErrorList{},
   352  		},
   353  		"TCP,TCPFin,UDP Timeouts < 0": {
   354  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   355  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   356  				TCPTimeout:    metav1.Duration{Duration: -1 * time.Second},
   357  				UDPTimeout:    metav1.Duration{Duration: -1 * time.Second},
   358  				TCPFinTimeout: metav1.Duration{Duration: -1 * time.Second},
   359  			},
   360  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"),
   361  				field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPFinTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"),
   362  				field.Invalid(newPath.Child("KubeIPVSConfiguration.UDPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
   363  		},
   364  	} {
   365  		t.Run(name, func(t *testing.T) {
   366  			errs := validateKubeProxyIPVSConfiguration(testCase.config, newPath.Child("KubeIPVSConfiguration"))
   367  			assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   368  		})
   369  	}
   370  }
   371  
   372  func TestValidateKubeProxyConntrackConfiguration(t *testing.T) {
   373  	newPath := field.NewPath("KubeProxyConfiguration")
   374  	for name, testCase := range map[string]struct {
   375  		config       kubeproxyconfig.KubeProxyConntrackConfiguration
   376  		expectedErrs field.ErrorList
   377  	}{
   378  		"valid 5 second timeouts": {
   379  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   380  				MaxPerCore:            ptr.To[int32](1),
   381  				Min:                   ptr.To[int32](1),
   382  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   383  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   384  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   385  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   386  			},
   387  			expectedErrs: field.ErrorList{},
   388  		},
   389  		"valid duration equal to 0 second timeout": {
   390  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   391  				MaxPerCore:            ptr.To[int32](1),
   392  				Min:                   ptr.To[int32](1),
   393  				TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second},
   394  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 0 * time.Second},
   395  				UDPTimeout:            metav1.Duration{Duration: 0 * time.Second},
   396  				UDPStreamTimeout:      metav1.Duration{Duration: 0 * time.Second},
   397  			},
   398  			expectedErrs: field.ErrorList{},
   399  		},
   400  		"invalid MaxPerCore < 0": {
   401  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   402  				MaxPerCore:            ptr.To[int32](-1),
   403  				Min:                   ptr.To[int32](1),
   404  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   405  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   406  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   407  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   408  			},
   409  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.MaxPerCore"), ptr.To[int32](-1), "must be greater than or equal to 0")},
   410  		},
   411  		"invalid minimum < 0": {
   412  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   413  				MaxPerCore:            ptr.To[int32](1),
   414  				Min:                   ptr.To[int32](-1),
   415  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   416  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   417  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   418  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   419  			},
   420  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.Min"), ptr.To[int32](-1), "must be greater than or equal to 0")},
   421  		},
   422  		"invalid TCPEstablishedTimeout < 0": {
   423  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   424  				MaxPerCore:            ptr.To[int32](1),
   425  				Min:                   ptr.To[int32](1),
   426  				TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second},
   427  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   428  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   429  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   430  			},
   431  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPEstablishedTimeout"), &metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   432  		},
   433  		"invalid TCPCloseWaitTimeout < 0": {
   434  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   435  				MaxPerCore:            ptr.To[int32](1),
   436  				Min:                   ptr.To[int32](1),
   437  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   438  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: -5 * time.Second},
   439  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   440  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   441  			},
   442  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPCloseWaitTimeout"), &metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   443  		},
   444  		"invalid UDPTimeout < 0": {
   445  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   446  				MaxPerCore:            ptr.To[int32](1),
   447  				Min:                   ptr.To[int32](1),
   448  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   449  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   450  				UDPTimeout:            metav1.Duration{Duration: -5 * time.Second},
   451  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   452  			},
   453  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   454  		},
   455  		"invalid UDPStreamTimeout < 0": {
   456  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   457  				MaxPerCore:            ptr.To[int32](1),
   458  				Min:                   ptr.To[int32](1),
   459  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   460  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   461  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   462  				UDPStreamTimeout:      metav1.Duration{Duration: -5 * time.Second},
   463  			},
   464  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPStreamTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   465  		},
   466  	} {
   467  		t.Run(name, func(t *testing.T) {
   468  			errs := validateKubeProxyConntrackConfiguration(testCase.config, newPath.Child("KubeConntrackConfiguration"))
   469  			assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   470  		})
   471  	}
   472  }
   473  
   474  func TestValidateProxyMode(t *testing.T) {
   475  	if runtime.GOOS == "windows" {
   476  		testValidateProxyModeWindows(t)
   477  	} else {
   478  		testValidateProxyModeLinux(t)
   479  	}
   480  }
   481  
   482  func testValidateProxyModeLinux(t *testing.T) {
   483  	newPath := field.NewPath("KubeProxyConfiguration")
   484  	for name, testCase := range map[string]struct {
   485  		mode         kubeproxyconfig.ProxyMode
   486  		expectedErrs field.ErrorList
   487  	}{
   488  		"blank mode should default": {
   489  			mode: kubeproxyconfig.ProxyMode(""),
   490  		},
   491  		"iptables is allowed": {
   492  			mode: kubeproxyconfig.ProxyModeIPTables,
   493  		},
   494  		"ipvs is allowed": {
   495  			mode: kubeproxyconfig.ProxyModeIPVS,
   496  		},
   497  		"nftables is allowed": {
   498  			mode: kubeproxyconfig.ProxyModeNFTables,
   499  		},
   500  		"winkernel is not allowed": {
   501  			mode:         kubeproxyconfig.ProxyModeKernelspace,
   502  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "kernelspace", "must be iptables, ipvs, nftables or blank (blank means the best-available proxy [currently iptables])")},
   503  		},
   504  		"invalid mode non-existent": {
   505  			mode:         kubeproxyconfig.ProxyMode("non-existing"),
   506  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", "must be iptables, ipvs, nftables or blank (blank means the best-available proxy [currently iptables])")},
   507  		},
   508  	} {
   509  		t.Run(name, func(t *testing.T) {
   510  			errs := validateProxyMode(testCase.mode, newPath)
   511  			assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   512  		})
   513  	}
   514  }
   515  
   516  func testValidateProxyModeWindows(t *testing.T) {
   517  	// TODO: remove skip once the test has been fixed.
   518  	if runtime.GOOS == "windows" {
   519  		t.Skip("Skipping failing test on Windows.")
   520  	}
   521  	newPath := field.NewPath("KubeProxyConfiguration")
   522  	for name, testCase := range map[string]struct {
   523  		mode         kubeproxyconfig.ProxyMode
   524  		expectedErrs field.ErrorList
   525  	}{
   526  		"blank mode should default": {
   527  			mode: kubeproxyconfig.ProxyMode(""),
   528  		},
   529  		"winkernel is allowed": {
   530  			mode: kubeproxyconfig.ProxyModeKernelspace,
   531  		},
   532  		"iptables is not allowed": {
   533  			mode:         kubeproxyconfig.ProxyModeIPTables,
   534  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "iptables", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")},
   535  		},
   536  		"ipvs is not allowed": {
   537  			mode:         kubeproxyconfig.ProxyModeIPVS,
   538  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "ipvs", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")},
   539  		},
   540  		"nftables is not allowed": {
   541  			mode:         kubeproxyconfig.ProxyModeNFTables,
   542  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "nftables", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")},
   543  		},
   544  		"invalid mode non-existent": {
   545  			mode:         kubeproxyconfig.ProxyMode("non-existing"),
   546  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])")},
   547  		},
   548  	} {
   549  		t.Run(name, func(t *testing.T) {
   550  			errs := validateProxyMode(testCase.mode, newPath)
   551  			assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   552  		})
   553  	}
   554  }
   555  
   556  func TestValidateClientConnectionConfiguration(t *testing.T) {
   557  	newPath := field.NewPath("KubeProxyConfiguration")
   558  	for name, testCase := range map[string]struct {
   559  		ccc          componentbaseconfig.ClientConnectionConfiguration
   560  		expectedErrs field.ErrorList
   561  	}{
   562  		"successful 0 value": {
   563  			ccc:          componentbaseconfig.ClientConnectionConfiguration{Burst: 0},
   564  			expectedErrs: field.ErrorList{},
   565  		},
   566  		"successful 5 value": {
   567  			ccc:          componentbaseconfig.ClientConnectionConfiguration{Burst: 5},
   568  			expectedErrs: field.ErrorList{},
   569  		},
   570  		"burst < 0": {
   571  			ccc:          componentbaseconfig.ClientConnectionConfiguration{Burst: -5},
   572  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("Burst"), int64(-5), "must be greater than or equal to 0")},
   573  		},
   574  	} {
   575  		t.Run(name, func(t *testing.T) {
   576  			errs := validateClientConnectionConfiguration(testCase.ccc, newPath)
   577  			assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   578  		})
   579  	}
   580  }
   581  
   582  func TestValidateHostPort(t *testing.T) {
   583  	newPath := field.NewPath("KubeProxyConfiguration")
   584  	for name, testCase := range map[string]struct {
   585  		ip           string
   586  		expectedErrs field.ErrorList
   587  	}{
   588  		"all IPs": {
   589  			ip: "0.0.0.0:10256",
   590  		},
   591  		"localhost": {
   592  			ip: "127.0.0.1:10256",
   593  		},
   594  		"specific IP": {
   595  			ip: "10.10.10.10:10256",
   596  		},
   597  		"missing port": {
   598  			ip:           "10.10.10.10",
   599  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "10.10.10.10", "must be IP:port")},
   600  		},
   601  		"digits outside of 1-255": {
   602  			ip:           "123.456.789.10:12345",
   603  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "123.456.789.10", "must be a valid IP")},
   604  		},
   605  		"invalid named-port": {
   606  			ip:           "10.10.10.10:foo",
   607  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "foo", "must be a valid port")},
   608  		},
   609  		"port cannot be 0": {
   610  			ip:           "10.10.10.10:0",
   611  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0", "must be a valid port")},
   612  		},
   613  		"port is greater than allowed range": {
   614  			ip:           "10.10.10.10:65536",
   615  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "65536", "must be a valid port")},
   616  		},
   617  	} {
   618  		t.Run(name, func(t *testing.T) {
   619  			errs := validateHostPort(testCase.ip, newPath.Child("HealthzBindAddress"))
   620  			if len(testCase.expectedErrs) == 0 {
   621  				assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors")
   622  			} else {
   623  				assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   624  			}
   625  		})
   626  	}
   627  }
   628  
   629  func TestValidateKubeProxyNodePortAddress(t *testing.T) {
   630  	newPath := field.NewPath("KubeProxyConfiguration")
   631  	for name, testCase := range map[string]struct {
   632  		addresses    []string
   633  		expectedErrs field.ErrorList
   634  	}{
   635  		"no addresses": {
   636  			addresses: []string{},
   637  		},
   638  		"valid 1": {
   639  			addresses: []string{"127.0.0.0/8"},
   640  		},
   641  		"valid 2": {
   642  			addresses: []string{"0.0.0.0/0"},
   643  		},
   644  		"valid 3": {
   645  			addresses: []string{"::/0"},
   646  		},
   647  		"valid 4": {
   648  			addresses: []string{"127.0.0.1/32", "1.2.3.0/24"},
   649  		},
   650  		"valid 5": {
   651  			addresses: []string{"127.0.0.1/32"},
   652  		},
   653  		"valid 6": {
   654  			addresses: []string{"::1/128"},
   655  		},
   656  		"valid 7": {
   657  			addresses: []string{"1.2.3.4/32"},
   658  		},
   659  		"valid 8": {
   660  			addresses: []string{"10.20.30.0/24"},
   661  		},
   662  		"valid 9": {
   663  			addresses: []string{"10.20.0.0/16", "100.200.0.0/16"},
   664  		},
   665  		"valid 10": {
   666  			addresses: []string{"10.0.0.0/8"},
   667  		},
   668  		"valid 11": {
   669  			addresses: []string{"2001:db8::/32"},
   670  		},
   671  		"primary": {
   672  			addresses: []string{kubeproxyconfig.NodePortAddressesPrimary},
   673  		},
   674  		"invalid foo address": {
   675  			addresses:    []string{"foo"},
   676  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "foo", "must be a valid CIDR")},
   677  		},
   678  		"invalid octet address": {
   679  			addresses:    []string{"10.0.0.0/0", "1.2.3"},
   680  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "1.2.3", "must be a valid CIDR")},
   681  		},
   682  		"address cannot be 0": {
   683  			addresses:    []string{"127.0.0.1/32", "0", "1.2.3.0/24"},
   684  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "0", "must be a valid CIDR")},
   685  		},
   686  		"address missing subnet range": {
   687  			addresses:    []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"},
   688  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "10.20.30.40", "must be a valid CIDR")},
   689  		},
   690  		"missing ipv6 subnet ranges": {
   691  			addresses: []string{"::0", "::1", "2001:db8::/32"},
   692  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "::0", "must be a valid CIDR"),
   693  				field.Invalid(newPath.Child("NodePortAddresses[1]"), "::1", "must be a valid CIDR")},
   694  		},
   695  		"invalid ipv6 ip format": {
   696  			addresses:    []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"},
   697  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[2]"), "2001:db8:xyz/64", "must be a valid CIDR")},
   698  		},
   699  		"invalid primary/CIDR mix 1": {
   700  			addresses:    []string{"primary", "127.0.0.1/32"},
   701  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "primary", "can't use both 'primary' and CIDRs")},
   702  		},
   703  		"invalid primary/CIDR mix 2": {
   704  			addresses:    []string{"127.0.0.1/32", "primary"},
   705  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "primary", "can't use both 'primary' and CIDRs")},
   706  		},
   707  	} {
   708  		t.Run(name, func(t *testing.T) {
   709  			errs := validateKubeProxyNodePortAddress(testCase.addresses, newPath.Child("NodePortAddresses"))
   710  			if len(testCase.expectedErrs) == 0 {
   711  				assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors")
   712  			} else {
   713  				assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   714  			}
   715  		})
   716  	}
   717  }
   718  
   719  func TestValidateKubeProxyExcludeCIDRs(t *testing.T) {
   720  	newPath := field.NewPath("KubeProxyConfiguration")
   721  	for name, testCase := range map[string]struct {
   722  		addresses    []string
   723  		expectedErrs field.ErrorList
   724  	}{
   725  		"no cidrs": {
   726  			addresses: []string{},
   727  		},
   728  		"valid 1": {
   729  			addresses: []string{"127.0.0.0/8"},
   730  		},
   731  		"valid 2": {
   732  			addresses: []string{"0.0.0.0/0"},
   733  		},
   734  		"valid 3": {
   735  			addresses: []string{"::/0"},
   736  		},
   737  		"valid 4": {
   738  			addresses: []string{"127.0.0.1/32", "1.2.3.0/24"},
   739  		},
   740  		"valid 5": {
   741  			addresses: []string{"127.0.0.1/32"},
   742  		},
   743  		"valid 6": {
   744  			addresses: []string{"::1/128"},
   745  		},
   746  		"valid 7": {
   747  			addresses: []string{"1.2.3.4/32"},
   748  		},
   749  		"valid 8": {
   750  			addresses: []string{"10.20.30.0/24"},
   751  		},
   752  		"valid 9": {
   753  			addresses: []string{"10.20.0.0/16", "100.200.0.0/16"},
   754  		},
   755  		"valid 10": {
   756  			addresses: []string{"10.0.0.0/8"},
   757  		},
   758  		"valid 11": {
   759  			addresses: []string{"2001:db8::/32"},
   760  		},
   761  		"invalid foo address": {
   762  			addresses:    []string{"foo"},
   763  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "foo", "must be a valid CIDR")},
   764  		},
   765  		"invalid octet address": {
   766  			addresses:    []string{"10.0.0.0/0", "1.2.3"},
   767  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "1.2.3", "must be a valid CIDR")},
   768  		},
   769  		"address cannot be 0": {
   770  			addresses:    []string{"127.0.0.1/32", "0", "1.2.3.0/24"},
   771  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "0", "must be a valid CIDR")},
   772  		},
   773  		"address missing subnet range": {
   774  			addresses:    []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"},
   775  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "10.20.30.40", "must be a valid CIDR")},
   776  		},
   777  		"missing ipv6 subnet ranges": {
   778  			addresses: []string{"::0", "::1", "2001:db8::/32"},
   779  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "::0", "must be a valid CIDR"),
   780  				field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "::1", "must be a valid CIDR")},
   781  		},
   782  		"invalid ipv6 ip format": {
   783  			addresses:    []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"},
   784  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[2]"), "2001:db8:xyz/64", "must be a valid CIDR")},
   785  		},
   786  	} {
   787  		t.Run(name, func(t *testing.T) {
   788  			errs := validateIPVSExcludeCIDRs(testCase.addresses, newPath.Child("ExcludeCIDRS"))
   789  			if len(testCase.expectedErrs) == 0 {
   790  				assert.Equal(t, field.ErrorList{}, errs, "expected no validation errors")
   791  			} else {
   792  				assert.Equal(t, testCase.expectedErrs, errs, "did not get expected validation errors")
   793  			}
   794  		})
   795  	}
   796  }