mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-05 23:47:50 +00:00
Make NodePortAddresses explicitly IP-family-specific
Both proxies handle IPv4 and IPv6 nodeport addresses separately, but GetNodeAddresses went out of its way to make that difficult. Fix that. This commit does not change any externally-visible semantics, but it makes the existing weird semantics more obvious. Specifically, if you say "--nodeport-addresses 10.0.0.0/8,192.168.0.0/16", then the dual-stack proxy code would have split that into a list of IPv4 CIDRs (["10.0.0.0/8", "192.168.0.0/16"]) to pass to the IPv4 proxier, and a list of IPv6 CIDRs ([]) to pass to the IPv6 proxier, and then the IPv6 proxier would say "well since the list of nodeport addresses is empty, I'll listen on all IPv6 addresses", which probably isn't what you meant, but that's what it did.
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
netutils "k8s.io/utils/net"
|
||||
)
|
||||
@@ -35,27 +36,45 @@ type NodePortAddresses struct {
|
||||
// RFC 5735 127.0.0.0/8 - This block is assigned for use as the Internet host loopback address
|
||||
var ipv4LoopbackStart = net.IPv4(127, 0, 0, 0)
|
||||
|
||||
// NewNodePortAddresses takes the `--nodeport-addresses` value (which is assumed to
|
||||
// contain only valid CIDRs) and returns a NodePortAddresses object. If cidrStrings is
|
||||
// empty, this is treated as `["0.0.0.0/0", "::/0"]`.
|
||||
func NewNodePortAddresses(cidrStrings []string) *NodePortAddresses {
|
||||
if len(cidrStrings) == 0 {
|
||||
cidrStrings = []string{IPv4ZeroCIDR, IPv6ZeroCIDR}
|
||||
}
|
||||
|
||||
npa := &NodePortAddresses{
|
||||
cidrStrings: cidrStrings,
|
||||
// NewNodePortAddresses takes an IP family and the `--nodeport-addresses` value (which is
|
||||
// assumed to contain only valid CIDRs, potentially of both IP families) and returns a
|
||||
// NodePortAddresses object for the given family. If there are no CIDRs of the given
|
||||
// family then the CIDR "0.0.0.0/0" or "::/0" will be added (even if there are CIDRs of
|
||||
// the other family).
|
||||
func NewNodePortAddresses(family v1.IPFamily, cidrStrings []string) *NodePortAddresses {
|
||||
npa := &NodePortAddresses{}
|
||||
|
||||
// Filter CIDRs to correct family
|
||||
for _, str := range cidrStrings {
|
||||
if (family == v1.IPv4Protocol) == netutils.IsIPv4CIDRString(str) {
|
||||
npa.cidrStrings = append(npa.cidrStrings, str)
|
||||
}
|
||||
}
|
||||
if len(npa.cidrStrings) == 0 {
|
||||
if family == v1.IPv4Protocol {
|
||||
npa.cidrStrings = []string{IPv4ZeroCIDR}
|
||||
} else {
|
||||
npa.cidrStrings = []string{IPv6ZeroCIDR}
|
||||
}
|
||||
}
|
||||
|
||||
// Now parse
|
||||
for _, str := range npa.cidrStrings {
|
||||
_, cidr, _ := netutils.ParseCIDRSloppy(str)
|
||||
npa.cidrs = append(npa.cidrs, cidr)
|
||||
|
||||
if netutils.IsIPv4CIDR(cidr) {
|
||||
if cidr.IP.IsLoopback() || cidr.Contains(ipv4LoopbackStart) {
|
||||
npa.containsIPv4Loopback = true
|
||||
}
|
||||
}
|
||||
|
||||
if IsZeroCIDR(str) {
|
||||
// Ignore everything else
|
||||
npa.cidrs = []*net.IPNet{cidr}
|
||||
break
|
||||
}
|
||||
|
||||
npa.cidrs = append(npa.cidrs, cidr)
|
||||
}
|
||||
|
||||
return npa
|
||||
@@ -65,10 +84,10 @@ func (npa *NodePortAddresses) String() string {
|
||||
return fmt.Sprintf("%v", npa.cidrStrings)
|
||||
}
|
||||
|
||||
// GetNodeAddresses return all matched node IP addresses for npa's CIDRs.
|
||||
// If npa's CIDRs include "0.0.0.0/0" and/or "::/0", then those values will be returned
|
||||
// verbatim in the response and no actual IPs of that family will be returned.
|
||||
// If no matching IPs are found, GetNodeAddresses will return an error.
|
||||
// GetNodeAddresses returns all matched node IP addresses for npa's IP family. If npa's
|
||||
// CIDRs include "0.0.0.0/0" or "::/0", then that value will be returned verbatim in
|
||||
// the response and no actual IPs of that family will be returned. If no matching IPs are
|
||||
// found, GetNodeAddresses will return an error.
|
||||
// NetworkInterfacer is injected for test purpose.
|
||||
func (npa *NodePortAddresses) GetNodeAddresses(nw NetworkInterfacer) (sets.Set[string], error) {
|
||||
uniqueAddressList := sets.New[string]()
|
||||
@@ -77,6 +96,7 @@ func (npa *NodePortAddresses) GetNodeAddresses(nw NetworkInterfacer) (sets.Set[s
|
||||
for _, cidr := range npa.cidrStrings {
|
||||
if IsZeroCIDR(cidr) {
|
||||
uniqueAddressList.Insert(cidr)
|
||||
return uniqueAddressList, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,12 +105,7 @@ func (npa *NodePortAddresses) GetNodeAddresses(nw NetworkInterfacer) (sets.Set[s
|
||||
return nil, fmt.Errorf("error listing all interfaceAddrs from host, error: %v", err)
|
||||
}
|
||||
|
||||
// Second round of iteration to parse IPs based on cidr.
|
||||
for _, cidr := range npa.cidrs {
|
||||
if IsZeroCIDR(cidr.String()) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
// nw.InterfaceAddrs may return net.IPAddr or net.IPNet on windows, and it will return net.IPNet on linux.
|
||||
@@ -104,12 +119,7 @@ func (npa *NodePortAddresses) GetNodeAddresses(nw NetworkInterfacer) (sets.Set[s
|
||||
}
|
||||
|
||||
if cidr.Contains(ip) {
|
||||
if netutils.IsIPv6(ip) && !uniqueAddressList.Has(IPv6ZeroCIDR) {
|
||||
uniqueAddressList.Insert(ip.String())
|
||||
}
|
||||
if !netutils.IsIPv6(ip) && !uniqueAddressList.Has(IPv4ZeroCIDR) {
|
||||
uniqueAddressList.Insert(ip.String())
|
||||
}
|
||||
uniqueAddressList.Insert(ip.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
fake "k8s.io/kubernetes/pkg/proxy/util/testing"
|
||||
netutils "k8s.io/utils/net"
|
||||
@@ -31,11 +32,15 @@ type InterfaceAddrsPair struct {
|
||||
}
|
||||
|
||||
func TestGetNodeAddresses(t *testing.T) {
|
||||
type expectation struct {
|
||||
ips sets.Set[string]
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
cidrs []string
|
||||
itfAddrsPairs []InterfaceAddrsPair
|
||||
expected sets.Set[string]
|
||||
expected map[v1.IPFamily]expectation
|
||||
}{
|
||||
{
|
||||
name: "IPv4 single",
|
||||
@@ -50,7 +55,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("10.20.30.51"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("10.20.30.51"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 zero CIDR",
|
||||
@@ -65,7 +77,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("0.0.0.0/0"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv6 multiple",
|
||||
@@ -80,7 +99,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("2001:db8::1", "::1"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("2001:db8::1", "::1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv6 zero CIDR",
|
||||
@@ -95,7 +121,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("::/0"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 localhost exact",
|
||||
@@ -110,7 +143,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("127.0.0.1"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("127.0.0.1"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 localhost subnet",
|
||||
@@ -121,7 +161,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.1.1"), Mask: net.CIDRMask(8, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("127.0.1.1"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("127.0.1.1"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 multiple",
|
||||
@@ -136,7 +183,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("100.200.201.1"), Mask: net.CIDRMask(24, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("10.20.30.51", "100.200.201.1"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("10.20.30.51", "100.200.201.1"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 multiple, no match",
|
||||
@@ -151,7 +205,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}},
|
||||
},
|
||||
},
|
||||
expected: nil,
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: nil,
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty list, IPv4 addrs",
|
||||
@@ -166,7 +227,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("127.0.0.1"), Mask: net.CIDRMask(8, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("0.0.0.0/0", "::/0"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty list, IPv6 addrs",
|
||||
@@ -181,7 +249,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("::1"), Mask: net.CIDRMask(128, 128)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("0.0.0.0/0", "::/0"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "IPv4 redundant CIDRs",
|
||||
@@ -192,7 +267,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
addrs: []net.Addr{&net.IPNet{IP: netutils.ParseIPSloppy("1.2.3.4"), Mask: net.CIDRMask(30, 32)}},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("0.0.0.0/0"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Dual-stack, redundant IPv4",
|
||||
@@ -213,7 +295,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("0.0.0.0/0", "2001:db8::1"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("0.0.0.0/0"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("2001:db8::1"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Dual-stack, redundant IPv6",
|
||||
@@ -234,7 +323,14 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: sets.New[string]("::/0", "1.2.3.4"),
|
||||
expected: map[v1.IPFamily]expectation{
|
||||
v1.IPv4Protocol: {
|
||||
ips: sets.New[string]("1.2.3.4"),
|
||||
},
|
||||
v1.IPv6Protocol: {
|
||||
ips: sets.New[string]("::/0"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -245,16 +341,20 @@ func TestGetNodeAddresses(t *testing.T) {
|
||||
nw.AddInterfaceAddr(&pair.itf, pair.addrs)
|
||||
}
|
||||
|
||||
npa := NewNodePortAddresses(tc.cidrs)
|
||||
addrList, err := npa.GetNodeAddresses(nw)
|
||||
// The fake InterfaceAddrs() never returns an error, so the only
|
||||
// error GetNodeAddresses will return is "no addresses found".
|
||||
if err != nil && tc.expected != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
for _, family := range []v1.IPFamily{v1.IPv4Protocol, v1.IPv6Protocol} {
|
||||
npa := NewNodePortAddresses(family, tc.cidrs)
|
||||
|
||||
if !addrList.Equal(tc.expected) {
|
||||
t.Errorf("unexpected mismatch, expected: %v, got: %v", tc.expected, addrList)
|
||||
addrList, err := npa.GetNodeAddresses(nw)
|
||||
// The fake InterfaceAddrs() never returns an error, so
|
||||
// the only error GetNodeAddresses will return is "no
|
||||
// addresses found".
|
||||
if err != nil && tc.expected[family].ips != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if !addrList.Equal(tc.expected[family].ips) {
|
||||
t.Errorf("unexpected mismatch for %s, expected: %v, got: %v", family, tc.expected[family].ips, addrList)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -308,9 +408,14 @@ func TestContainsIPv4Loopback(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
npa := NewNodePortAddresses(tt.cidrStrings)
|
||||
npa := NewNodePortAddresses(v1.IPv4Protocol, tt.cidrStrings)
|
||||
if got := npa.ContainsIPv4Loopback(); got != tt.want {
|
||||
t.Errorf("ContainsIPv4Loopback() = %v, want %v", got, tt.want)
|
||||
t.Errorf("IPv4 ContainsIPv4Loopback() = %v, want %v", got, tt.want)
|
||||
}
|
||||
// ContainsIPv4Loopback should always be false for family=IPv6
|
||||
npa = NewNodePortAddresses(v1.IPv6Protocol, tt.cidrStrings)
|
||||
if got := npa.ContainsIPv4Loopback(); got {
|
||||
t.Errorf("IPv6 ContainsIPv4Loopback() = %v, want %v", got, false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user