
The iptables and nftables proxy backends had 2 unit tests (TestDeleteEndpointConnections and TestProxierDeleteNodePortStaleUDP) that were effectively testing that: - If the proxy saw various Service/EndpointSlice events this would result in specific changes to the service/endpoints trackers, AND - If the service/endpoints trackers changed in those specific ways this would result in specific UpdateServiceMapResult and UpdateEndpointsMapResult values being generated, AND - If you passed those specific UpdateServiceMapResult and UpdateEndpointsMapResult values to conntrack.CleanStaleEntries it would make specific calls to the lower-level conntrack methods, AND - If you called the lower-level conntrack methods with those specific arguments, it would result in specific executions of the conntrack binary, mixed with a specific number of klog invocations. This... is not a good unit test. We already test the change tracker behavior in other unit tests, and we already tested the Update{Service,Endpoints}MapResult behavior in the pkg/proxy unit tests, and we already tested the conntrack exec behavior in pkg/proxy/conntrack/conntrack_test.go, and we now test the CleanStaleEntries behavior in pkg/proxy/conntrack/cleanup_test.go. So there is no need to try to test the top-to-bottom behavior as a "unit test".
140 lines
5.4 KiB
Go
140 lines
5.4 KiB
Go
//go:build linux
|
|
// +build linux
|
|
|
|
/*
|
|
Copyright 2016 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 conntrack
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
v1 "k8s.io/api/core/v1"
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/utils/exec"
|
|
utilnet "k8s.io/utils/net"
|
|
)
|
|
|
|
// Interface for dealing with conntrack
|
|
type Interface interface {
|
|
// ClearEntriesForIP deletes conntrack entries for connections of the given
|
|
// protocol, to the given IP.
|
|
ClearEntriesForIP(ip string, protocol v1.Protocol) error
|
|
|
|
// ClearEntriesForPort deletes conntrack entries for connections of the given
|
|
// protocol and IP family, to the given port.
|
|
ClearEntriesForPort(port int, isIPv6 bool, protocol v1.Protocol) error
|
|
|
|
// ClearEntriesForNAT deletes conntrack entries for connections of the given
|
|
// protocol, which had been DNATted from origin to dest.
|
|
ClearEntriesForNAT(origin, dest string, protocol v1.Protocol) error
|
|
|
|
// ClearEntriesForPortNAT deletes conntrack entries for connections of the given
|
|
// protocol, which had been DNATted from the given port (on any IP) to dest.
|
|
ClearEntriesForPortNAT(dest string, port int, protocol v1.Protocol) error
|
|
}
|
|
|
|
// execCT implements Interface by execing the conntrack tool
|
|
type execCT struct {
|
|
execer exec.Interface
|
|
}
|
|
|
|
var _ Interface = &execCT{}
|
|
|
|
// noConnectionToDelete is the error string returned by conntrack when no matching connections are found
|
|
const noConnectionToDelete = "0 flow entries have been deleted"
|
|
|
|
func protoStr(proto v1.Protocol) string {
|
|
return strings.ToLower(string(proto))
|
|
}
|
|
|
|
func parametersWithFamily(isIPv6 bool, parameters ...string) []string {
|
|
if isIPv6 {
|
|
parameters = append(parameters, "-f", "ipv6")
|
|
}
|
|
return parameters
|
|
}
|
|
|
|
// exec executes the conntrack tool using the given parameters
|
|
func (ct *execCT) exec(parameters ...string) error {
|
|
conntrackPath, err := ct.execer.LookPath("conntrack")
|
|
if err != nil {
|
|
return fmt.Errorf("error looking for path of conntrack: %v", err)
|
|
}
|
|
klog.V(4).InfoS("Clearing conntrack entries", "parameters", parameters)
|
|
output, err := ct.execer.Command(conntrackPath, parameters...).CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("conntrack command returned: %q, error message: %s", string(output), err)
|
|
}
|
|
klog.V(4).InfoS("Conntrack entries deleted", "output", string(output))
|
|
return nil
|
|
}
|
|
|
|
// ClearEntriesForIP is part of Interface
|
|
func (ct *execCT) ClearEntriesForIP(ip string, protocol v1.Protocol) error {
|
|
parameters := parametersWithFamily(utilnet.IsIPv6String(ip), "-D", "--orig-dst", ip, "-p", protoStr(protocol))
|
|
err := ct.exec(parameters...)
|
|
if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) {
|
|
// TODO: Better handling for deletion failure. When failure occur, stale udp connection may not get flushed.
|
|
// These stale udp connection will keep black hole traffic. Making this a best effort operation for now, since it
|
|
// is expensive to baby-sit all udp connections to kubernetes services.
|
|
return fmt.Errorf("error deleting connection tracking state for UDP service IP: %s, error: %v", ip, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearEntriesForPort is part of Interface
|
|
func (ct *execCT) ClearEntriesForPort(port int, isIPv6 bool, protocol v1.Protocol) error {
|
|
if port <= 0 {
|
|
return fmt.Errorf("wrong port number. The port number must be greater than zero")
|
|
}
|
|
parameters := parametersWithFamily(isIPv6, "-D", "-p", protoStr(protocol), "--dport", strconv.Itoa(port))
|
|
err := ct.exec(parameters...)
|
|
if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) {
|
|
return fmt.Errorf("error deleting conntrack entries for UDP port: %d, error: %v", port, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearEntriesForNAT is part of Interface
|
|
func (ct *execCT) ClearEntriesForNAT(origin, dest string, protocol v1.Protocol) error {
|
|
parameters := parametersWithFamily(utilnet.IsIPv6String(origin), "-D", "--orig-dst", origin, "--dst-nat", dest,
|
|
"-p", protoStr(protocol))
|
|
err := ct.exec(parameters...)
|
|
if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) {
|
|
// TODO: Better handling for deletion failure. When failure occur, stale udp connection may not get flushed.
|
|
// These stale udp connection will keep black hole traffic. Making this a best effort operation for now, since it
|
|
// is expensive to baby sit all udp connections to kubernetes services.
|
|
return fmt.Errorf("error deleting conntrack entries for UDP peer {%s, %s}, error: %v", origin, dest, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ClearEntriesForPortNAT is part of Interface
|
|
func (ct *execCT) ClearEntriesForPortNAT(dest string, port int, protocol v1.Protocol) error {
|
|
if port <= 0 {
|
|
return fmt.Errorf("wrong port number. The port number must be greater than zero")
|
|
}
|
|
parameters := parametersWithFamily(utilnet.IsIPv6String(dest), "-D", "-p", protoStr(protocol), "--dport", strconv.Itoa(port), "--dst-nat", dest)
|
|
err := ct.exec(parameters...)
|
|
if err != nil && !strings.Contains(err.Error(), noConnectionToDelete) {
|
|
return fmt.Errorf("error deleting conntrack entries for UDP port: %d, error: %v", port, err)
|
|
}
|
|
return nil
|
|
}
|