From 9f4edccb97bd3df25eb88e4122e34f75ea3ca078 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Fri, 19 Sep 2025 17:05:46 -0400 Subject: [PATCH] Move proxier supportedness-checking code into separate files. (No code changes, just moving between files.) --- pkg/proxy/ipvs/proxier.go | 91 ----------------------- pkg/proxy/ipvs/proxier_test.go | 58 --------------- pkg/proxy/ipvs/supported.go | 120 +++++++++++++++++++++++++++++++ pkg/proxy/ipvs/supported_test.go | 85 ++++++++++++++++++++++ pkg/proxy/nftables/proxier.go | 49 ------------- pkg/proxy/nftables/supported.go | 76 ++++++++++++++++++++ pkg/proxy/winkernel/proxier.go | 72 ------------------- pkg/proxy/winkernel/supported.go | 102 ++++++++++++++++++++++++++ 8 files changed, 383 insertions(+), 270 deletions(-) create mode 100644 pkg/proxy/ipvs/supported.go create mode 100644 pkg/proxy/ipvs/supported_test.go create mode 100644 pkg/proxy/nftables/supported.go create mode 100644 pkg/proxy/winkernel/supported.go diff --git a/pkg/proxy/ipvs/proxier.go b/pkg/proxy/ipvs/proxier.go index a9ec5d1b109..57c61454d2a 100644 --- a/pkg/proxy/ipvs/proxier.go +++ b/pkg/proxy/ipvs/proxier.go @@ -563,97 +563,6 @@ func getFirstColumn(r io.Reader) ([]string, error) { return words, nil } -// CanUseIPVSProxier checks if we can use the ipvs Proxier. -// The ipset version and the scheduler are checked. If any virtual servers (VS) -// already exist with the configured scheduler, we just return. Otherwise -// we check if a dummy VS can be configured with the configured scheduler. -// Kernel modules will be loaded automatically if necessary. -func CanUseIPVSProxier(ctx context.Context, ipvs utilipvs.Interface, ipsetver IPSetVersioner, scheduler string) error { - logger := klog.FromContext(ctx) - // BUG: https://github.com/moby/ipvs/issues/27 - // If ipvs is not compiled into the kernel no error is returned and handle==nil. - // This in turn causes ipvs.GetVirtualServers and ipvs.AddVirtualServer - // to return ok (err==nil). If/when this bug is fixed parameter "ipvs" will be nil - // if ipvs is not supported by the kernel. Until then a re-read work-around is used. - if ipvs == nil { - return fmt.Errorf("ipvs not supported by the kernel") - } - - // Check ipset version - versionString, err := ipsetver.GetVersion() - if err != nil { - return fmt.Errorf("error getting ipset version, error: %w", err) - } - if !checkMinVersion(versionString) { - return fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion) - } - - if scheduler == "" { - scheduler = defaultScheduler - } - - // If any virtual server (VS) using the scheduler exist we skip the checks. - vservers, err := ipvs.GetVirtualServers() - if err != nil { - logger.Error(err, "Can't read the ipvs") - return err - } - logger.V(5).Info("Virtual Servers", "count", len(vservers)) - if len(vservers) > 0 { - // This is most likely a kube-proxy re-start. We know that ipvs works - // and if any VS uses the configured scheduler, we are done. - for _, vs := range vservers { - if vs.Scheduler == scheduler { - logger.V(5).Info("VS exist, Skipping checks") - return nil - } - } - logger.V(5).Info("No existing VS uses the configured scheduler", "scheduler", scheduler) - } - - // Try to insert a dummy VS with the passed scheduler. - // We should use a VIP address that is not used on the node. - // An address "198.51.100.0" from the TEST-NET-2 rage in https://datatracker.ietf.org/doc/html/rfc5737 - // is used. These addresses are reserved for documentation. If the user is using - // this address for a VS anyway we *will* mess up, but that would be an invalid configuration. - // If the user have configured the address to an interface on the node (but not a VS) - // then traffic will temporary be routed to ipvs during the probe and dropped. - // The later case is also and invalid configuration, but the traffic impact will be minor. - // This should not be a problem if users honors reserved addresses, but cut/paste - // from documentation is not unheard of, so the restriction to not use the TEST-NET-2 range - // must be documented. - vs := utilipvs.VirtualServer{ - Address: netutils.ParseIPSloppy("198.51.100.0"), - Protocol: "TCP", - Port: 20000, - Scheduler: scheduler, - } - if err := ipvs.AddVirtualServer(&vs); err != nil { - logger.Error(err, "Could not create dummy VS", "scheduler", scheduler) - return err - } - - // To overcome the BUG described above we check that the VS is *really* added. - vservers, err = ipvs.GetVirtualServers() - if err != nil { - logger.Error(err, "ipvs.GetVirtualServers") - return err - } - logger.V(5).Info("Virtual Servers after adding dummy", "count", len(vservers)) - if len(vservers) == 0 { - logger.Info("Dummy VS not created", "scheduler", scheduler) - return fmt.Errorf("ipvs not supported") // This is a BUG work-around - } - logger.V(5).Info("Dummy VS created", "vs", vs) - - if err := ipvs.DeleteVirtualServer(&vs); err != nil { - logger.Error(err, "Could not delete dummy VS") - return err - } - - return nil -} - // CleanupIptablesLeftovers removes all iptables rules and chains created by the Proxier // It returns true if an error was encountered. Errors are logged. func cleanupIptablesLeftovers(ctx context.Context, ipt utiliptables.Interface) (encounteredError bool) { diff --git a/pkg/proxy/ipvs/proxier_test.go b/pkg/proxy/ipvs/proxier_test.go index 28c5c7f54b7..75b30d3c2d7 100644 --- a/pkg/proxy/ipvs/proxier_test.go +++ b/pkg/proxy/ipvs/proxier_test.go @@ -349,64 +349,6 @@ func TestCleanupLeftovers(t *testing.T) { // FIXME: check ipvs and ipset state } -func TestCanUseIPVSProxier(t *testing.T) { - _, ctx := ktesting.NewTestContext(t) - testCases := []struct { - name string - scheduler string - ipsetVersion string - ipsetErr error - ipvsErr string - ok bool - }{ - { - name: "happy days", - ipsetVersion: MinIPSetCheckVersion, - ok: true, - }, - { - name: "ipset error", - scheduler: "", - ipsetVersion: MinIPSetCheckVersion, - ipsetErr: fmt.Errorf("oops"), - ok: false, - }, - { - name: "ipset version too low", - scheduler: "rr", - ipsetVersion: "4.3.0", - ok: false, - }, - { - name: "GetVirtualServers fail", - ipsetVersion: MinIPSetCheckVersion, - ipvsErr: "GetVirtualServers", - ok: false, - }, - { - name: "AddVirtualServer fail", - ipsetVersion: MinIPSetCheckVersion, - ipvsErr: "AddVirtualServer", - ok: false, - }, - { - name: "DeleteVirtualServer fail", - ipsetVersion: MinIPSetCheckVersion, - ipvsErr: "DeleteVirtualServer", - ok: false, - }, - } - - for _, tc := range testCases { - ipvs := &fakeIpvs{tc.ipvsErr, false} - versioner := &fakeIPSetVersioner{version: tc.ipsetVersion, err: tc.ipsetErr} - err := CanUseIPVSProxier(ctx, ipvs, versioner, tc.scheduler) - if (err == nil) != tc.ok { - t.Errorf("Case [%s], expect %v, got err: %v", tc.name, tc.ok, err) - } - } -} - func TestGetNodeIPs(t *testing.T) { testCases := []struct { isIPv6 bool diff --git a/pkg/proxy/ipvs/supported.go b/pkg/proxy/ipvs/supported.go new file mode 100644 index 00000000000..daa7c952531 --- /dev/null +++ b/pkg/proxy/ipvs/supported.go @@ -0,0 +1,120 @@ +//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 ( + "context" + "fmt" + + "k8s.io/klog/v2" + utilipvs "k8s.io/kubernetes/pkg/proxy/ipvs/util" + netutils "k8s.io/utils/net" +) + +// CanUseIPVSProxier checks if we can use the ipvs Proxier. +// The ipset version and the scheduler are checked. If any virtual servers (VS) +// already exist with the configured scheduler, we just return. Otherwise +// we check if a dummy VS can be configured with the configured scheduler. +// Kernel modules will be loaded automatically if necessary. +func CanUseIPVSProxier(ctx context.Context, ipvs utilipvs.Interface, ipsetver IPSetVersioner, scheduler string) error { + logger := klog.FromContext(ctx) + // BUG: https://github.com/moby/ipvs/issues/27 + // If ipvs is not compiled into the kernel no error is returned and handle==nil. + // This in turn causes ipvs.GetVirtualServers and ipvs.AddVirtualServer + // to return ok (err==nil). If/when this bug is fixed parameter "ipvs" will be nil + // if ipvs is not supported by the kernel. Until then a re-read work-around is used. + if ipvs == nil { + return fmt.Errorf("ipvs not supported by the kernel") + } + + // Check ipset version + versionString, err := ipsetver.GetVersion() + if err != nil { + return fmt.Errorf("error getting ipset version, error: %w", err) + } + if !checkMinVersion(versionString) { + return fmt.Errorf("ipset version: %s is less than min required version: %s", versionString, MinIPSetCheckVersion) + } + + if scheduler == "" { + scheduler = defaultScheduler + } + + // If any virtual server (VS) using the scheduler exist we skip the checks. + vservers, err := ipvs.GetVirtualServers() + if err != nil { + logger.Error(err, "Can't read the ipvs") + return err + } + logger.V(5).Info("Virtual Servers", "count", len(vservers)) + if len(vservers) > 0 { + // This is most likely a kube-proxy re-start. We know that ipvs works + // and if any VS uses the configured scheduler, we are done. + for _, vs := range vservers { + if vs.Scheduler == scheduler { + logger.V(5).Info("VS exist, Skipping checks") + return nil + } + } + logger.V(5).Info("No existing VS uses the configured scheduler", "scheduler", scheduler) + } + + // Try to insert a dummy VS with the passed scheduler. + // We should use a VIP address that is not used on the node. + // An address "198.51.100.0" from the TEST-NET-2 rage in https://datatracker.ietf.org/doc/html/rfc5737 + // is used. These addresses are reserved for documentation. If the user is using + // this address for a VS anyway we *will* mess up, but that would be an invalid configuration. + // If the user have configured the address to an interface on the node (but not a VS) + // then traffic will temporary be routed to ipvs during the probe and dropped. + // The later case is also and invalid configuration, but the traffic impact will be minor. + // This should not be a problem if users honors reserved addresses, but cut/paste + // from documentation is not unheard of, so the restriction to not use the TEST-NET-2 range + // must be documented. + vs := utilipvs.VirtualServer{ + Address: netutils.ParseIPSloppy("198.51.100.0"), + Protocol: "TCP", + Port: 20000, + Scheduler: scheduler, + } + if err := ipvs.AddVirtualServer(&vs); err != nil { + logger.Error(err, "Could not create dummy VS", "scheduler", scheduler) + return err + } + + // To overcome the BUG described above we check that the VS is *really* added. + vservers, err = ipvs.GetVirtualServers() + if err != nil { + logger.Error(err, "ipvs.GetVirtualServers") + return err + } + logger.V(5).Info("Virtual Servers after adding dummy", "count", len(vservers)) + if len(vservers) == 0 { + logger.Info("Dummy VS not created", "scheduler", scheduler) + return fmt.Errorf("ipvs not supported") // This is a BUG work-around + } + logger.V(5).Info("Dummy VS created", "vs", vs) + + if err := ipvs.DeleteVirtualServer(&vs); err != nil { + logger.Error(err, "Could not delete dummy VS") + return err + } + + return nil +} diff --git a/pkg/proxy/ipvs/supported_test.go b/pkg/proxy/ipvs/supported_test.go new file mode 100644 index 00000000000..dc87cf63845 --- /dev/null +++ b/pkg/proxy/ipvs/supported_test.go @@ -0,0 +1,85 @@ +//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 ( + "fmt" + "testing" + + "k8s.io/kubernetes/test/utils/ktesting" +) + +func TestCanUseIPVSProxier(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) + testCases := []struct { + name string + scheduler string + ipsetVersion string + ipsetErr error + ipvsErr string + ok bool + }{ + { + name: "happy days", + ipsetVersion: MinIPSetCheckVersion, + ok: true, + }, + { + name: "ipset error", + scheduler: "", + ipsetVersion: MinIPSetCheckVersion, + ipsetErr: fmt.Errorf("oops"), + ok: false, + }, + { + name: "ipset version too low", + scheduler: "rr", + ipsetVersion: "4.3.0", + ok: false, + }, + { + name: "GetVirtualServers fail", + ipsetVersion: MinIPSetCheckVersion, + ipvsErr: "GetVirtualServers", + ok: false, + }, + { + name: "AddVirtualServer fail", + ipsetVersion: MinIPSetCheckVersion, + ipvsErr: "AddVirtualServer", + ok: false, + }, + { + name: "DeleteVirtualServer fail", + ipsetVersion: MinIPSetCheckVersion, + ipvsErr: "DeleteVirtualServer", + ok: false, + }, + } + + for _, tc := range testCases { + ipvs := &fakeIpvs{tc.ipvsErr, false} + versioner := &fakeIPSetVersioner{version: tc.ipsetVersion, err: tc.ipsetErr} + err := CanUseIPVSProxier(ctx, ipvs, versioner, tc.scheduler) + if (err == nil) != tc.ok { + t.Errorf("Case [%s], expect %v, got err: %v", tc.name, tc.ok, err) + } + } +} diff --git a/pkg/proxy/nftables/proxier.go b/pkg/proxy/nftables/proxier.go index 4f88ec0cf26..0b46f64e0fd 100644 --- a/pkg/proxy/nftables/proxier.go +++ b/pkg/proxy/nftables/proxier.go @@ -25,7 +25,6 @@ import ( "encoding/base32" "fmt" "net" - "os" "os/exec" "strconv" "strings" @@ -39,7 +38,6 @@ import ( discovery "k8s.io/api/discovery/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/version" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/events" "k8s.io/klog/v2" @@ -50,7 +48,6 @@ import ( "k8s.io/kubernetes/pkg/proxy/metrics" "k8s.io/kubernetes/pkg/proxy/runner" proxyutil "k8s.io/kubernetes/pkg/proxy/util" - utilkernel "k8s.io/kubernetes/pkg/util/kernel" netutils "k8s.io/utils/net" "k8s.io/utils/ptr" "sigs.k8s.io/knftables" @@ -279,52 +276,6 @@ func NewProxier(ctx context.Context, return proxier, nil } -// Create a knftables.Interface and check if we can use the nftables proxy mode on this host. -func getNFTablesInterface(ipFamily v1.IPFamily) (knftables.Interface, error) { - var nftablesFamily knftables.Family - if ipFamily == v1.IPv4Protocol { - nftablesFamily = knftables.IPv4Family - } else { - nftablesFamily = knftables.IPv6Family - } - - // We require (or rather, knftables.New does) that the nft binary be version 1.0.1 - // or later, because versions before that would always attempt to parse the entire - // nft ruleset at startup, even if you were only operating on a single table. - // That's bad, because in some cases, new versions of nft have added new rule - // types in ways that triggered bugs in older versions of nft, causing them to - // crash. Thus, if kube-proxy used nft < 1.0.1, it could potentially get locked - // out of its rules because of something some other component had done in a - // completely different table. - nft, err := knftables.New(nftablesFamily, kubeProxyTable) - if err != nil { - return nil, err - } - - // Likewise, we want to ensure that the host filesystem has nft >= 1.0.1, so that - // it's not possible that *our* rules break *the system's* nft. (In particular, we - // know that if kube-proxy uses nft >= 1.0.3 and the system has nft <= 0.9.8, that - // the system nft will become completely unusable.) Unfortunately, we can't easily - // figure out the version of nft installed on the host filesystem, so instead, we - // check the kernel version, under the assumption that the distro will have an nft - // binary that supports the same features as its kernel does, and so kernel 5.13 - // or later implies nft 1.0.1 or later. https://issues.k8s.io/122743 - // - // However, we allow the user to bypass this check by setting - // `KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK` to anything non-empty. - if os.Getenv("KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK") == "" { - kernelVersion, err := utilkernel.GetVersion() - if err != nil { - return nil, fmt.Errorf("could not check kernel version: %w", err) - } - if kernelVersion.LessThan(version.MustParseGeneric(utilkernel.NFTablesKubeProxyKernelVersion)) { - return nil, fmt.Errorf("kube-proxy in nftables mode requires kernel %s or later", utilkernel.NFTablesKubeProxyKernelVersion) - } - } - - return nft, nil -} - // internal struct for string service information type servicePortInfo struct { *proxy.BaseServicePortInfo diff --git a/pkg/proxy/nftables/supported.go b/pkg/proxy/nftables/supported.go new file mode 100644 index 00000000000..43886a5f1f7 --- /dev/null +++ b/pkg/proxy/nftables/supported.go @@ -0,0 +1,76 @@ +//go:build linux +// +build linux + +/* +Copyright 2015 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 nftables + +import ( + "fmt" + "os" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/version" + utilkernel "k8s.io/kubernetes/pkg/util/kernel" + "sigs.k8s.io/knftables" +) + +// Create a knftables.Interface and check if we can use the nftables proxy mode on this host. +func getNFTablesInterface(ipFamily v1.IPFamily) (knftables.Interface, error) { + var nftablesFamily knftables.Family + if ipFamily == v1.IPv4Protocol { + nftablesFamily = knftables.IPv4Family + } else { + nftablesFamily = knftables.IPv6Family + } + + // We require (or rather, knftables.New does) that the nft binary be version 1.0.1 + // or later, because versions before that would always attempt to parse the entire + // nft ruleset at startup, even if you were only operating on a single table. + // That's bad, because in some cases, new versions of nft have added new rule + // types in ways that triggered bugs in older versions of nft, causing them to + // crash. Thus, if kube-proxy used nft < 1.0.1, it could potentially get locked + // out of its rules because of something some other component had done in a + // completely different table. + nft, err := knftables.New(nftablesFamily, kubeProxyTable) + if err != nil { + return nil, err + } + + // Likewise, we want to ensure that the host filesystem has nft >= 1.0.1, so that + // it's not possible that *our* rules break *the system's* nft. (In particular, we + // know that if kube-proxy uses nft >= 1.0.3 and the system has nft <= 0.9.8, that + // the system nft will become completely unusable.) Unfortunately, we can't easily + // figure out the version of nft installed on the host filesystem, so instead, we + // check the kernel version, under the assumption that the distro will have an nft + // binary that supports the same features as its kernel does, and so kernel 5.13 + // or later implies nft 1.0.1 or later. https://issues.k8s.io/122743 + // + // However, we allow the user to bypass this check by setting + // `KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK` to anything non-empty. + if os.Getenv("KUBE_PROXY_NFTABLES_SKIP_KERNEL_VERSION_CHECK") == "" { + kernelVersion, err := utilkernel.GetVersion() + if err != nil { + return nil, fmt.Errorf("could not check kernel version: %w", err) + } + if kernelVersion.LessThan(version.MustParseGeneric(utilkernel.NFTablesKubeProxyKernelVersion)) { + return nil, fmt.Errorf("kube-proxy in nftables mode requires kernel %s or later", utilkernel.NFTablesKubeProxyKernelVersion) + } + } + + return nft, nil +} diff --git a/pkg/proxy/winkernel/proxier.go b/pkg/proxy/winkernel/proxier.go index ad3bd26fd61..35558bbce95 100644 --- a/pkg/proxy/winkernel/proxier.go +++ b/pkg/proxy/winkernel/proxier.go @@ -52,38 +52,10 @@ import ( netutils "k8s.io/utils/net" ) -// KernelCompatTester tests whether the required kernel capabilities are -// present to run the windows kernel proxier. -type KernelCompatTester interface { - IsCompatible() error -} - type HostMacProvider interface { GetHostMac(nodeIP net.IP) string } -// CanUseWinKernelProxier returns true if we should use the Kernel Proxier -// instead of the "classic" userspace Proxier. This is determined by checking -// the windows kernel version and for the existence of kernel features. -func CanUseWinKernelProxier(kcompat KernelCompatTester) (bool, error) { - // Check that the kernel supports what we need. - if err := kcompat.IsCompatible(); err != nil { - return false, err - } - return true, nil -} - -type WindowsKernelCompatTester struct{} - -// IsCompatible returns true if winkernel can support this mode of proxy -func (lkct WindowsKernelCompatTester) IsCompatible() error { - _, err := hnslib.HNSListPolicyListRequest() - if err != nil { - return fmt.Errorf("Windows kernel is not compatible for Kernel mode") - } - return nil -} - type externalIPInfo struct { ip string hnsID string @@ -266,50 +238,6 @@ func isOverlay(hnsNetworkInfo *hnsNetworkInfo) bool { return strings.EqualFold(hnsNetworkInfo.networkType, NETWORK_TYPE_OVERLAY) } -// StackCompatTester tests whether the required kernel and network are dualstack capable -type StackCompatTester interface { - DualStackCompatible(networkName string) bool -} - -type DualStackCompatTester struct{} - -func (t DualStackCompatTester) DualStackCompatible(networkName string) bool { - hcnImpl := newHcnImpl() - // First tag of hnslib that has a proper check for dual stack support is v0.8.22 due to a bug. - if err := hcnImpl.Ipv6DualStackSupported(); err != nil { - // Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing - // to see if dual stack is supported), but the only time this can happen, at least that can be discerned, is if the host - // is pre-1803 and hcn didn't exist. hnslib should truthfully return a known error if this happened that we can - // check against, and the case where 'err != this known error' would be the 'this feature isn't supported' case, as is being - // used here. For now, seeming as how nothing before ws2019 (1809) is listed as supported for k8s we can pretty much assume - // any error here isn't because the query failed, it's just that dualstack simply isn't supported on the host. With all - // that in mind, just log as info and not error to let the user know we're falling back. - klog.InfoS("This version of Windows does not support dual-stack, falling back to single-stack", "err", err.Error()) - return false - } - - // check if network is using overlay - hns, _ := newHostNetworkService(hcnImpl) - networkName, err := getNetworkName(networkName) - if err != nil { - klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") - return false - } - networkInfo, err := getNetworkInfo(hns, networkName) - if err != nil { - klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") - return false - } - - if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WinOverlay) && isOverlay(networkInfo) { - // Overlay (VXLAN) networks on Windows do not support dual-stack networking today - klog.InfoS("Winoverlay does not support dual-stack, falling back to single-stack") - return false - } - - return true -} - // internal struct for endpoints information type endpointInfo struct { ip string diff --git a/pkg/proxy/winkernel/supported.go b/pkg/proxy/winkernel/supported.go new file mode 100644 index 00000000000..7332ef117e2 --- /dev/null +++ b/pkg/proxy/winkernel/supported.go @@ -0,0 +1,102 @@ +//go:build windows +// +build windows + +/* +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 winkernel + +import ( + "fmt" + + "github.com/Microsoft/hnslib" + + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/klog/v2" + kubefeatures "k8s.io/kubernetes/pkg/features" +) + +// KernelCompatTester tests whether the required kernel capabilities are +// present to run the windows kernel proxier. +type KernelCompatTester interface { + IsCompatible() error +} + +// CanUseWinKernelProxier returns true if we should use the Kernel Proxier +// instead of the "classic" userspace Proxier. This is determined by checking +// the windows kernel version and for the existence of kernel features. +func CanUseWinKernelProxier(kcompat KernelCompatTester) (bool, error) { + // Check that the kernel supports what we need. + if err := kcompat.IsCompatible(); err != nil { + return false, err + } + return true, nil +} + +type WindowsKernelCompatTester struct{} + +// IsCompatible returns true if winkernel can support this mode of proxy +func (lkct WindowsKernelCompatTester) IsCompatible() error { + _, err := hnslib.HNSListPolicyListRequest() + if err != nil { + return fmt.Errorf("Windows kernel is not compatible for Kernel mode") + } + return nil +} + +// StackCompatTester tests whether the required kernel and network are dualstack capable +type StackCompatTester interface { + DualStackCompatible(networkName string) bool +} + +type DualStackCompatTester struct{} + +func (t DualStackCompatTester) DualStackCompatible(networkName string) bool { + hcnImpl := newHcnImpl() + // First tag of hnslib that has a proper check for dual stack support is v0.8.22 due to a bug. + if err := hcnImpl.Ipv6DualStackSupported(); err != nil { + // Hcn *can* fail the query to grab the version of hcn itself (which this call will do internally before parsing + // to see if dual stack is supported), but the only time this can happen, at least that can be discerned, is if the host + // is pre-1803 and hcn didn't exist. hnslib should truthfully return a known error if this happened that we can + // check against, and the case where 'err != this known error' would be the 'this feature isn't supported' case, as is being + // used here. For now, seeming as how nothing before ws2019 (1809) is listed as supported for k8s we can pretty much assume + // any error here isn't because the query failed, it's just that dualstack simply isn't supported on the host. With all + // that in mind, just log as info and not error to let the user know we're falling back. + klog.InfoS("This version of Windows does not support dual-stack, falling back to single-stack", "err", err.Error()) + return false + } + + // check if network is using overlay + hns, _ := newHostNetworkService(hcnImpl) + networkName, err := getNetworkName(networkName) + if err != nil { + klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") + return false + } + networkInfo, err := getNetworkInfo(hns, networkName) + if err != nil { + klog.ErrorS(err, "Unable to determine dual-stack status, falling back to single-stack") + return false + } + + if utilfeature.DefaultFeatureGate.Enabled(kubefeatures.WinOverlay) && isOverlay(networkInfo) { + // Overlay (VXLAN) networks on Windows do not support dual-stack networking today + klog.InfoS("Winoverlay does not support dual-stack, falling back to single-stack") + return false + } + + return true +}