kube-proxy: store ExternalIPs as net.IP

They were stored as strings which could be non-canonical
and cause problems
This commit is contained in:
Lars Ekman
2024-01-07 10:48:39 +01:00
parent d2294007b0
commit 9eac24c656
8 changed files with 47 additions and 41 deletions

View File

@@ -50,8 +50,8 @@ func deleteStaleServiceConntrackEntries(isIPv6 bool, exec utilexec.Interface, sv
if svcInfo, ok := svcPortMap[svcPortName]; ok { if svcInfo, ok := svcPortMap[svcPortName]; ok {
klog.V(4).InfoS("Newly-active UDP service may have stale conntrack entries", "servicePortName", svcPortName) klog.V(4).InfoS("Newly-active UDP service may have stale conntrack entries", "servicePortName", svcPortName)
conntrackCleanupServiceIPs.Insert(svcInfo.ClusterIP().String()) conntrackCleanupServiceIPs.Insert(svcInfo.ClusterIP().String())
for _, extIP := range svcInfo.ExternalIPStrings() { for _, extIP := range svcInfo.ExternalIPs() {
conntrackCleanupServiceIPs.Insert(extIP) conntrackCleanupServiceIPs.Insert(extIP.String())
} }
for _, lbIP := range svcInfo.LoadBalancerVIPs() { for _, lbIP := range svcInfo.LoadBalancerVIPs() {
conntrackCleanupServiceIPs.Insert(lbIP.String()) conntrackCleanupServiceIPs.Insert(lbIP.String())
@@ -97,8 +97,8 @@ func deleteStaleEndpointConntrackEntries(exec utilexec.Interface, svcPortMap pro
if err != nil { if err != nil {
klog.ErrorS(err, "Failed to delete endpoint connections", "servicePortName", epSvcPair.ServicePortName) klog.ErrorS(err, "Failed to delete endpoint connections", "servicePortName", epSvcPair.ServicePortName)
} }
for _, extIP := range svcInfo.ExternalIPStrings() { for _, extIP := range svcInfo.ExternalIPs() {
err := ClearEntriesForNAT(exec, extIP, endpointIP, v1.ProtocolUDP) err := ClearEntriesForNAT(exec, extIP.String(), endpointIP, v1.ProtocolUDP)
if err != nil { if err != nil {
klog.ErrorS(err, "Failed to delete endpoint connections for externalIP", "servicePortName", epSvcPair.ServicePortName, "externalIP", extIP) klog.ErrorS(err, "Failed to delete endpoint connections for externalIP", "servicePortName", epSvcPair.ServicePortName, "externalIP", extIP)
} }

View File

@@ -1077,7 +1077,7 @@ func (proxier *Proxier) syncProxyRules() {
} }
// Capture externalIPs. // Capture externalIPs.
for _, externalIP := range svcInfo.ExternalIPStrings() { for _, externalIP := range svcInfo.ExternalIPs() {
if hasEndpoints { if hasEndpoints {
// Send traffic bound for external IPs to the "external // Send traffic bound for external IPs to the "external
// destinations" chain. // destinations" chain.
@@ -1085,7 +1085,7 @@ func (proxier *Proxier) syncProxyRules() {
"-A", string(kubeServicesChain), "-A", string(kubeServicesChain),
"-m", "comment", "--comment", fmt.Sprintf(`"%s external IP"`, svcPortNameString), "-m", "comment", "--comment", fmt.Sprintf(`"%s external IP"`, svcPortNameString),
"-m", protocol, "-p", protocol, "-m", protocol, "-p", protocol,
"-d", externalIP, "-d", externalIP.String(),
"--dport", strconv.Itoa(svcInfo.Port()), "--dport", strconv.Itoa(svcInfo.Port()),
"-j", string(externalTrafficChain)) "-j", string(externalTrafficChain))
} }
@@ -1097,7 +1097,7 @@ func (proxier *Proxier) syncProxyRules() {
"-A", string(kubeExternalServicesChain), "-A", string(kubeExternalServicesChain),
"-m", "comment", "--comment", externalTrafficFilterComment, "-m", "comment", "--comment", externalTrafficFilterComment,
"-m", protocol, "-p", protocol, "-m", protocol, "-p", protocol,
"-d", externalIP, "-d", externalIP.String(),
"--dport", strconv.Itoa(svcInfo.Port()), "--dport", strconv.Itoa(svcInfo.Port()),
"-j", externalTrafficFilterTarget, "-j", externalTrafficFilterTarget,
) )

View File

@@ -1097,10 +1097,10 @@ func (proxier *Proxier) syncProxyRules() {
} }
// Capture externalIPs. // Capture externalIPs.
for _, externalIP := range svcInfo.ExternalIPStrings() { for _, externalIP := range svcInfo.ExternalIPs() {
// ipset call // ipset call
entry := &utilipset.Entry{ entry := &utilipset.Entry{
IP: externalIP, IP: externalIP.String(),
Port: svcInfo.Port(), Port: svcInfo.Port(),
Protocol: protocol, Protocol: protocol,
SetType: utilipset.HashIPPort, SetType: utilipset.HashIPPort,
@@ -1123,7 +1123,7 @@ func (proxier *Proxier) syncProxyRules() {
// ipvs call // ipvs call
serv := &utilipvs.VirtualServer{ serv := &utilipvs.VirtualServer{
Address: netutils.ParseIPSloppy(externalIP), Address: externalIP,
Port: uint16(svcInfo.Port()), Port: uint16(svcInfo.Port()),
Protocol: string(svcInfo.Protocol()), Protocol: string(svcInfo.Protocol()),
Scheduler: proxier.ipvsScheduler, Scheduler: proxier.ipvsScheduler,

View File

@@ -1153,14 +1153,14 @@ func (proxier *Proxier) syncProxyRules() {
} }
// Capture externalIPs. // Capture externalIPs.
for _, externalIP := range svcInfo.ExternalIPStrings() { for _, externalIP := range svcInfo.ExternalIPs() {
if hasEndpoints { if hasEndpoints {
// Send traffic bound for external IPs to the "external // Send traffic bound for external IPs to the "external
// destinations" chain. // destinations" chain.
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeServiceIPsMap, Map: kubeServiceIPsMap,
Key: []string{ Key: []string{
externalIP, externalIP.String(),
protocol, protocol,
strconv.Itoa(svcInfo.Port()), strconv.Itoa(svcInfo.Port()),
}, },
@@ -1176,7 +1176,7 @@ func (proxier *Proxier) syncProxyRules() {
tx.Add(&knftables.Element{ tx.Add(&knftables.Element{
Map: kubeNoEndpointServicesMap, Map: kubeNoEndpointServicesMap,
Key: []string{ Key: []string{
externalIP, externalIP.String(),
protocol, protocol,
strconv.Itoa(svcInfo.Port()), strconv.Itoa(svcInfo.Port()),
}, },

View File

@@ -411,7 +411,7 @@ func TestServiceToServiceMap(t *testing.T) {
}, },
expected: map[ServicePortName]*BaseServicePortInfo{ expected: map[ServicePortName]*BaseServicePortInfo{
makeServicePortName("test", "validIPv4", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) { makeServicePortName("test", "validIPv4", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
bsvcPortInfo.externalIPs = []string{testExternalIPv4} bsvcPortInfo.externalIPs = makeIPs(testExternalIPv4)
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv4} bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv4}
bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv4) bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv4)
}), }),
@@ -449,7 +449,7 @@ func TestServiceToServiceMap(t *testing.T) {
}, },
expected: map[ServicePortName]*BaseServicePortInfo{ expected: map[ServicePortName]*BaseServicePortInfo{
makeServicePortName("test", "validIPv6", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv6, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) { makeServicePortName("test", "validIPv6", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv6, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
bsvcPortInfo.externalIPs = []string{testExternalIPv6} bsvcPortInfo.externalIPs = makeIPs(testExternalIPv6)
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv6} bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv6}
bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv6) bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv6)
}), }),
@@ -487,7 +487,7 @@ func TestServiceToServiceMap(t *testing.T) {
}, },
expected: map[ServicePortName]*BaseServicePortInfo{ expected: map[ServicePortName]*BaseServicePortInfo{
makeServicePortName("test", "filterIPv6InIPV4Mode", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) { makeServicePortName("test", "filterIPv6InIPV4Mode", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv4, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
bsvcPortInfo.externalIPs = []string{testExternalIPv4} bsvcPortInfo.externalIPs = makeIPs(testExternalIPv4)
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv4} bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv4}
bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv4) bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv4)
}), }),
@@ -525,7 +525,7 @@ func TestServiceToServiceMap(t *testing.T) {
}, },
expected: map[ServicePortName]*BaseServicePortInfo{ expected: map[ServicePortName]*BaseServicePortInfo{
makeServicePortName("test", "filterIPv4InIPV6Mode", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv6, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) { makeServicePortName("test", "filterIPv4InIPV6Mode", "testPort", v1.ProtocolTCP): makeTestServiceInfo(testClusterIPv6, 12345, "TCP", 0, func(bsvcPortInfo *BaseServicePortInfo) {
bsvcPortInfo.externalIPs = []string{testExternalIPv6} bsvcPortInfo.externalIPs = makeIPs(testExternalIPv6)
bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv6} bsvcPortInfo.loadBalancerSourceRanges = []string{testSourceRangeIPv6}
bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv6) bsvcPortInfo.loadBalancerVIPs = makeIPs(testExternalIPv6)
}), }),
@@ -580,7 +580,7 @@ func TestServiceToServiceMap(t *testing.T) {
svcInfo.port != expectedInfo.port || svcInfo.port != expectedInfo.port ||
svcInfo.protocol != expectedInfo.protocol || svcInfo.protocol != expectedInfo.protocol ||
svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort || svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort ||
!sets.New[string](svcInfo.externalIPs...).Equal(sets.New[string](expectedInfo.externalIPs...)) || !reflect.DeepEqual(svcInfo.externalIPs, expectedInfo.externalIPs) ||
!sets.New[string](svcInfo.loadBalancerSourceRanges...).Equal(sets.New[string](expectedInfo.loadBalancerSourceRanges...)) || !sets.New[string](svcInfo.loadBalancerSourceRanges...).Equal(sets.New[string](expectedInfo.loadBalancerSourceRanges...)) ||
!reflect.DeepEqual(svcInfo.loadBalancerVIPs, expectedInfo.loadBalancerVIPs) { !reflect.DeepEqual(svcInfo.loadBalancerVIPs, expectedInfo.loadBalancerVIPs) {
t.Errorf("[%s] expected new[%v]to be %v, got %v", tc.desc, svcKey, expectedInfo, *svcInfo) t.Errorf("[%s] expected new[%v]to be %v, got %v", tc.desc, svcKey, expectedInfo, *svcInfo)
@@ -591,7 +591,7 @@ func TestServiceToServiceMap(t *testing.T) {
svcInfo.port != expectedInfo.port || svcInfo.port != expectedInfo.port ||
svcInfo.protocol != expectedInfo.protocol || svcInfo.protocol != expectedInfo.protocol ||
svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort || svcInfo.healthCheckNodePort != expectedInfo.healthCheckNodePort ||
!sets.New[string](svcInfo.externalIPs...).Equal(sets.New[string](expectedInfo.externalIPs...)) || !reflect.DeepEqual(svcInfo.externalIPs, expectedInfo.externalIPs) ||
!sets.New[string](svcInfo.loadBalancerSourceRanges...).Equal(sets.New[string](expectedInfo.loadBalancerSourceRanges...)) || !sets.New[string](svcInfo.loadBalancerSourceRanges...).Equal(sets.New[string](expectedInfo.loadBalancerSourceRanges...)) ||
!reflect.DeepEqual(svcInfo.loadBalancerVIPs, expectedInfo.loadBalancerVIPs) { !reflect.DeepEqual(svcInfo.loadBalancerVIPs, expectedInfo.loadBalancerVIPs) {
t.Errorf("expected new[%v]to be %v, got %v", svcKey, expectedInfo, *svcInfo) t.Errorf("expected new[%v]to be %v, got %v", svcKey, expectedInfo, *svcInfo)

View File

@@ -40,8 +40,8 @@ type ServicePort interface {
SessionAffinityType() v1.ServiceAffinity SessionAffinityType() v1.ServiceAffinity
// StickyMaxAgeSeconds returns service max connection age // StickyMaxAgeSeconds returns service max connection age
StickyMaxAgeSeconds() int StickyMaxAgeSeconds() int
// ExternalIPStrings returns service ExternalIPs as a string array. // ExternalIPs returns service ExternalIPs
ExternalIPStrings() []string ExternalIPs() []net.IP
// LoadBalancerVIPs returns service LoadBalancerIPs which are VIP mode // LoadBalancerVIPs returns service LoadBalancerIPs which are VIP mode
LoadBalancerVIPs() []net.IP LoadBalancerVIPs() []net.IP
// Protocol returns service protocol. // Protocol returns service protocol.
@@ -81,7 +81,7 @@ type BaseServicePortInfo struct {
loadBalancerVIPs []net.IP loadBalancerVIPs []net.IP
sessionAffinityType v1.ServiceAffinity sessionAffinityType v1.ServiceAffinity
stickyMaxAgeSeconds int stickyMaxAgeSeconds int
externalIPs []string externalIPs []net.IP
loadBalancerSourceRanges []string loadBalancerSourceRanges []string
healthCheckNodePort int healthCheckNodePort int
externalPolicyLocal bool externalPolicyLocal bool
@@ -136,8 +136,8 @@ func (bsvcPortInfo *BaseServicePortInfo) NodePort() int {
return bsvcPortInfo.nodePort return bsvcPortInfo.nodePort
} }
// ExternalIPStrings is part of ServicePort interface. // ExternalIPs is part of ServicePort interface.
func (bsvcPortInfo *BaseServicePortInfo) ExternalIPStrings() []string { func (bsvcPortInfo *BaseServicePortInfo) ExternalIPs() []net.IP {
return bsvcPortInfo.externalIPs return bsvcPortInfo.externalIPs
} }
@@ -216,20 +216,19 @@ func newBaseServiceInfo(service *v1.Service, ipFamily v1.IPFamily, port *v1.Serv
// prior to dual stack services, this was considered an error, but with dual stack // prior to dual stack services, this was considered an error, but with dual stack
// services, this is actually expected. Hence we downgraded from reporting by events // services, this is actually expected. Hence we downgraded from reporting by events
// to just log lines with high verbosity // to just log lines with high verbosity
ipFamilyMap := proxyutil.MapIPsByIPFamily(service.Spec.ExternalIPs) ipFamilyMap := proxyutil.MapIPsByIPFamily(service.Spec.ExternalIPs)
info.externalIPs = ipFamilyMap[ipFamily] info.externalIPs = ipFamilyMap[ipFamily]
// Log the IPs not matching the ipFamily // Log the IPs not matching the ipFamily
if ips, ok := ipFamilyMap[proxyutil.OtherIPFamily(ipFamily)]; ok && len(ips) > 0 { if ips, ok := ipFamilyMap[proxyutil.OtherIPFamily(ipFamily)]; ok && len(ips) > 0 {
klog.V(4).InfoS("Service change tracker ignored the following external IPs for given service as they don't match IP Family", klog.V(4).InfoS("Service change tracker ignored the following external IPs for given service as they don't match IP Family",
"ipFamily", ipFamily, "externalIPs", strings.Join(ips, ", "), "service", klog.KObj(service)) "ipFamily", ipFamily, "externalIPs", ips, "service", klog.KObj(service))
} }
ipFamilyMap = proxyutil.MapCIDRsByIPFamily(loadBalancerSourceRanges) cidrFamilyMap := proxyutil.MapCIDRsByIPFamily(loadBalancerSourceRanges)
info.loadBalancerSourceRanges = ipFamilyMap[ipFamily] info.loadBalancerSourceRanges = cidrFamilyMap[ipFamily]
// Log the CIDRs not matching the ipFamily // Log the CIDRs not matching the ipFamily
if cidrs, ok := ipFamilyMap[proxyutil.OtherIPFamily(ipFamily)]; ok && len(cidrs) > 0 { if cidrs, ok := cidrFamilyMap[proxyutil.OtherIPFamily(ipFamily)]; ok && len(cidrs) > 0 {
klog.V(4).InfoS("Service change tracker ignored the following load balancer source ranges for given Service as they don't match IP Family", klog.V(4).InfoS("Service change tracker ignored the following load balancer source ranges for given Service as they don't match IP Family",
"ipFamily", ipFamily, "loadBalancerSourceRanges", strings.Join(cidrs, ", "), "service", klog.KObj(service)) "ipFamily", ipFamily, "loadBalancerSourceRanges", strings.Join(cidrs, ", "), "service", klog.KObj(service))
} }

View File

@@ -180,19 +180,18 @@ func LogAndEmitIncorrectIPVersionEvent(recorder events.EventRecorder, fieldName,
} }
// MapIPsByIPFamily maps a slice of IPs to their respective IP families (v4 or v6) // MapIPsByIPFamily maps a slice of IPs to their respective IP families (v4 or v6)
func MapIPsByIPFamily(ipStrings []string) map[v1.IPFamily][]string { func MapIPsByIPFamily(ipStrings []string) map[v1.IPFamily][]net.IP {
ipFamilyMap := map[v1.IPFamily][]string{} ipFamilyMap := map[v1.IPFamily][]net.IP{}
for _, ipStr := range ipStrings { for _, ipStr := range ipStrings {
ip := netutils.ParseIPSloppy(ipStr) ip := netutils.ParseIPSloppy(ipStr)
// Handle only the valid IPs if ip != nil {
if ipFamily := GetIPFamilyFromIP(ip); ipFamily != v1.IPFamilyUnknown { // Since ip is parsed ok, GetIPFamilyFromIP will never return v1.IPFamilyUnknown
ipFamilyMap[ipFamily] = append(ipFamilyMap[ipFamily], ipStr) ipFamily := GetIPFamilyFromIP(ip)
ipFamilyMap[ipFamily] = append(ipFamilyMap[ipFamily], ip)
} else { } else {
// this function is called in multiple places. All of which // ExternalIPs may not be validated by the api-server.
// have sanitized data. Except the case of ExternalIPs which is // Specifically empty strings validation, which yields into a lot
// not validated by api-server. Specifically empty strings // of bad error logs.
// validation. Which yields into a lot of bad error logs.
// check for empty string
if len(strings.TrimSpace(ipStr)) != 0 { if len(strings.TrimSpace(ipStr)) != 0 {
klog.ErrorS(nil, "Skipping invalid IP", "ip", ipStr) klog.ErrorS(nil, "Skipping invalid IP", "ip", ipStr)
} }

View File

@@ -338,10 +338,18 @@ func TestMapIPsByIPFamily(t *testing.T) {
ipMap := MapIPsByIPFamily(testcase.ipString) ipMap := MapIPsByIPFamily(testcase.ipString)
if !reflect.DeepEqual(testcase.expectCorrect, ipMap[ipFamily]) { var ipStr []string
for _, ip := range ipMap[ipFamily] {
ipStr = append(ipStr, ip.String())
}
if !reflect.DeepEqual(testcase.expectCorrect, ipStr) {
t.Errorf("Test %v failed: expected %v, got %v", testcase.desc, testcase.expectCorrect, ipMap[ipFamily]) t.Errorf("Test %v failed: expected %v, got %v", testcase.desc, testcase.expectCorrect, ipMap[ipFamily])
} }
if !reflect.DeepEqual(testcase.expectIncorrect, ipMap[otherIPFamily]) { ipStr = nil
for _, ip := range ipMap[otherIPFamily] {
ipStr = append(ipStr, ip.String())
}
if !reflect.DeepEqual(testcase.expectIncorrect, ipStr) {
t.Errorf("Test %v failed: expected %v, got %v", testcase.desc, testcase.expectIncorrect, ipMap[otherIPFamily]) t.Errorf("Test %v failed: expected %v, got %v", testcase.desc, testcase.expectIncorrect, ipMap[otherIPFamily])
} }
}) })