2017-08-16 03:17:36 -04:00
|
|
|
/*
|
|
|
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
|
limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package util
|
|
|
|
|
|
|
|
|
|
import (
|
2018-01-16 21:23:33 -05:00
|
|
|
"fmt"
|
2017-08-16 03:17:36 -04:00
|
|
|
"net"
|
2026-02-15 10:53:17 -05:00
|
|
|
"strconv"
|
2021-09-23 16:08:09 -04:00
|
|
|
"strings"
|
2025-01-08 07:00:18 -05:00
|
|
|
"time"
|
2017-08-16 03:17:36 -04:00
|
|
|
|
2019-08-21 14:33:41 -04:00
|
|
|
v1 "k8s.io/api/core/v1"
|
2018-01-16 21:23:33 -05:00
|
|
|
"k8s.io/apimachinery/pkg/util/sets"
|
2021-11-12 10:52:27 -05:00
|
|
|
utilsysctl "k8s.io/component-helpers/node/util/sysctl"
|
2024-05-17 09:11:39 -04:00
|
|
|
"k8s.io/klog/v2"
|
|
|
|
|
"k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
2021-08-19 19:16:14 -04:00
|
|
|
netutils "k8s.io/utils/net"
|
2017-08-16 03:17:36 -04:00
|
|
|
)
|
|
|
|
|
|
2018-01-16 21:23:33 -05:00
|
|
|
const (
|
2019-05-09 11:34:56 -04:00
|
|
|
// IPv4ZeroCIDR is the CIDR block for the whole IPv4 address space
|
2018-01-16 21:23:33 -05:00
|
|
|
IPv4ZeroCIDR = "0.0.0.0/0"
|
2019-05-09 11:34:56 -04:00
|
|
|
|
|
|
|
|
// IPv6ZeroCIDR is the CIDR block for the whole IPv6 address space
|
2018-01-16 21:23:33 -05:00
|
|
|
IPv6ZeroCIDR = "::/0"
|
2025-01-08 07:00:18 -05:00
|
|
|
|
|
|
|
|
// FullSyncPeriod is iptables and nftables proxier full sync period
|
|
|
|
|
FullSyncPeriod = 1 * time.Hour
|
2018-01-16 21:23:33 -05:00
|
|
|
)
|
|
|
|
|
|
2019-05-09 11:34:56 -04:00
|
|
|
// IsZeroCIDR checks whether the input CIDR string is either
|
|
|
|
|
// the IPv4 or IPv6 zero CIDR
|
2025-07-04 03:58:06 -04:00
|
|
|
func IsZeroCIDR(cidr *net.IPNet) bool {
|
|
|
|
|
if cidr == nil {
|
|
|
|
|
return false
|
2018-01-16 21:23:33 -05:00
|
|
|
}
|
2025-07-04 03:58:06 -04:00
|
|
|
prefixLen, _ := cidr.Mask.Size()
|
|
|
|
|
return prefixLen == 0
|
2018-01-16 21:23:33 -05:00
|
|
|
}
|
|
|
|
|
|
2019-05-09 11:34:56 -04:00
|
|
|
// ShouldSkipService checks if a given service should skip proxying
|
2020-06-30 11:35:38 -04:00
|
|
|
func ShouldSkipService(service *v1.Service) bool {
|
2017-08-16 03:17:36 -04:00
|
|
|
// if ClusterIP is "None" or empty, skip proxying
|
|
|
|
|
if !helper.IsServiceIPSet(service) {
|
2021-09-20 16:54:35 -04:00
|
|
|
klog.V(3).InfoS("Skipping service due to cluster IP", "service", klog.KObj(service), "clusterIP", service.Spec.ClusterIP)
|
2017-08-16 03:17:36 -04:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
// Even if ClusterIP is set, ServiceTypeExternalName services don't get proxied
|
2018-08-15 09:51:19 -04:00
|
|
|
if service.Spec.Type == v1.ServiceTypeExternalName {
|
2021-09-20 16:54:35 -04:00
|
|
|
klog.V(3).InfoS("Skipping service due to Type=ExternalName", "service", klog.KObj(service))
|
2017-08-16 03:17:36 -04:00
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
2018-01-16 21:23:33 -05:00
|
|
|
|
2021-08-21 02:44:27 -04:00
|
|
|
// AddressSet validates the addresses in the slice using the "isValid" function.
|
|
|
|
|
// Addresses that pass the validation are returned as a string Set.
|
2023-02-19 12:08:57 -05:00
|
|
|
func AddressSet(isValid func(ip net.IP) bool, addrs []net.Addr) sets.Set[string] {
|
|
|
|
|
ips := sets.New[string]()
|
2021-08-21 02:44:27 -04:00
|
|
|
for _, a := range addrs {
|
|
|
|
|
var ip net.IP
|
|
|
|
|
switch v := a.(type) {
|
|
|
|
|
case *net.IPAddr:
|
|
|
|
|
ip = v.IP
|
|
|
|
|
case *net.IPNet:
|
|
|
|
|
ip = v.IP
|
|
|
|
|
default:
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if isValid(ip) {
|
|
|
|
|
ips.Insert(ip.String())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ips
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-17 02:13:51 -05:00
|
|
|
// MapIPsByIPFamily maps a slice of IPs to their respective IP families (v4 or v6)
|
2024-01-07 04:48:39 -05:00
|
|
|
func MapIPsByIPFamily(ipStrings []string) map[v1.IPFamily][]net.IP {
|
|
|
|
|
ipFamilyMap := map[v1.IPFamily][]net.IP{}
|
2024-01-07 02:33:30 -05:00
|
|
|
for _, ipStr := range ipStrings {
|
|
|
|
|
ip := netutils.ParseIPSloppy(ipStr)
|
2024-01-07 04:48:39 -05:00
|
|
|
if ip != nil {
|
|
|
|
|
// Since ip is parsed ok, GetIPFamilyFromIP will never return v1.IPFamilyUnknown
|
|
|
|
|
ipFamily := GetIPFamilyFromIP(ip)
|
|
|
|
|
ipFamilyMap[ipFamily] = append(ipFamilyMap[ipFamily], ip)
|
2020-11-20 17:22:55 -05:00
|
|
|
} else {
|
2024-01-07 04:48:39 -05:00
|
|
|
// ExternalIPs may not be validated by the api-server.
|
|
|
|
|
// Specifically empty strings validation, which yields into a lot
|
|
|
|
|
// of bad error logs.
|
2024-01-07 02:33:30 -05:00
|
|
|
if len(strings.TrimSpace(ipStr)) != 0 {
|
|
|
|
|
klog.ErrorS(nil, "Skipping invalid IP", "ip", ipStr)
|
2021-09-23 16:08:09 -04:00
|
|
|
}
|
2020-11-17 02:13:51 -05:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ipFamilyMap
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-09 03:08:30 -05:00
|
|
|
// MapCIDRsByIPFamily maps a slice of CIDRs to their respective IP families (v4 or v6)
|
|
|
|
|
func MapCIDRsByIPFamily(cidrsStrings []string) map[v1.IPFamily][]*net.IPNet {
|
|
|
|
|
ipFamilyMap := map[v1.IPFamily][]*net.IPNet{}
|
|
|
|
|
for _, cidrStrUntrimmed := range cidrsStrings {
|
|
|
|
|
cidrStr := strings.TrimSpace(cidrStrUntrimmed)
|
|
|
|
|
_, cidr, err := netutils.ParseCIDRSloppy(cidrStr)
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Ignore empty strings. Same as in MapIPsByIPFamily
|
|
|
|
|
if len(cidrStr) != 0 {
|
|
|
|
|
klog.ErrorS(err, "Invalid CIDR ignored", "CIDR", cidrStr)
|
|
|
|
|
}
|
|
|
|
|
continue
|
2020-11-17 02:13:51 -05:00
|
|
|
}
|
2024-01-09 03:08:30 -05:00
|
|
|
// since we just succefully parsed the CIDR, IPFamilyOfCIDR will never return "IPFamilyUnknown"
|
|
|
|
|
ipFamily := convertToV1IPFamily(netutils.IPFamilyOfCIDR(cidr))
|
|
|
|
|
ipFamilyMap[ipFamily] = append(ipFamilyMap[ipFamily], cidr)
|
2020-11-13 18:17:30 -05:00
|
|
|
}
|
|
|
|
|
return ipFamilyMap
|
2019-02-02 02:01:21 -05:00
|
|
|
}
|
|
|
|
|
|
2023-07-06 04:48:52 -04:00
|
|
|
// GetIPFamilyFromIP Returns the IP family of ipStr, or IPFamilyUnknown if ipStr can't be parsed as an IP
|
2024-01-07 02:33:30 -05:00
|
|
|
func GetIPFamilyFromIP(ip net.IP) v1.IPFamily {
|
|
|
|
|
return convertToV1IPFamily(netutils.IPFamilyOf(ip))
|
2020-11-20 17:22:55 -05:00
|
|
|
}
|
|
|
|
|
|
2023-07-06 04:48:52 -04:00
|
|
|
// Convert netutils.IPFamily to v1.IPFamily
|
|
|
|
|
func convertToV1IPFamily(ipFamily netutils.IPFamily) v1.IPFamily {
|
|
|
|
|
switch ipFamily {
|
|
|
|
|
case netutils.IPv4:
|
|
|
|
|
return v1.IPv4Protocol
|
|
|
|
|
case netutils.IPv6:
|
2023-06-15 05:22:11 -04:00
|
|
|
return v1.IPv6Protocol
|
2020-11-20 17:22:55 -05:00
|
|
|
}
|
2023-07-06 04:48:52 -04:00
|
|
|
|
|
|
|
|
return v1.IPFamilyUnknown
|
2020-11-17 02:13:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// OtherIPFamily returns the other ip family
|
|
|
|
|
func OtherIPFamily(ipFamily v1.IPFamily) v1.IPFamily {
|
|
|
|
|
if ipFamily == v1.IPv6Protocol {
|
|
|
|
|
return v1.IPv4Protocol
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return v1.IPv6Protocol
|
|
|
|
|
}
|
2019-02-02 02:01:21 -05:00
|
|
|
|
2019-04-09 03:30:11 -04:00
|
|
|
// AppendPortIfNeeded appends the given port to IP address unless it is already in
|
|
|
|
|
// "ipv4:port" or "[ipv6]:port" format.
|
|
|
|
|
func AppendPortIfNeeded(addr string, port int32) string {
|
|
|
|
|
// Return if address is already in "ipv4:port" or "[ipv6]:port" format.
|
|
|
|
|
if _, _, err := net.SplitHostPort(addr); err == nil {
|
|
|
|
|
return addr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simply return for invalid case. This should be caught by validation instead.
|
2021-08-19 19:16:14 -04:00
|
|
|
ip := netutils.ParseIPSloppy(addr)
|
2019-04-09 03:30:11 -04:00
|
|
|
if ip == nil {
|
|
|
|
|
return addr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append port to address.
|
2026-02-15 10:53:17 -05:00
|
|
|
return net.JoinHostPort(addr, strconv.Itoa(int(port)))
|
2019-04-09 03:30:11 -04:00
|
|
|
}
|
2019-08-21 14:33:41 -04:00
|
|
|
|
2020-04-10 11:47:03 -04:00
|
|
|
// EnsureSysctl sets a kernel sysctl to a given numeric value.
|
|
|
|
|
func EnsureSysctl(sysctl utilsysctl.Interface, name string, newVal int) error {
|
|
|
|
|
if oldVal, _ := sysctl.GetSysctl(name); oldVal != newVal {
|
|
|
|
|
if err := sysctl.SetSysctl(name, newVal); err != nil {
|
|
|
|
|
return fmt.Errorf("can't set sysctl %s to %d: %v", name, newVal, err)
|
|
|
|
|
}
|
2021-09-20 16:54:35 -04:00
|
|
|
klog.V(1).InfoS("Changed sysctl", "name", name, "before", oldVal, "after", newVal)
|
2020-04-10 11:47:03 -04:00
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-06-09 17:30:40 -04:00
|
|
|
|
dual stack services (#91824)
* api: structure change
* api: defaulting, conversion, and validation
* [FIX] validation: auto remove second ip/family when service changes to SingleStack
* [FIX] api: defaulting, conversion, and validation
* api-server: clusterIPs alloc, printers, storage and strategy
* [FIX] clusterIPs default on read
* alloc: auto remove second ip/family when service changes to SingleStack
* api-server: repair loop handling for clusterIPs
* api-server: force kubernetes default service into single stack
* api-server: tie dualstack feature flag with endpoint feature flag
* controller-manager: feature flag, endpoint, and endpointSlice controllers handling multi family service
* [FIX] controller-manager: feature flag, endpoint, and endpointSlicecontrollers handling multi family service
* kube-proxy: feature-flag, utils, proxier, and meta proxier
* [FIX] kubeproxy: call both proxier at the same time
* kubenet: remove forced pod IP sorting
* kubectl: modify describe to include ClusterIPs, IPFamilies, and IPFamilyPolicy
* e2e: fix tests that depends on IPFamily field AND add dual stack tests
* e2e: fix expected error message for ClusterIP immutability
* add integration tests for dualstack
the third phase of dual stack is a very complex change in the API,
basically it introduces Dual Stack services. Main changes are:
- It pluralizes the Service IPFamily field to IPFamilies,
and removes the singular field.
- It introduces a new field IPFamilyPolicyType that can take
3 values to express the "dual-stack(mad)ness" of the cluster:
SingleStack, PreferDualStack and RequireDualStack
- It pluralizes ClusterIP to ClusterIPs.
The goal is to add coverage to the services API operations,
taking into account the 6 different modes a cluster can have:
- single stack: IP4 or IPv6 (as of today)
- dual stack: IPv4 only, IPv6 only, IPv4 - IPv6, IPv6 - IPv4
* [FIX] add integration tests for dualstack
* generated data
* generated files
Co-authored-by: Antonio Ojea <aojea@redhat.com>
2020-10-26 16:15:59 -04:00
|
|
|
// GetClusterIPByFamily returns a service clusterip by family
|
|
|
|
|
func GetClusterIPByFamily(ipFamily v1.IPFamily, service *v1.Service) string {
|
|
|
|
|
// allowing skew
|
|
|
|
|
if len(service.Spec.IPFamilies) == 0 {
|
|
|
|
|
if len(service.Spec.ClusterIP) == 0 || service.Spec.ClusterIP == v1.ClusterIPNone {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
IsIPv6Family := (ipFamily == v1.IPv6Protocol)
|
2021-08-19 19:16:14 -04:00
|
|
|
if IsIPv6Family == netutils.IsIPv6String(service.Spec.ClusterIP) {
|
dual stack services (#91824)
* api: structure change
* api: defaulting, conversion, and validation
* [FIX] validation: auto remove second ip/family when service changes to SingleStack
* [FIX] api: defaulting, conversion, and validation
* api-server: clusterIPs alloc, printers, storage and strategy
* [FIX] clusterIPs default on read
* alloc: auto remove second ip/family when service changes to SingleStack
* api-server: repair loop handling for clusterIPs
* api-server: force kubernetes default service into single stack
* api-server: tie dualstack feature flag with endpoint feature flag
* controller-manager: feature flag, endpoint, and endpointSlice controllers handling multi family service
* [FIX] controller-manager: feature flag, endpoint, and endpointSlicecontrollers handling multi family service
* kube-proxy: feature-flag, utils, proxier, and meta proxier
* [FIX] kubeproxy: call both proxier at the same time
* kubenet: remove forced pod IP sorting
* kubectl: modify describe to include ClusterIPs, IPFamilies, and IPFamilyPolicy
* e2e: fix tests that depends on IPFamily field AND add dual stack tests
* e2e: fix expected error message for ClusterIP immutability
* add integration tests for dualstack
the third phase of dual stack is a very complex change in the API,
basically it introduces Dual Stack services. Main changes are:
- It pluralizes the Service IPFamily field to IPFamilies,
and removes the singular field.
- It introduces a new field IPFamilyPolicyType that can take
3 values to express the "dual-stack(mad)ness" of the cluster:
SingleStack, PreferDualStack and RequireDualStack
- It pluralizes ClusterIP to ClusterIPs.
The goal is to add coverage to the services API operations,
taking into account the 6 different modes a cluster can have:
- single stack: IP4 or IPv6 (as of today)
- dual stack: IPv4 only, IPv6 only, IPv4 - IPv6, IPv6 - IPv4
* [FIX] add integration tests for dualstack
* generated data
* generated files
Co-authored-by: Antonio Ojea <aojea@redhat.com>
2020-10-26 16:15:59 -04:00
|
|
|
return service.Spec.ClusterIP
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for idx, family := range service.Spec.IPFamilies {
|
|
|
|
|
if family == ipFamily {
|
|
|
|
|
if idx < len(service.Spec.ClusterIPs) {
|
|
|
|
|
return service.Spec.ClusterIPs[idx]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ""
|
|
|
|
|
}
|
2021-01-19 08:09:03 -05:00
|
|
|
|
2023-07-06 04:48:52 -04:00
|
|
|
func IsVIPMode(ing v1.LoadBalancerIngress) bool {
|
|
|
|
|
if ing.IPMode == nil {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return *ing.IPMode == v1.LoadBalancerIPModeVIP
|
|
|
|
|
}
|