k8s.io/kubernetes@v1.29.3/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  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	componentbaseconfig "k8s.io/component-base/config"
    27  	logsapi "k8s.io/component-base/logs/api/v1"
    28  	kubeproxyconfig "k8s.io/kubernetes/pkg/proxy/apis/config"
    29  
    30  	"k8s.io/utils/ptr"
    31  )
    32  
    33  func TestValidateKubeProxyConfiguration(t *testing.T) {
    34  	var proxyMode kubeproxyconfig.ProxyMode
    35  	if runtime.GOOS == "windows" {
    36  		proxyMode = kubeproxyconfig.ProxyModeKernelspace
    37  	} else {
    38  		proxyMode = kubeproxyconfig.ProxyModeIPVS
    39  	}
    40  	successCases := []kubeproxyconfig.KubeProxyConfiguration{{
    41  		BindAddress:        "192.168.59.103",
    42  		HealthzBindAddress: "0.0.0.0:10256",
    43  		MetricsBindAddress: "127.0.0.1:10249",
    44  		ClusterCIDR:        "192.168.59.0/24",
    45  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
    46  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
    47  			MasqueradeAll: true,
    48  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
    49  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
    50  		},
    51  		Mode: proxyMode,
    52  		IPVS: kubeproxyconfig.KubeProxyIPVSConfiguration{
    53  			SyncPeriod:    metav1.Duration{Duration: 10 * time.Second},
    54  			MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
    55  		},
    56  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
    57  			MaxPerCore:            ptr.To[int32](1),
    58  			Min:                   ptr.To[int32](1),
    59  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
    60  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
    61  		},
    62  		Logging: logsapi.LoggingConfiguration{
    63  			Format: "text",
    64  		},
    65  	}, {
    66  		BindAddress:        "192.168.59.103",
    67  		HealthzBindAddress: "0.0.0.0:10256",
    68  		MetricsBindAddress: "127.0.0.1:10249",
    69  		ClusterCIDR:        "192.168.59.0/24",
    70  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
    71  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
    72  			MasqueradeAll: true,
    73  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
    74  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
    75  		},
    76  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
    77  			MaxPerCore:            ptr.To[int32](1),
    78  			Min:                   ptr.To[int32](1),
    79  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
    80  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
    81  		},
    82  		Logging: logsapi.LoggingConfiguration{
    83  			Format: "text",
    84  		},
    85  	}, {
    86  		BindAddress:        "192.168.59.103",
    87  		HealthzBindAddress: "",
    88  		MetricsBindAddress: "127.0.0.1:10249",
    89  		ClusterCIDR:        "192.168.59.0/24",
    90  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
    91  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
    92  			MasqueradeAll: true,
    93  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
    94  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
    95  		},
    96  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
    97  			MaxPerCore:            ptr.To[int32](1),
    98  			Min:                   ptr.To[int32](1),
    99  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   100  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   101  		},
   102  		Logging: logsapi.LoggingConfiguration{
   103  			Format: "text",
   104  		},
   105  	}, {
   106  		BindAddress:        "fd00:192:168:59::103",
   107  		HealthzBindAddress: "",
   108  		MetricsBindAddress: "[::1]:10249",
   109  		ClusterCIDR:        "fd00:192:168:59::/64",
   110  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   111  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   112  			MasqueradeAll: true,
   113  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   114  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   115  		},
   116  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   117  			MaxPerCore:            ptr.To[int32](1),
   118  			Min:                   ptr.To[int32](1),
   119  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   120  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   121  		},
   122  		Logging: logsapi.LoggingConfiguration{
   123  			Format: "text",
   124  		},
   125  	}, {
   126  		BindAddress:        "10.10.12.11",
   127  		HealthzBindAddress: "0.0.0.0:12345",
   128  		MetricsBindAddress: "127.0.0.1:10249",
   129  		ClusterCIDR:        "192.168.59.0/24",
   130  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   131  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   132  			MasqueradeAll: true,
   133  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   134  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   135  		},
   136  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   137  			MaxPerCore:            ptr.To[int32](1),
   138  			Min:                   ptr.To[int32](1),
   139  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   140  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   141  		},
   142  		Logging: logsapi.LoggingConfiguration{
   143  			Format: "text",
   144  		},
   145  	}, {
   146  		BindAddress:        "10.10.12.11",
   147  		HealthzBindAddress: "0.0.0.0:12345",
   148  		MetricsBindAddress: "127.0.0.1:10249",
   149  		ClusterCIDR:        "fd00:192:168::/64",
   150  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   151  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   152  			MasqueradeAll: true,
   153  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   154  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   155  		},
   156  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   157  			MaxPerCore:            ptr.To[int32](1),
   158  			Min:                   ptr.To[int32](1),
   159  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   160  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   161  		},
   162  		Logging: logsapi.LoggingConfiguration{
   163  			Format: "text",
   164  		},
   165  	}, {
   166  		BindAddress:        "10.10.12.11",
   167  		HealthzBindAddress: "0.0.0.0:12345",
   168  		MetricsBindAddress: "127.0.0.1:10249",
   169  		ClusterCIDR:        "192.168.59.0/24,fd00:192:168::/64",
   170  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   171  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   172  			MasqueradeAll: true,
   173  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   174  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   175  		},
   176  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   177  			MaxPerCore:            ptr.To[int32](1),
   178  			Min:                   ptr.To[int32](1),
   179  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   180  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   181  		},
   182  		Logging: logsapi.LoggingConfiguration{
   183  			Format: "text",
   184  		},
   185  	}, {
   186  		BindAddress:        "10.10.12.11",
   187  		HealthzBindAddress: "0.0.0.0:12345",
   188  		MetricsBindAddress: "127.0.0.1:10249",
   189  		ClusterCIDR:        "192.168.59.0/24",
   190  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   191  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   192  			MasqueradeAll: true,
   193  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   194  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   195  		},
   196  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   197  			MaxPerCore:            ptr.To[int32](1),
   198  			Min:                   ptr.To[int32](1),
   199  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   200  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   201  		},
   202  		DetectLocalMode: kubeproxyconfig.LocalModeInterfaceNamePrefix,
   203  		DetectLocal: kubeproxyconfig.DetectLocalConfiguration{
   204  			InterfaceNamePrefix: "vethabcde",
   205  		},
   206  		Logging: logsapi.LoggingConfiguration{
   207  			Format: "text",
   208  		},
   209  	}, {
   210  		BindAddress:        "10.10.12.11",
   211  		HealthzBindAddress: "0.0.0.0:12345",
   212  		MetricsBindAddress: "127.0.0.1:10249",
   213  		ClusterCIDR:        "192.168.59.0/24",
   214  		ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   215  		IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   216  			MasqueradeAll: true,
   217  			SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   218  			MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   219  		},
   220  		Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   221  			MaxPerCore:            ptr.To[int32](1),
   222  			Min:                   ptr.To[int32](1),
   223  			TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   224  			TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   225  		},
   226  		DetectLocalMode: kubeproxyconfig.LocalModeBridgeInterface,
   227  		DetectLocal: kubeproxyconfig.DetectLocalConfiguration{
   228  			BridgeInterface: "avz",
   229  		},
   230  		Logging: logsapi.LoggingConfiguration{
   231  			Format: "text",
   232  		},
   233  	}}
   234  
   235  	for _, successCase := range successCases {
   236  		if errs := Validate(&successCase); len(errs) != 0 {
   237  			t.Errorf("expected success: %v", errs)
   238  		}
   239  	}
   240  
   241  	newPath := field.NewPath("KubeProxyConfiguration")
   242  	testCases := map[string]struct {
   243  		config       kubeproxyconfig.KubeProxyConfiguration
   244  		expectedErrs field.ErrorList
   245  	}{
   246  		"invalid BindAddress": {
   247  			config: kubeproxyconfig.KubeProxyConfiguration{
   248  				BindAddress:        "10.10.12.11:2000",
   249  				HealthzBindAddress: "0.0.0.0:10256",
   250  				MetricsBindAddress: "127.0.0.1:10249",
   251  				ClusterCIDR:        "192.168.59.0/24",
   252  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   253  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   254  					MasqueradeAll: true,
   255  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   256  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   257  				},
   258  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   259  					MaxPerCore:            ptr.To[int32](1),
   260  					Min:                   ptr.To[int32](1),
   261  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   262  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   263  				},
   264  				Logging: logsapi.LoggingConfiguration{
   265  					Format: "text",
   266  				},
   267  			},
   268  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("BindAddress"), "10.10.12.11:2000", "not a valid textual representation of an IP address")},
   269  		},
   270  		"invalid HealthzBindAddress": {
   271  			config: kubeproxyconfig.KubeProxyConfiguration{
   272  				BindAddress:        "10.10.12.11",
   273  				HealthzBindAddress: "0.0.0.0",
   274  				MetricsBindAddress: "127.0.0.1:10249",
   275  				ClusterCIDR:        "192.168.59.0/24",
   276  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   277  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   278  					MasqueradeAll: true,
   279  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   280  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   281  				},
   282  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   283  					MaxPerCore:            ptr.To[int32](1),
   284  					Min:                   ptr.To[int32](1),
   285  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   286  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   287  				},
   288  				Logging: logsapi.LoggingConfiguration{
   289  					Format: "text",
   290  				},
   291  			},
   292  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0.0.0.0", "must be IP:port")},
   293  		},
   294  		"invalid MetricsBindAddress": {
   295  			config: kubeproxyconfig.KubeProxyConfiguration{
   296  				BindAddress:        "10.10.12.11",
   297  				HealthzBindAddress: "0.0.0.0:12345",
   298  				MetricsBindAddress: "127.0.0.1",
   299  				ClusterCIDR:        "192.168.59.0/24",
   300  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   301  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   302  					MasqueradeAll: true,
   303  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   304  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   305  				},
   306  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   307  					MaxPerCore:            ptr.To[int32](1),
   308  					Min:                   ptr.To[int32](1),
   309  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   310  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   311  				},
   312  				Logging: logsapi.LoggingConfiguration{
   313  					Format: "text",
   314  				},
   315  			},
   316  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("MetricsBindAddress"), "127.0.0.1", "must be IP:port")},
   317  		},
   318  		"ClusterCIDR missing subset range": {
   319  			config: kubeproxyconfig.KubeProxyConfiguration{
   320  				BindAddress:        "10.10.12.11",
   321  				HealthzBindAddress: "0.0.0.0:12345",
   322  				MetricsBindAddress: "127.0.0.1:10249",
   323  				ClusterCIDR:        "192.168.59.0",
   324  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   325  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   326  					MasqueradeAll: true,
   327  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   328  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   329  				},
   330  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   331  					MaxPerCore:            ptr.To[int32](1),
   332  					Min:                   ptr.To[int32](1),
   333  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   334  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   335  				},
   336  				Logging: logsapi.LoggingConfiguration{
   337  					Format: "text",
   338  				},
   339  			},
   340  			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)")},
   341  		},
   342  		"Invalid number of ClusterCIDRs": {
   343  			config: kubeproxyconfig.KubeProxyConfiguration{
   344  				BindAddress:        "10.10.12.11",
   345  				HealthzBindAddress: "0.0.0.0:12345",
   346  				MetricsBindAddress: "127.0.0.1:10249",
   347  				ClusterCIDR:        "192.168.59.0/24,fd00:192:168::/64,10.0.0.0/16",
   348  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   349  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   350  					MasqueradeAll: true,
   351  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   352  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   353  				},
   354  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   355  					MaxPerCore:            ptr.To[int32](1),
   356  					Min:                   ptr.To[int32](1),
   357  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   358  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   359  				},
   360  				Logging: logsapi.LoggingConfiguration{
   361  					Format: "text",
   362  				},
   363  			},
   364  			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)")},
   365  		},
   366  		"ConfigSyncPeriod must be > 0": {
   367  			config: kubeproxyconfig.KubeProxyConfiguration{
   368  				BindAddress:        "10.10.12.11",
   369  				HealthzBindAddress: "0.0.0.0:12345",
   370  				MetricsBindAddress: "127.0.0.1:10249",
   371  				ClusterCIDR:        "192.168.59.0/24",
   372  				ConfigSyncPeriod:   metav1.Duration{Duration: -1 * time.Second},
   373  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   374  					MasqueradeAll: true,
   375  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   376  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   377  				},
   378  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   379  					MaxPerCore:            ptr.To[int32](1),
   380  					Min:                   ptr.To[int32](1),
   381  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   382  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   383  				},
   384  				Logging: logsapi.LoggingConfiguration{
   385  					Format: "text",
   386  				},
   387  			},
   388  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ConfigSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than 0")},
   389  		},
   390  		"IPVS mode selected without providing required SyncPeriod": {
   391  			config: kubeproxyconfig.KubeProxyConfiguration{
   392  				BindAddress:        "192.168.59.103",
   393  				HealthzBindAddress: "0.0.0.0:10256",
   394  				MetricsBindAddress: "127.0.0.1:10249",
   395  				ClusterCIDR:        "192.168.59.0/24",
   396  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   397  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   398  					MasqueradeAll: true,
   399  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   400  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   401  				},
   402  				// not specifying valid period in IPVS mode.
   403  				Mode: kubeproxyconfig.ProxyModeIPVS,
   404  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   405  					MaxPerCore:            ptr.To[int32](1),
   406  					Min:                   ptr.To[int32](1),
   407  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   408  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   409  				},
   410  				Logging: logsapi.LoggingConfiguration{
   411  					Format: "text",
   412  				},
   413  			},
   414  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeProxyIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0")},
   415  		},
   416  		"interfacePrefix is empty": {
   417  			config: kubeproxyconfig.KubeProxyConfiguration{
   418  				BindAddress:        "10.10.12.11",
   419  				HealthzBindAddress: "0.0.0.0:12345",
   420  				MetricsBindAddress: "127.0.0.1:10249",
   421  				ClusterCIDR:        "192.168.59.0/24",
   422  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   423  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   424  					MasqueradeAll: true,
   425  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   426  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   427  				},
   428  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   429  					MaxPerCore:            ptr.To[int32](1),
   430  					Min:                   ptr.To[int32](1),
   431  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   432  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   433  				},
   434  				DetectLocalMode: kubeproxyconfig.LocalModeInterfaceNamePrefix,
   435  				DetectLocal: kubeproxyconfig.DetectLocalConfiguration{
   436  					InterfaceNamePrefix: "",
   437  				},
   438  				Logging: logsapi.LoggingConfiguration{
   439  					Format: "text",
   440  				},
   441  			},
   442  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfacePrefix"), "", "must not be empty")},
   443  		},
   444  		"bridgeInterfaceName is empty": {
   445  			config: kubeproxyconfig.KubeProxyConfiguration{
   446  				BindAddress:        "10.10.12.11",
   447  				HealthzBindAddress: "0.0.0.0:12345",
   448  				MetricsBindAddress: "127.0.0.1:10249",
   449  				ClusterCIDR:        "192.168.59.0/24",
   450  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   451  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   452  					MasqueradeAll: true,
   453  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   454  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   455  				},
   456  				Conntrack: 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  				},
   462  				DetectLocalMode: kubeproxyconfig.LocalModeBridgeInterface,
   463  				DetectLocal: kubeproxyconfig.DetectLocalConfiguration{
   464  					InterfaceNamePrefix: "eth0", // we won't care about prefix since mode is not prefix
   465  				},
   466  				Logging: logsapi.LoggingConfiguration{
   467  					Format: "text",
   468  				},
   469  			},
   470  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("InterfaceName"), "", "must not be empty")},
   471  		},
   472  		"invalid DetectLocalMode": {
   473  			config: kubeproxyconfig.KubeProxyConfiguration{
   474  				BindAddress:        "10.10.12.11",
   475  				HealthzBindAddress: "0.0.0.0:12345",
   476  				MetricsBindAddress: "127.0.0.1:10249",
   477  				ClusterCIDR:        "192.168.59.0/24",
   478  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   479  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   480  					MasqueradeAll: true,
   481  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   482  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   483  				},
   484  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   485  					MaxPerCore:            ptr.To[int32](1),
   486  					Min:                   ptr.To[int32](1),
   487  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   488  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   489  				},
   490  				DetectLocalMode: "Guess",
   491  				Logging: logsapi.LoggingConfiguration{
   492  					Format: "text",
   493  				},
   494  			},
   495  			expectedErrs: field.ErrorList{field.NotSupported(newPath.Child("DetectLocalMode"), "Guess", []string{"ClusterCIDR", "NodeCIDR", "BridgeInterface", "InterfaceNamePrefix", ""})},
   496  		},
   497  		"invalid logging format": {
   498  			config: kubeproxyconfig.KubeProxyConfiguration{
   499  				BindAddress:        "10.10.12.11",
   500  				HealthzBindAddress: "0.0.0.0:12345",
   501  				MetricsBindAddress: "127.0.0.1:10249",
   502  				ClusterCIDR:        "192.168.59.0/24",
   503  				ConfigSyncPeriod:   metav1.Duration{Duration: 1 * time.Second},
   504  				IPTables: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   505  					MasqueradeAll: true,
   506  					SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   507  					MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   508  				},
   509  				Conntrack: kubeproxyconfig.KubeProxyConntrackConfiguration{
   510  					MaxPerCore:            ptr.To[int32](1),
   511  					Min:                   ptr.To[int32](1),
   512  					TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   513  					TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   514  				},
   515  				Logging: logsapi.LoggingConfiguration{
   516  					Format: "unsupported format",
   517  				},
   518  			},
   519  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("logging.format"), "unsupported format", "Unsupported log format")},
   520  		},
   521  	}
   522  
   523  	for name, testCase := range testCases {
   524  		if runtime.GOOS == "windows" && testCase.config.Mode == kubeproxyconfig.ProxyModeIPVS {
   525  			// IPVS is not supported on Windows.
   526  			t.Log("Skipping test on Windows: ", name)
   527  			continue
   528  		}
   529  		t.Run(name, func(t *testing.T) {
   530  			errs := Validate(&testCase.config)
   531  			if len(testCase.expectedErrs) != len(errs) {
   532  				t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
   533  			}
   534  			for i, err := range errs {
   535  				if err.Error() != testCase.expectedErrs[i].Error() {
   536  					t.Fatalf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
   537  				}
   538  			}
   539  		})
   540  	}
   541  }
   542  
   543  func TestValidateKubeProxyIPTablesConfiguration(t *testing.T) {
   544  	newPath := field.NewPath("KubeProxyConfiguration")
   545  
   546  	testCases := map[string]struct {
   547  		config       kubeproxyconfig.KubeProxyIPTablesConfiguration
   548  		expectedErrs field.ErrorList
   549  	}{
   550  		"valid iptables config": {
   551  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   552  				MasqueradeAll: true,
   553  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   554  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   555  			},
   556  			expectedErrs: field.ErrorList{},
   557  		},
   558  		"valid custom MasqueradeBit": {
   559  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   560  				MasqueradeBit: ptr.To[int32](5),
   561  				MasqueradeAll: true,
   562  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   563  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   564  			},
   565  			expectedErrs: field.ErrorList{},
   566  		},
   567  		"SyncPeriod must be > 0": {
   568  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   569  				MasqueradeAll: true,
   570  				SyncPeriod:    metav1.Duration{Duration: -5 * time.Second},
   571  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   572  			},
   573  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"),
   574  				field.Invalid(newPath.Child("KubeIPTablesConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPTablesConfiguration.MinSyncPeriod")},
   575  		},
   576  		"MinSyncPeriod must be > 0": {
   577  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   578  				MasqueradeBit: ptr.To[int32](5),
   579  				MasqueradeAll: true,
   580  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   581  				MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
   582  			},
   583  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
   584  		},
   585  		"MasqueradeBit cannot be < 0": {
   586  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   587  				MasqueradeBit: ptr.To[int32](-10),
   588  				MasqueradeAll: true,
   589  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   590  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   591  			},
   592  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPTablesConfiguration.MasqueradeBit"), -10, "must be within the range [0, 31]")},
   593  		},
   594  		"SyncPeriod must be >= MinSyncPeriod": {
   595  			config: kubeproxyconfig.KubeProxyIPTablesConfiguration{
   596  				MasqueradeBit: ptr.To[int32](5),
   597  				MasqueradeAll: true,
   598  				SyncPeriod:    metav1.Duration{Duration: 1 * time.Second},
   599  				MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
   600  			},
   601  			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")},
   602  		},
   603  	}
   604  
   605  	for _, testCase := range testCases {
   606  		errs := validateKubeProxyIPTablesConfiguration(testCase.config, newPath.Child("KubeIPTablesConfiguration"))
   607  		if len(testCase.expectedErrs) != len(errs) {
   608  			t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
   609  		}
   610  		for i, err := range errs {
   611  			if err.Error() != testCase.expectedErrs[i].Error() {
   612  				t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
   613  			}
   614  		}
   615  	}
   616  }
   617  
   618  func TestValidateKubeProxyIPVSConfiguration(t *testing.T) {
   619  	newPath := field.NewPath("KubeProxyConfiguration")
   620  	testCases := map[string]struct {
   621  		config       kubeproxyconfig.KubeProxyIPVSConfiguration
   622  		expectedErrs field.ErrorList
   623  	}{
   624  		"SyncPeriod is not greater than 0": {
   625  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   626  				SyncPeriod:    metav1.Duration{Duration: -5 * time.Second},
   627  				MinSyncPeriod: metav1.Duration{Duration: 2 * time.Second},
   628  			},
   629  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than 0"),
   630  				field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 2 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
   631  		},
   632  		"SyncPeriod cannot be 0": {
   633  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   634  				SyncPeriod:    metav1.Duration{Duration: 0 * time.Second},
   635  				MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
   636  			},
   637  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 0}, "must be greater than 0"),
   638  				field.Invalid(newPath.Child("KubeIPVSConfiguration.SyncPeriod"), metav1.Duration{Duration: 10 * time.Second}, "must be greater than or equal to KubeProxyConfiguration.KubeIPVSConfiguration.MinSyncPeriod")},
   639  		},
   640  		"MinSyncPeriod cannot be less than 0": {
   641  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   642  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   643  				MinSyncPeriod: metav1.Duration{Duration: -1 * time.Second},
   644  			},
   645  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.MinSyncPeriod"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
   646  		},
   647  		"SyncPeriod must be greater than MinSyncPeriod": {
   648  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   649  				SyncPeriod:    metav1.Duration{Duration: 1 * time.Second},
   650  				MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
   651  			},
   652  			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")},
   653  		},
   654  		"SyncPeriod == MinSyncPeriod": {
   655  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   656  				SyncPeriod:    metav1.Duration{Duration: 10 * time.Second},
   657  				MinSyncPeriod: metav1.Duration{Duration: 10 * time.Second},
   658  			},
   659  			expectedErrs: field.ErrorList{},
   660  		},
   661  		"SyncPeriod should be > MinSyncPeriod": {
   662  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   663  				SyncPeriod:    metav1.Duration{Duration: 10 * time.Second},
   664  				MinSyncPeriod: metav1.Duration{Duration: 5 * time.Second},
   665  			},
   666  			expectedErrs: field.ErrorList{},
   667  		},
   668  		"MinSyncPeriod can be 0": {
   669  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   670  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   671  				MinSyncPeriod: metav1.Duration{Duration: 0 * time.Second},
   672  			},
   673  			expectedErrs: field.ErrorList{},
   674  		},
   675  		"IPVS Timeout can be 0": {
   676  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   677  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   678  				TCPTimeout:    metav1.Duration{Duration: 0 * time.Second},
   679  				TCPFinTimeout: metav1.Duration{Duration: 0 * time.Second},
   680  				UDPTimeout:    metav1.Duration{Duration: 0 * time.Second},
   681  			},
   682  			expectedErrs: field.ErrorList{},
   683  		},
   684  		"IPVS Timeout > 0": {
   685  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   686  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   687  				TCPTimeout:    metav1.Duration{Duration: 1 * time.Second},
   688  				TCPFinTimeout: metav1.Duration{Duration: 2 * time.Second},
   689  				UDPTimeout:    metav1.Duration{Duration: 3 * time.Second},
   690  			},
   691  			expectedErrs: field.ErrorList{},
   692  		},
   693  		"TCP,TCPFin,UDP Timeouts < 0": {
   694  			config: kubeproxyconfig.KubeProxyIPVSConfiguration{
   695  				SyncPeriod:    metav1.Duration{Duration: 5 * time.Second},
   696  				TCPTimeout:    metav1.Duration{Duration: -1 * time.Second},
   697  				UDPTimeout:    metav1.Duration{Duration: -1 * time.Second},
   698  				TCPFinTimeout: metav1.Duration{Duration: -1 * time.Second},
   699  			},
   700  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"),
   701  				field.Invalid(newPath.Child("KubeIPVSConfiguration.TCPFinTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0"),
   702  				field.Invalid(newPath.Child("KubeIPVSConfiguration.UDPTimeout"), metav1.Duration{Duration: -1 * time.Second}, "must be greater than or equal to 0")},
   703  		},
   704  	}
   705  	for _, testCase := range testCases {
   706  		errs := validateKubeProxyIPVSConfiguration(testCase.config, newPath.Child("KubeIPVSConfiguration"))
   707  		if len(testCase.expectedErrs) != len(errs) {
   708  			t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
   709  		}
   710  		for i, err := range errs {
   711  			if err.Error() != testCase.expectedErrs[i].Error() {
   712  				t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
   713  			}
   714  		}
   715  	}
   716  }
   717  
   718  func TestValidateKubeProxyConntrackConfiguration(t *testing.T) {
   719  	newPath := field.NewPath("KubeProxyConfiguration")
   720  	testCases := map[string]struct {
   721  		config       kubeproxyconfig.KubeProxyConntrackConfiguration
   722  		expectedErrs field.ErrorList
   723  	}{
   724  		"valid 5 second timeouts": {
   725  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   726  				MaxPerCore:            ptr.To[int32](1),
   727  				Min:                   ptr.To[int32](1),
   728  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   729  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   730  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   731  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   732  			},
   733  			expectedErrs: field.ErrorList{},
   734  		},
   735  		"valid duration equal to 0 second timeout": {
   736  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   737  				MaxPerCore:            ptr.To[int32](1),
   738  				Min:                   ptr.To[int32](1),
   739  				TCPEstablishedTimeout: &metav1.Duration{Duration: 0 * time.Second},
   740  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 0 * time.Second},
   741  				UDPTimeout:            metav1.Duration{Duration: 0 * time.Second},
   742  				UDPStreamTimeout:      metav1.Duration{Duration: 0 * time.Second},
   743  			},
   744  			expectedErrs: field.ErrorList{},
   745  		},
   746  		"invalid MaxPerCore < 0": {
   747  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   748  				MaxPerCore:            ptr.To[int32](-1),
   749  				Min:                   ptr.To[int32](1),
   750  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   751  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   752  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   753  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   754  			},
   755  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.MaxPerCore"), -1, "must be greater than or equal to 0")},
   756  		},
   757  		"invalid minimum < 0": {
   758  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   759  				MaxPerCore:            ptr.To[int32](1),
   760  				Min:                   ptr.To[int32](-1),
   761  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   762  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   763  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   764  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   765  			},
   766  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.Min"), -1, "must be greater than or equal to 0")},
   767  		},
   768  		"invalid TCPEstablishedTimeout < 0": {
   769  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   770  				MaxPerCore:            ptr.To[int32](1),
   771  				Min:                   ptr.To[int32](1),
   772  				TCPEstablishedTimeout: &metav1.Duration{Duration: -5 * time.Second},
   773  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   774  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   775  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   776  			},
   777  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPEstablishedTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   778  		},
   779  		"invalid TCPCloseWaitTimeout < 0": {
   780  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   781  				MaxPerCore:            ptr.To[int32](1),
   782  				Min:                   ptr.To[int32](1),
   783  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   784  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: -5 * time.Second},
   785  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   786  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   787  			},
   788  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.TCPCloseWaitTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   789  		},
   790  		"invalid UDPTimeout < 0": {
   791  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   792  				MaxPerCore:            ptr.To[int32](1),
   793  				Min:                   ptr.To[int32](1),
   794  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   795  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   796  				UDPTimeout:            metav1.Duration{Duration: -5 * time.Second},
   797  				UDPStreamTimeout:      metav1.Duration{Duration: 5 * time.Second},
   798  			},
   799  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   800  		},
   801  		"invalid UDPStreamTimeout < 0": {
   802  			config: kubeproxyconfig.KubeProxyConntrackConfiguration{
   803  				MaxPerCore:            ptr.To[int32](1),
   804  				Min:                   ptr.To[int32](1),
   805  				TCPEstablishedTimeout: &metav1.Duration{Duration: 5 * time.Second},
   806  				TCPCloseWaitTimeout:   &metav1.Duration{Duration: 5 * time.Second},
   807  				UDPTimeout:            metav1.Duration{Duration: 5 * time.Second},
   808  				UDPStreamTimeout:      metav1.Duration{Duration: -5 * time.Second},
   809  			},
   810  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("KubeConntrackConfiguration.UDPStreamTimeout"), metav1.Duration{Duration: -5 * time.Second}, "must be greater than or equal to 0")},
   811  		},
   812  	}
   813  
   814  	for _, testCase := range testCases {
   815  		errs := validateKubeProxyConntrackConfiguration(testCase.config, newPath.Child("KubeConntrackConfiguration"))
   816  		if len(testCase.expectedErrs) != len(errs) {
   817  			t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
   818  		}
   819  		for i, err := range errs {
   820  			if err.Error() != testCase.expectedErrs[i].Error() {
   821  				t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
   822  			}
   823  		}
   824  	}
   825  }
   826  
   827  func TestValidateProxyMode(t *testing.T) {
   828  	newPath := field.NewPath("KubeProxyConfiguration")
   829  	successCases := []kubeproxyconfig.ProxyMode{""}
   830  	expectedNonExistentErrorMsg := "must be iptables, ipvs or blank (blank means the best-available proxy [currently iptables])"
   831  
   832  	if runtime.GOOS == "windows" {
   833  		successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace)
   834  		expectedNonExistentErrorMsg = "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])"
   835  	} else {
   836  		successCases = append(successCases, kubeproxyconfig.ProxyModeIPTables, kubeproxyconfig.ProxyModeIPVS)
   837  	}
   838  
   839  	for _, successCase := range successCases {
   840  		if errs := validateProxyMode(successCase, newPath.Child("ProxyMode")); len(errs) != 0 {
   841  			t.Errorf("expected success: %v", errs)
   842  		}
   843  	}
   844  
   845  	testCases := map[string]struct {
   846  		mode         kubeproxyconfig.ProxyMode
   847  		expectedErrs field.ErrorList
   848  	}{
   849  		"blank mode should default": {
   850  			mode:         kubeproxyconfig.ProxyMode(""),
   851  			expectedErrs: field.ErrorList{},
   852  		},
   853  		"invalid mode non-existent": {
   854  			mode:         kubeproxyconfig.ProxyMode("non-existing"),
   855  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", expectedNonExistentErrorMsg)},
   856  		},
   857  	}
   858  	for _, testCase := range testCases {
   859  		errs := validateProxyMode(testCase.mode, newPath)
   860  		if len(testCase.expectedErrs) != len(errs) {
   861  			t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
   862  		}
   863  		for i, err := range errs {
   864  			if err.Error() != testCase.expectedErrs[i].Error() {
   865  				t.Errorf("Expected error: %s, got %v", testCase.expectedErrs[i], err.Error())
   866  			}
   867  		}
   868  	}
   869  }
   870  
   871  func TestValidateClientConnectionConfiguration(t *testing.T) {
   872  	newPath := field.NewPath("KubeProxyConfiguration")
   873  
   874  	testCases := map[string]struct {
   875  		ccc          componentbaseconfig.ClientConnectionConfiguration
   876  		expectedErrs field.ErrorList
   877  	}{
   878  		"successful 0 value": {
   879  			ccc:          componentbaseconfig.ClientConnectionConfiguration{Burst: 0},
   880  			expectedErrs: field.ErrorList{},
   881  		},
   882  		"successful 5 value": {
   883  			ccc:          componentbaseconfig.ClientConnectionConfiguration{Burst: 5},
   884  			expectedErrs: field.ErrorList{},
   885  		},
   886  		"burst < 0": {
   887  			ccc:          componentbaseconfig.ClientConnectionConfiguration{Burst: -5},
   888  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("Burst"), -5, "must be greater than or equal to 0")},
   889  		},
   890  	}
   891  
   892  	for _, testCase := range testCases {
   893  		errs := validateClientConnectionConfiguration(testCase.ccc, newPath)
   894  		if len(testCase.expectedErrs) != len(errs) {
   895  			t.Fatalf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
   896  		}
   897  		for i, err := range errs {
   898  			if err.Error() != testCase.expectedErrs[i].Error() {
   899  				t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
   900  			}
   901  		}
   902  	}
   903  }
   904  
   905  func TestValidateHostPort(t *testing.T) {
   906  	newPath := field.NewPath("KubeProxyConfiguration")
   907  
   908  	successCases := []string{
   909  		"0.0.0.0:10256",
   910  		"127.0.0.1:10256",
   911  		"10.10.10.10:10256",
   912  	}
   913  
   914  	for _, successCase := range successCases {
   915  		if errs := validateHostPort(successCase, newPath.Child("HealthzBindAddress")); len(errs) != 0 {
   916  			t.Errorf("expected success: %v", errs)
   917  		}
   918  	}
   919  
   920  	errorCases := map[string]struct {
   921  		ip           string
   922  		expectedErrs field.ErrorList
   923  	}{
   924  		"missing port": {
   925  			ip:           "10.10.10.10",
   926  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "10.10.10.10", "must be IP:port")},
   927  		},
   928  		"digits outside of 1-255": {
   929  			ip:           "123.456.789.10:12345",
   930  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "123.456.789.10", "must be a valid IP")},
   931  		},
   932  		"invalid named-port": {
   933  			ip:           "10.10.10.10:foo",
   934  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "foo", "must be a valid port")},
   935  		},
   936  		"port cannot be 0": {
   937  			ip:           "10.10.10.10:0",
   938  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "0", "must be a valid port")},
   939  		},
   940  		"port is greater than allowed range": {
   941  			ip:           "10.10.10.10:65536",
   942  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("HealthzBindAddress"), "65536", "must be a valid port")},
   943  		},
   944  	}
   945  
   946  	for _, errorCase := range errorCases {
   947  		errs := validateHostPort(errorCase.ip, newPath.Child("HealthzBindAddress"))
   948  		if len(errorCase.expectedErrs) != len(errs) {
   949  			t.Fatalf("Expected %d errors, got %d errors: %v", len(errorCase.expectedErrs), len(errs), errs)
   950  		}
   951  		for i, err := range errs {
   952  			if err.Error() != errorCase.expectedErrs[i].Error() {
   953  				t.Errorf("Expected error: %s, got %s", errorCase.expectedErrs[i], err.Error())
   954  			}
   955  		}
   956  	}
   957  }
   958  
   959  func TestValidateKubeProxyNodePortAddress(t *testing.T) {
   960  	newPath := field.NewPath("KubeProxyConfiguration")
   961  
   962  	successCases := []struct {
   963  		addresses []string
   964  	}{
   965  		{[]string{}},
   966  		{[]string{"127.0.0.0/8"}},
   967  		{[]string{"0.0.0.0/0"}},
   968  		{[]string{"::/0"}},
   969  		{[]string{"127.0.0.1/32", "1.2.3.0/24"}},
   970  		{[]string{"127.0.0.0/8"}},
   971  		{[]string{"127.0.0.1/32"}},
   972  		{[]string{"::1/128"}},
   973  		{[]string{"1.2.3.4/32"}},
   974  		{[]string{"10.20.30.0/24"}},
   975  		{[]string{"10.20.0.0/16", "100.200.0.0/16"}},
   976  		{[]string{"10.0.0.0/8"}},
   977  		{[]string{"2001:db8::/32"}},
   978  	}
   979  
   980  	for _, successCase := range successCases {
   981  		if errs := validateKubeProxyNodePortAddress(successCase.addresses, newPath.Child("NodePortAddresses")); len(errs) != 0 {
   982  			t.Errorf("expected success: %v", errs)
   983  		}
   984  	}
   985  
   986  	testCases := map[string]struct {
   987  		addresses    []string
   988  		expectedErrs field.ErrorList
   989  	}{
   990  		"invalid foo address": {
   991  			addresses:    []string{"foo"},
   992  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "foo", "must be a valid CIDR")},
   993  		},
   994  		"invalid octet address": {
   995  			addresses:    []string{"10.0.0.0/0", "1.2.3"},
   996  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "1.2.3", "must be a valid CIDR")},
   997  		},
   998  		"address cannot be 0": {
   999  			addresses:    []string{"127.0.0.1/32", "0", "1.2.3.0/24"},
  1000  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "0", "must be a valid CIDR")},
  1001  		},
  1002  		"address missing subnet range": {
  1003  			addresses:    []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"},
  1004  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[1]"), "10.20.30.40", "must be a valid CIDR")},
  1005  		},
  1006  		"missing ipv6 subnet ranges": {
  1007  			addresses: []string{"::0", "::1", "2001:db8::/32"},
  1008  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[0]"), "::0", "must be a valid CIDR"),
  1009  				field.Invalid(newPath.Child("NodePortAddresses[1]"), "::1", "must be a valid CIDR")},
  1010  		},
  1011  		"invalid ipv6 ip format": {
  1012  			addresses:    []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"},
  1013  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("NodePortAddresses[2]"), "2001:db8:xyz/64", "must be a valid CIDR")},
  1014  		},
  1015  	}
  1016  
  1017  	for _, testCase := range testCases {
  1018  		errs := validateKubeProxyNodePortAddress(testCase.addresses, newPath.Child("NodePortAddresses"))
  1019  		if len(testCase.expectedErrs) != len(errs) {
  1020  			t.Errorf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
  1021  		}
  1022  		for i, err := range errs {
  1023  			if err.Error() != testCase.expectedErrs[i].Error() {
  1024  				t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
  1025  			}
  1026  		}
  1027  	}
  1028  }
  1029  
  1030  func TestValidateKubeProxyExcludeCIDRs(t *testing.T) {
  1031  	newPath := field.NewPath("KubeProxyConfiguration")
  1032  
  1033  	successCases := []struct {
  1034  		addresses []string
  1035  	}{
  1036  		{[]string{}},
  1037  		{[]string{"127.0.0.0/8"}},
  1038  		{[]string{"0.0.0.0/0"}},
  1039  		{[]string{"::/0"}},
  1040  		{[]string{"127.0.0.1/32", "1.2.3.0/24"}},
  1041  		{[]string{"127.0.0.0/8"}},
  1042  		{[]string{"127.0.0.1/32"}},
  1043  		{[]string{"::1/128"}},
  1044  		{[]string{"1.2.3.4/32"}},
  1045  		{[]string{"10.20.30.0/24"}},
  1046  		{[]string{"10.20.0.0/16", "100.200.0.0/16"}},
  1047  		{[]string{"10.0.0.0/8"}},
  1048  		{[]string{"2001:db8::/32"}},
  1049  	}
  1050  
  1051  	for _, successCase := range successCases {
  1052  		if errs := validateIPVSExcludeCIDRs(successCase.addresses, newPath.Child("ExcludeCIDRs")); len(errs) != 0 {
  1053  			t.Errorf("expected success: %v", errs)
  1054  		}
  1055  	}
  1056  
  1057  	testCases := map[string]struct {
  1058  		addresses    []string
  1059  		expectedErrs field.ErrorList
  1060  	}{
  1061  		"invalid foo address": {
  1062  			addresses:    []string{"foo"},
  1063  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "foo", "must be a valid CIDR")},
  1064  		},
  1065  		"invalid octet address": {
  1066  			addresses:    []string{"10.0.0.0/0", "1.2.3"},
  1067  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "1.2.3", "must be a valid CIDR")},
  1068  		},
  1069  		"address cannot be 0": {
  1070  			addresses:    []string{"127.0.0.1/32", "0", "1.2.3.0/24"},
  1071  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "0", "must be a valid CIDR")},
  1072  		},
  1073  		"address missing subnet range": {
  1074  			addresses:    []string{"127.0.0.1/32", "10.20.30.40", "1.2.3.0/24"},
  1075  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "10.20.30.40", "must be a valid CIDR")},
  1076  		},
  1077  		"missing ipv6 subnet ranges": {
  1078  			addresses: []string{"::0", "::1", "2001:db8::/32"},
  1079  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[0]"), "::0", "must be a valid CIDR"),
  1080  				field.Invalid(newPath.Child("ExcludeCIDRS[1]"), "::1", "must be a valid CIDR")},
  1081  		},
  1082  		"invalid ipv6 ip format": {
  1083  			addresses:    []string{"::1/128", "2001:db8::/32", "2001:db8:xyz/64"},
  1084  			expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ExcludeCIDRS[2]"), "2001:db8:xyz/64", "must be a valid CIDR")},
  1085  		},
  1086  	}
  1087  
  1088  	for _, testCase := range testCases {
  1089  		errs := validateIPVSExcludeCIDRs(testCase.addresses, newPath.Child("ExcludeCIDRS"))
  1090  		if len(testCase.expectedErrs) != len(errs) {
  1091  			t.Errorf("Expected %d errors, got %d errors: %v", len(testCase.expectedErrs), len(errs), errs)
  1092  		}
  1093  		for i, err := range errs {
  1094  			if err.Error() != testCase.expectedErrs[i].Error() {
  1095  				t.Errorf("Expected error: %s, got %s", testCase.expectedErrs[i], err.Error())
  1096  			}
  1097  		}
  1098  	}
  1099  }