k8s.io/kubernetes@v1.29.3/pkg/proxy/util/nodeport_addresses_test.go (about) 1 /* 2 Copyright 2022 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 util 18 19 import ( 20 "fmt" 21 "net" 22 "testing" 23 24 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/util/sets" 26 fake "k8s.io/kubernetes/pkg/proxy/util/testing" 27 netutils "k8s.io/utils/net" 28 ) 29 30 type InterfaceAddrsPair struct { 31 itf net.Interface 32 addrs []net.Addr 33 } 34 35 func checkNodeIPs(expected sets.Set[string], actual []net.IP) error { 36 notFound := expected.Clone() 37 extra := sets.New[string]() 38 for _, ip := range actual { 39 str := ip.String() 40 if notFound.Has(str) { 41 notFound.Delete(str) 42 } else { 43 extra.Insert(str) 44 } 45 } 46 if len(notFound) != 0 || len(extra) != 0 { 47 return fmt.Errorf("not found: %v, extra: %v", notFound.UnsortedList(), extra.UnsortedList()) 48 } 49 return nil 50 } 51 52 func TestGetNodeIPs(t *testing.T) { 53 type expectation struct { 54 matchAll bool 55 ips sets.Set[string] 56 } 57 58 testCases := []struct { 59 name string 60 cidrs []string 61 itfAddrsPairs []InterfaceAddrsPair 62 expected map[v1.IPFamily]expectation 63 }{ 64 { 65 name: "IPv4 single", 66 cidrs: []string{"10.20.30.0/24"}, 67 itfAddrsPairs: []InterfaceAddrsPair{ 68 { 69 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 70 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, 71 }, 72 { 73 itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, 74 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}}, 75 }, 76 }, 77 expected: map[v1.IPFamily]expectation{ 78 v1.IPv4Protocol: { 79 ips: sets.New[string]("10.20.30.51"), 80 }, 81 v1.IPv6Protocol: { 82 matchAll: true, 83 ips: nil, 84 }, 85 }, 86 }, 87 { 88 name: "IPv4 zero CIDR", 89 cidrs: []string{"0.0.0.0/0"}, 90 itfAddrsPairs: []InterfaceAddrsPair{ 91 { 92 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 93 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, 94 }, 95 { 96 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 97 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, 98 }, 99 }, 100 expected: map[v1.IPFamily]expectation{ 101 v1.IPv4Protocol: { 102 matchAll: true, 103 ips: sets.New[string]("10.20.30.51", "127.0.0.1"), 104 }, 105 v1.IPv6Protocol: { 106 matchAll: true, 107 ips: nil, 108 }, 109 }, 110 }, 111 { 112 name: "IPv6 multiple", 113 cidrs: []string{"2001:db8::/64", "::1/128"}, 114 itfAddrsPairs: []InterfaceAddrsPair{ 115 { 116 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 117 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}}, 118 }, 119 { 120 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 121 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, 122 }, 123 }, 124 expected: map[v1.IPFamily]expectation{ 125 v1.IPv4Protocol: { 126 matchAll: true, 127 ips: nil, 128 }, 129 v1.IPv6Protocol: { 130 ips: sets.New[string]("2001:db8::1", "::1"), 131 }, 132 }, 133 }, 134 { 135 name: "IPv6 zero CIDR", 136 cidrs: []string{"::/0"}, 137 itfAddrsPairs: []InterfaceAddrsPair{ 138 { 139 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 140 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}}, 141 }, 142 { 143 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 144 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, 145 }, 146 }, 147 expected: map[v1.IPFamily]expectation{ 148 v1.IPv4Protocol: { 149 matchAll: true, 150 ips: nil, 151 }, 152 v1.IPv6Protocol: { 153 matchAll: true, 154 ips: sets.New[string]("2001:db8::1", "::1"), 155 }, 156 }, 157 }, 158 { 159 name: "IPv4 localhost exact", 160 cidrs: []string{"127.0.0.1/32"}, 161 itfAddrsPairs: []InterfaceAddrsPair{ 162 { 163 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 164 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, 165 }, 166 { 167 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 168 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, 169 }, 170 }, 171 expected: map[v1.IPFamily]expectation{ 172 v1.IPv4Protocol: { 173 ips: sets.New[string]("127.0.0.1"), 174 }, 175 v1.IPv6Protocol: { 176 matchAll: true, 177 ips: nil, 178 }, 179 }, 180 }, 181 { 182 name: "IPv4 localhost subnet", 183 cidrs: []string{"127.0.0.0/8"}, 184 itfAddrsPairs: []InterfaceAddrsPair{ 185 { 186 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 187 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.1.1"), Mask: net.CIDRMask(8, 32)}}, 188 }, 189 }, 190 expected: map[v1.IPFamily]expectation{ 191 v1.IPv4Protocol: { 192 ips: sets.New[string]("127.0.1.1"), 193 }, 194 v1.IPv6Protocol: { 195 matchAll: true, 196 ips: nil, 197 }, 198 }, 199 }, 200 { 201 name: "IPv4 multiple", 202 cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, 203 itfAddrsPairs: []InterfaceAddrsPair{ 204 { 205 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 206 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("10.20.30.51"), Mask: net.CIDRMask(24, 32)}}, 207 }, 208 { 209 itf: net.Interface{Index: 2, MTU: 0, Name: "eth1", HardwareAddr: nil, Flags: 0}, 210 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}}, 211 }, 212 }, 213 expected: map[v1.IPFamily]expectation{ 214 v1.IPv4Protocol: { 215 ips: sets.New[string]("10.20.30.51", "100.200.201.1"), 216 }, 217 v1.IPv6Protocol: { 218 matchAll: true, 219 ips: nil, 220 }, 221 }, 222 }, 223 { 224 name: "IPv4 multiple, no match", 225 cidrs: []string{"10.20.30.0/24", "100.200.201.0/24"}, 226 itfAddrsPairs: []InterfaceAddrsPair{ 227 { 228 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 229 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("192.168.1.2"), Mask: net.CIDRMask(24, 32)}}, 230 }, 231 { 232 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 233 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, 234 }, 235 }, 236 expected: map[v1.IPFamily]expectation{ 237 v1.IPv4Protocol: { 238 ips: nil, 239 }, 240 v1.IPv6Protocol: { 241 matchAll: true, 242 ips: nil, 243 }, 244 }, 245 }, 246 { 247 name: "empty list, IPv4 addrs", 248 cidrs: []string{}, 249 itfAddrsPairs: []InterfaceAddrsPair{ 250 { 251 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 252 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("192.168.1.2"), Mask: net.CIDRMask(24, 32)}}, 253 }, 254 { 255 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 256 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}}, 257 }, 258 }, 259 expected: map[v1.IPFamily]expectation{ 260 v1.IPv4Protocol: { 261 matchAll: true, 262 ips: sets.New[string]("192.168.1.2", "127.0.0.1"), 263 }, 264 v1.IPv6Protocol: { 265 matchAll: true, 266 ips: nil, 267 }, 268 }, 269 }, 270 { 271 name: "empty list, IPv6 addrs", 272 cidrs: []string{}, 273 itfAddrsPairs: []InterfaceAddrsPair{ 274 { 275 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 276 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}}, 277 }, 278 { 279 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 280 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}}, 281 }, 282 }, 283 expected: map[v1.IPFamily]expectation{ 284 v1.IPv4Protocol: { 285 matchAll: true, 286 ips: nil, 287 }, 288 v1.IPv6Protocol: { 289 matchAll: true, 290 ips: sets.New[string]("2001:db8::1", "::1"), 291 }, 292 }, 293 }, 294 { 295 name: "IPv4 redundant CIDRs", 296 cidrs: []string{"1.2.3.0/24", "0.0.0.0/0"}, 297 itfAddrsPairs: []InterfaceAddrsPair{ 298 { 299 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 300 addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}}, 301 }, 302 }, 303 expected: map[v1.IPFamily]expectation{ 304 v1.IPv4Protocol: { 305 matchAll: true, 306 ips: sets.New[string]("1.2.3.4"), 307 }, 308 v1.IPv6Protocol: { 309 matchAll: true, 310 ips: nil, 311 }, 312 }, 313 }, 314 { 315 name: "Dual-stack, redundant IPv4", 316 cidrs: []string{"0.0.0.0/0", "1.2.3.0/24", "2001:db8::1/128"}, 317 itfAddrsPairs: []InterfaceAddrsPair{ 318 { 319 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 320 addrs: []net.Addr{ 321 &net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}, 322 &net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, 323 }, 324 }, 325 { 326 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 327 addrs: []net.Addr{ 328 &net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}, 329 &net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}, 330 }, 331 }, 332 }, 333 expected: map[v1.IPFamily]expectation{ 334 v1.IPv4Protocol: { 335 matchAll: true, 336 ips: sets.New[string]("1.2.3.4", "127.0.0.1"), 337 }, 338 v1.IPv6Protocol: { 339 ips: sets.New[string]("2001:db8::1"), 340 }, 341 }, 342 }, 343 { 344 name: "Dual-stack, redundant IPv6", 345 cidrs: []string{"::/0", "1.2.3.0/24", "2001:db8::1/128"}, 346 itfAddrsPairs: []InterfaceAddrsPair{ 347 { 348 itf: net.Interface{Index: 0, MTU: 0, Name: "eth0", HardwareAddr: nil, Flags: 0}, 349 addrs: []net.Addr{ 350 &net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}, 351 &net.IPNet{IP: netutils.ParseIPSloppy("2001:db8::1"), Mask: net.CIDRMask(64, 128)}, 352 }, 353 }, 354 { 355 itf: net.Interface{Index: 1, MTU: 0, Name: "lo", HardwareAddr: nil, Flags: 0}, 356 addrs: []net.Addr{ 357 &net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}, 358 &net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}, 359 }, 360 }, 361 }, 362 expected: map[v1.IPFamily]expectation{ 363 v1.IPv4Protocol: { 364 ips: sets.New[string]("1.2.3.4"), 365 }, 366 v1.IPv6Protocol: { 367 matchAll: true, 368 ips: sets.New[string]("2001:db8::1", "::1"), 369 }, 370 }, 371 }, 372 } 373 374 for _, tc := range testCases { 375 t.Run(tc.name, func(t *testing.T) { 376 nw := fake.NewFakeNetwork() 377 for _, pair := range tc.itfAddrsPairs { 378 nw.AddInterfaceAddr(&pair.itf, pair.addrs) 379 } 380 381 for _, family := range []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol} { 382 npa := NewNodePortAddresses(family, tc.cidrs) 383 384 if npa.MatchAll() != tc.expected[family].matchAll { 385 t.Errorf("unexpected MatchAll(%s), expected: %v", family, tc.expected[family].matchAll) 386 } 387 388 ips, err := npa.GetNodeIPs(nw) 389 expectedIPs := tc.expected[family].ips 390 391 // The fake InterfaceAddrs() never returns an error, so 392 // the only error GetNodeIPs will return is "no 393 // addresses found". 394 if err != nil { 395 t.Errorf("unexpected error: %v", err) 396 } 397 err = checkNodeIPs(expectedIPs, ips) 398 if err != nil { 399 t.Errorf("unexpected mismatch for %s: %v", family, err) 400 } 401 } 402 }) 403 } 404 } 405 406 func TestContainsIPv4Loopback(t *testing.T) { 407 tests := []struct { 408 name string 409 cidrStrings []string 410 want bool 411 }{ 412 { 413 name: "empty", 414 want: true, 415 }, 416 { 417 name: "all zeros ipv4", 418 cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "0.0.0.0/0"}, 419 want: true, 420 }, 421 { 422 name: "all zeros ipv6", 423 cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::/0"}, 424 want: false, 425 }, 426 { 427 name: "ipv4 loopback", 428 cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "127.0.0.0/8"}, 429 want: true, 430 }, 431 { 432 name: "ipv6 loopback", 433 cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "::1/128"}, 434 want: false, 435 }, 436 { 437 name: "ipv4 loopback smaller range", 438 cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "127.0.2.0/28"}, 439 want: true, 440 }, 441 { 442 name: "ipv4 loopback within larger range", 443 cidrStrings: []string{"224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64", "64.0.0.0/2"}, 444 want: true, 445 }, 446 { 447 name: "non loop loopback", 448 cidrStrings: []string{"128.0.2.0/28", "224.0.0.0/24", "192.168.0.0/16", "fd00:1:d::/64"}, 449 want: false, 450 }, 451 } 452 for _, tt := range tests { 453 t.Run(tt.name, func(t *testing.T) { 454 npa := NewNodePortAddresses(v1.IPv4Protocol, tt.cidrStrings) 455 if got := npa.ContainsIPv4Loopback(); got != tt.want { 456 t.Errorf("IPv4 ContainsIPv4Loopback() = %v, want %v", got, tt.want) 457 } 458 // ContainsIPv4Loopback should always be false for family=IPv6 459 npa = NewNodePortAddresses(v1.IPv6Protocol, tt.cidrStrings) 460 if got := npa.ContainsIPv4Loopback(); got { 461 t.Errorf("IPv6 ContainsIPv4Loopback() = %v, want %v", got, false) 462 } 463 }) 464 } 465 }