| //go:build linux |
| // +build linux |
| |
| /* |
| 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 ipvs |
| |
| import ( |
| "k8s.io/apimachinery/pkg/util/sets" |
| utilversion "k8s.io/apimachinery/pkg/util/version" |
| utilipset "k8s.io/kubernetes/pkg/proxy/ipvs/ipset" |
| |
| "fmt" |
| "strings" |
| |
| "k8s.io/klog/v2" |
| ) |
| |
| const ( |
| // MinIPSetCheckVersion is the min ipset version we need. IPv6 is supported in ipset 6.x |
| MinIPSetCheckVersion = "6.0" |
| |
| kubeLoopBackIPSetComment = "Kubernetes endpoints dst ip:port, source ip for solving hairpin purpose" |
| kubeLoopBackIPSet = "KUBE-LOOP-BACK" |
| |
| kubeClusterIPSetComment = "Kubernetes service cluster ip + port for masquerade purpose" |
| kubeClusterIPSet = "KUBE-CLUSTER-IP" |
| |
| kubeExternalIPSetComment = "Kubernetes service external ip + port for masquerade and filter purpose" |
| kubeExternalIPSet = "KUBE-EXTERNAL-IP" |
| |
| kubeExternalIPLocalSetComment = "Kubernetes service external ip + port with externalTrafficPolicy=local" |
| kubeExternalIPLocalSet = "KUBE-EXTERNAL-IP-LOCAL" |
| |
| kubeLoadBalancerSetComment = "Kubernetes service lb portal" |
| kubeLoadBalancerSet = "KUBE-LOAD-BALANCER" |
| |
| kubeLoadBalancerLocalSetComment = "Kubernetes service load balancer ip + port with externalTrafficPolicy=local" |
| kubeLoadBalancerLocalSet = "KUBE-LOAD-BALANCER-LOCAL" |
| |
| kubeLoadBalancerFWSetComment = "Kubernetes service load balancer ip + port for load balancer with sourceRange" |
| kubeLoadBalancerFWSet = "KUBE-LOAD-BALANCER-FW" |
| |
| kubeLoadBalancerSourceIPSetComment = "Kubernetes service load balancer ip + port + source IP for packet filter purpose" |
| kubeLoadBalancerSourceIPSet = "KUBE-LOAD-BALANCER-SOURCE-IP" |
| |
| kubeLoadBalancerSourceCIDRSetComment = "Kubernetes service load balancer ip + port + source cidr for packet filter purpose" |
| kubeLoadBalancerSourceCIDRSet = "KUBE-LOAD-BALANCER-SOURCE-CIDR" |
| |
| kubeNodePortSetTCPComment = "Kubernetes nodeport TCP port for masquerade purpose" |
| kubeNodePortSetTCP = "KUBE-NODE-PORT-TCP" |
| |
| kubeNodePortLocalSetTCPComment = "Kubernetes nodeport TCP port with externalTrafficPolicy=local" |
| kubeNodePortLocalSetTCP = "KUBE-NODE-PORT-LOCAL-TCP" |
| |
| kubeNodePortSetUDPComment = "Kubernetes nodeport UDP port for masquerade purpose" |
| kubeNodePortSetUDP = "KUBE-NODE-PORT-UDP" |
| |
| kubeNodePortLocalSetUDPComment = "Kubernetes nodeport UDP port with externalTrafficPolicy=local" |
| kubeNodePortLocalSetUDP = "KUBE-NODE-PORT-LOCAL-UDP" |
| |
| kubeNodePortSetSCTPComment = "Kubernetes nodeport SCTP port for masquerade purpose with type 'hash ip:port'" |
| kubeNodePortSetSCTP = "KUBE-NODE-PORT-SCTP-HASH" |
| |
| kubeNodePortLocalSetSCTPComment = "Kubernetes nodeport SCTP port with externalTrafficPolicy=local with type 'hash ip:port'" |
| kubeNodePortLocalSetSCTP = "KUBE-NODE-PORT-LOCAL-SCTP-HASH" |
| |
| kubeHealthCheckNodePortSetComment = "Kubernetes health check node port" |
| kubeHealthCheckNodePortSet = "KUBE-HEALTH-CHECK-NODE-PORT" |
| |
| kubeIPVSSetComment = "Addresses on the ipvs interface" |
| kubeIPVSSet = "KUBE-IPVS-IPS" |
| ) |
| |
| // IPSetVersioner can query the current ipset version. |
| type IPSetVersioner interface { |
| // returns "X.Y" |
| GetVersion() (string, error) |
| } |
| |
| // IPSet wraps util/ipset which is used by IPVS proxier. |
| type IPSet struct { |
| utilipset.IPSet |
| // activeEntries is the current active entries of the ipset. |
| activeEntries sets.Set[string] |
| // handle is the util ipset interface handle. |
| handle utilipset.Interface |
| } |
| |
| // NewIPSet initialize a new IPSet struct |
| func NewIPSet(handle utilipset.Interface, name string, setType utilipset.Type, isIPv6 bool, comment string) *IPSet { |
| hashFamily := utilipset.ProtocolFamilyIPV4 |
| if isIPv6 { |
| hashFamily = utilipset.ProtocolFamilyIPV6 |
| // In dual-stack both ipv4 and ipv6 ipset's can co-exist. To |
| // ensure unique names the prefix for ipv6 is changed from |
| // "KUBE-" to "KUBE-6-". The "KUBE-" prefix is kept for |
| // backward compatibility. The maximum name length of an ipset |
| // is 31 characters which must be taken into account. The |
| // ipv4 names are not altered to minimize the risk for |
| // problems on upgrades. |
| if strings.HasPrefix(name, "KUBE-") { |
| name = strings.Replace(name, "KUBE-", "KUBE-6-", 1) |
| if len(name) > 31 { |
| klog.InfoS("Ipset name truncated", "ipSetName", name, "truncatedName", name[:31]) |
| name = name[:31] |
| } |
| } |
| } |
| set := &IPSet{ |
| IPSet: utilipset.IPSet{ |
| Name: name, |
| SetType: setType, |
| HashFamily: hashFamily, |
| Comment: comment, |
| }, |
| activeEntries: sets.New[string](), |
| handle: handle, |
| } |
| return set |
| } |
| |
| func (set *IPSet) validateEntry(entry *utilipset.Entry) bool { |
| return entry.Validate(&set.IPSet) |
| } |
| |
| func (set *IPSet) isEmpty() bool { |
| return set.activeEntries.Len() == 0 |
| } |
| |
| func (set *IPSet) getComment() string { |
| return fmt.Sprintf("\"%s\"", set.Comment) |
| } |
| |
| func (set *IPSet) resetEntries() { |
| set.activeEntries = sets.New[string]() |
| } |
| |
| func (set *IPSet) syncIPSetEntries() { |
| appliedEntries, err := set.handle.ListEntries(set.Name) |
| if err != nil { |
| klog.ErrorS(err, "Failed to list ip set entries") |
| return |
| } |
| |
| // currentIPSetEntries represents Endpoints watched from API Server. |
| currentIPSetEntries := sets.New[string]() |
| for _, appliedEntry := range appliedEntries { |
| currentIPSetEntries.Insert(appliedEntry) |
| } |
| |
| if !set.activeEntries.Equal(currentIPSetEntries) { |
| // Clean legacy entries |
| for _, entry := range currentIPSetEntries.Difference(set.activeEntries).UnsortedList() { |
| if err := set.handle.DelEntry(entry, set.Name); err != nil { |
| if !utilipset.IsNotFoundError(err) { |
| klog.ErrorS(err, "Failed to delete ip set entry from ip set", "ipSetEntry", entry, "ipSet", set.Name) |
| } |
| } else { |
| klog.V(3).InfoS("Successfully deleted legacy ip set entry from ip set", "ipSetEntry", entry, "ipSet", set.Name) |
| } |
| } |
| // Create active entries |
| for _, entry := range set.activeEntries.Difference(currentIPSetEntries).UnsortedList() { |
| if err := set.handle.AddEntry(entry, &set.IPSet, true); err != nil { |
| klog.ErrorS(err, "Failed to add ip set entry to ip set", "ipSetEntry", entry, "ipSet", set.Name) |
| } else { |
| klog.V(3).InfoS("Successfully added ip set entry to ip set", "ipSetEntry", entry, "ipSet", set.Name) |
| } |
| } |
| } |
| } |
| |
| func ensureIPSet(set *IPSet) error { |
| if err := set.handle.CreateSet(&set.IPSet, true); err != nil { |
| klog.ErrorS(err, "Failed to make sure existence of ip set", "ipSet", set) |
| return err |
| } |
| return nil |
| } |
| |
| // checkMinVersion checks if ipset current version satisfies required min version |
| func checkMinVersion(vstring string) bool { |
| version, err := utilversion.ParseGeneric(vstring) |
| if err != nil { |
| klog.ErrorS(err, "Got invalid version string", "versionString", vstring) |
| return false |
| } |
| |
| minVersion, err := utilversion.ParseGeneric(MinIPSetCheckVersion) |
| if err != nil { |
| klog.ErrorS(err, "Got invalid version string", "versionString", MinIPSetCheckVersion) |
| return false |
| } |
| return !version.LessThan(minVersion) |
| } |