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  }