k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/e2e/framework/network/utils.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package network
    18  
    19  import (
    20  	"context"
    21  	"crypto/tls"
    22  	"encoding/json"
    23  	"fmt"
    24  	"io"
    25  	"net"
    26  	"net/http"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/onsi/ginkgo/v2"
    32  	v1 "k8s.io/api/core/v1"
    33  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/util/intstr"
    36  	utilnet "k8s.io/apimachinery/pkg/util/net"
    37  	"k8s.io/apimachinery/pkg/util/sets"
    38  	"k8s.io/apimachinery/pkg/util/uuid"
    39  	"k8s.io/apimachinery/pkg/util/wait"
    40  	clientset "k8s.io/client-go/kubernetes"
    41  	coreclientset "k8s.io/client-go/kubernetes/typed/core/v1"
    42  	"k8s.io/kubernetes/test/e2e/framework"
    43  	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
    44  	e2enode "k8s.io/kubernetes/test/e2e/framework/node"
    45  	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
    46  	e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
    47  	e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
    48  	e2essh "k8s.io/kubernetes/test/e2e/framework/ssh"
    49  	storageutils "k8s.io/kubernetes/test/e2e/storage/utils"
    50  	imageutils "k8s.io/kubernetes/test/utils/image"
    51  	netutils "k8s.io/utils/net"
    52  )
    53  
    54  const (
    55  	// EndpointHTTPPort is an endpoint HTTP port for testing.
    56  	EndpointHTTPPort = 8083
    57  	// EndpointUDPPort is an endpoint UDP port for testing.
    58  	EndpointUDPPort = 8081
    59  	// EndpointSCTPPort is an endpoint SCTP port for testing.
    60  	EndpointSCTPPort = 8082
    61  	// testContainerHTTPPort is the test container http port.
    62  	testContainerHTTPPort = 9080
    63  	// ClusterHTTPPort is a cluster HTTP port for testing.
    64  	ClusterHTTPPort = 80
    65  	// ClusterUDPPort is a cluster UDP port for testing.
    66  	ClusterUDPPort = 90
    67  	// ClusterSCTPPort is a cluster SCTP port for testing.
    68  	ClusterSCTPPort            = 95
    69  	testPodName                = "test-container-pod"
    70  	hostTestPodName            = "host-test-container-pod"
    71  	nodePortServiceName        = "node-port-service"
    72  	sessionAffinityServiceName = "session-affinity-service"
    73  	// wait time between poll attempts of a Service vip and/or nodePort.
    74  	// coupled with testTries to produce a net timeout value.
    75  	hitEndpointRetryDelay = 2 * time.Second
    76  	// Number of retries to hit a given set of endpoints. Needs to be high
    77  	// because we verify iptables statistical rr loadbalancing.
    78  	testTries = 30
    79  	// Maximum number of pods in a test, to make test work in large clusters.
    80  	maxNetProxyPodsCount = 10
    81  	// SessionAffinityChecks is number of checks to hit a given set of endpoints when enable session affinity.
    82  	SessionAffinityChecks = 10
    83  	// RegexIPv4 is a regex to match IPv4 addresses
    84  	RegexIPv4 = "(?:\\d+)\\.(?:\\d+)\\.(?:\\d+)\\.(?:\\d+)"
    85  	// RegexIPv6 is a regex to match IPv6 addresses
    86  	RegexIPv6                 = "(?:(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-fA-F]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,1}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,2}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:(?:[0-9a-fA-F]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,3}(?:(?:[0-9a-fA-F]{1,4})))?::(?:(?:[0-9a-fA-F]{1,4})):)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,4}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,5}(?:(?:[0-9a-fA-F]{1,4})))?::)(?:(?:[0-9a-fA-F]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-fA-F]{1,4})):){0,6}(?:(?:[0-9a-fA-F]{1,4})))?::))))"
    87  	resizeNodeReadyTimeout    = 2 * time.Minute
    88  	resizeNodeNotReadyTimeout = 2 * time.Minute
    89  	// netexec dial commands
    90  	// the destination will echo its hostname.
    91  	echoHostname = "hostname"
    92  )
    93  
    94  // Option is used to configure the NetworkingTest object
    95  type Option func(*NetworkingTestConfig)
    96  
    97  // EnableSCTP listen on SCTP ports on the endpoints
    98  func EnableSCTP(config *NetworkingTestConfig) {
    99  	config.SCTPEnabled = true
   100  }
   101  
   102  // EnableDualStack create Dual Stack services
   103  func EnableDualStack(config *NetworkingTestConfig) {
   104  	config.DualStackEnabled = true
   105  }
   106  
   107  // UseHostNetwork run the test container with HostNetwork=true.
   108  func UseHostNetwork(config *NetworkingTestConfig) {
   109  	config.HostNetwork = true
   110  }
   111  
   112  // EndpointsUseHostNetwork run the endpoints pods with HostNetwork=true.
   113  func EndpointsUseHostNetwork(config *NetworkingTestConfig) {
   114  	config.EndpointsHostNetwork = true
   115  }
   116  
   117  // PreferExternalAddresses prefer node External Addresses for the tests
   118  func PreferExternalAddresses(config *NetworkingTestConfig) {
   119  	config.PreferExternalAddresses = true
   120  }
   121  
   122  // NewNetworkingTestConfig creates and sets up a new test config helper.
   123  func NewNetworkingTestConfig(ctx context.Context, f *framework.Framework, setters ...Option) *NetworkingTestConfig {
   124  	// default options
   125  	config := &NetworkingTestConfig{
   126  		f:         f,
   127  		Namespace: f.Namespace.Name,
   128  	}
   129  	for _, setter := range setters {
   130  		setter(config)
   131  	}
   132  	ginkgo.By(fmt.Sprintf("Performing setup for networking test in namespace %v", config.Namespace))
   133  	config.setup(ctx, getServiceSelector())
   134  	return config
   135  }
   136  
   137  // NewCoreNetworkingTestConfig creates and sets up a new test config helper for Node E2E.
   138  func NewCoreNetworkingTestConfig(ctx context.Context, f *framework.Framework, hostNetwork bool) *NetworkingTestConfig {
   139  	// default options
   140  	config := &NetworkingTestConfig{
   141  		f:           f,
   142  		Namespace:   f.Namespace.Name,
   143  		HostNetwork: hostNetwork,
   144  	}
   145  	ginkgo.By(fmt.Sprintf("Performing setup for networking test in namespace %v", config.Namespace))
   146  	config.setupCore(ctx, getServiceSelector())
   147  	return config
   148  }
   149  
   150  func getServiceSelector() map[string]string {
   151  	ginkgo.By("creating a selector")
   152  	selectorName := "selector-" + string(uuid.NewUUID())
   153  	serviceSelector := map[string]string{
   154  		selectorName: "true",
   155  	}
   156  	return serviceSelector
   157  }
   158  
   159  // NetworkingTestConfig is a convenience class around some utility methods
   160  // for testing kubeproxy/networking/services/endpoints.
   161  type NetworkingTestConfig struct {
   162  	// TestContainerPod is a test pod running the netexec image. It is capable
   163  	// of executing tcp/udp requests against ip:port.
   164  	TestContainerPod *v1.Pod
   165  	// HostTestContainerPod is a pod running using the hostexec image.
   166  	HostTestContainerPod *v1.Pod
   167  	// if the HostTestContainerPod is running with HostNetwork=true.
   168  	HostNetwork bool
   169  	// if the endpoints Pods are running with HostNetwork=true.
   170  	EndpointsHostNetwork bool
   171  	// if the test pods are listening on sctp port. We need this as sctp tests
   172  	// are marked as disruptive as they may load the sctp module.
   173  	SCTPEnabled bool
   174  	// DualStackEnabled enables dual stack on services
   175  	DualStackEnabled bool
   176  	// EndpointPods are the pods belonging to the Service created by this
   177  	// test config. Each invocation of `setup` creates a service with
   178  	// 1 pod per node running the netexecImage.
   179  	EndpointPods []*v1.Pod
   180  	f            *framework.Framework
   181  	podClient    *e2epod.PodClient
   182  	// NodePortService is a Service with Type=NodePort spanning over all
   183  	// endpointPods.
   184  	NodePortService *v1.Service
   185  	// SessionAffinityService is a Service with SessionAffinity=ClientIP
   186  	// spanning over all endpointPods.
   187  	SessionAffinityService *v1.Service
   188  	// Nodes is a list of nodes in the cluster.
   189  	Nodes []v1.Node
   190  	// MaxTries is the number of retries tolerated for tests run against
   191  	// endpoints and services created by this config.
   192  	MaxTries int
   193  	// The ClusterIP of the Service created by this test config.
   194  	ClusterIP string
   195  	// The SecondaryClusterIP of the Service created by this test config.
   196  	SecondaryClusterIP string
   197  	// NodeIP it's an ExternalIP if the node has one,
   198  	// or an InternalIP if not, for use in nodePort testing.
   199  	NodeIP string
   200  	// SecondaryNodeIP it's an ExternalIP of the secondary IP family if the node has one,
   201  	// or an InternalIP if not, for usein nodePort testing.
   202  	SecondaryNodeIP string
   203  	// The http/udp/sctp nodePorts of the Service.
   204  	NodeHTTPPort int
   205  	NodeUDPPort  int
   206  	NodeSCTPPort int
   207  	// The kubernetes namespace within which all resources for this
   208  	// config are created
   209  	Namespace string
   210  	// Whether to prefer node External Addresses for the tests
   211  	PreferExternalAddresses bool
   212  }
   213  
   214  // NetexecDialResponse represents the response returned by the `netexec` subcommand of `agnhost`
   215  type NetexecDialResponse struct {
   216  	Responses []string `json:"responses"`
   217  	Errors    []string `json:"errors"`
   218  }
   219  
   220  // DialFromEndpointContainer executes a curl via kubectl exec in an endpoint container.   Returns an error to be handled by the caller.
   221  func (config *NetworkingTestConfig) DialFromEndpointContainer(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error {
   222  	return config.DialFromContainer(ctx, protocol, echoHostname, config.EndpointPods[0].Status.PodIP, targetIP, EndpointHTTPPort, targetPort, maxTries, minTries, expectedEps)
   223  }
   224  
   225  // DialFromTestContainer executes a curl via kubectl exec in a test container. Returns an error to be handled by the caller.
   226  func (config *NetworkingTestConfig) DialFromTestContainer(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error {
   227  	return config.DialFromContainer(ctx, protocol, echoHostname, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedEps)
   228  }
   229  
   230  // DialEchoFromTestContainer executes a curl via kubectl exec in a test container. The response is expected to match the echoMessage,  Returns an error to be handled by the caller.
   231  func (config *NetworkingTestConfig) DialEchoFromTestContainer(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, echoMessage string) error {
   232  	expectedResponse := sets.NewString()
   233  	expectedResponse.Insert(echoMessage)
   234  	var dialCommand string
   235  
   236  	// NOTE(claudiub): netexec /dialCommand will send a request to the given targetIP and targetPort as follows:
   237  	// for HTTP: it will send a request to: http://targetIP:targetPort/dialCommand
   238  	// for UDP: it will send targetCommand as a message. The consumer receives the data message and looks for
   239  	// a few starting strings, including echo, and treats it accordingly.
   240  	if protocol == "http" {
   241  		dialCommand = fmt.Sprintf("echo?msg=%s", echoMessage)
   242  	} else {
   243  		dialCommand = fmt.Sprintf("echo%%20%s", echoMessage)
   244  	}
   245  	return config.DialFromContainer(ctx, protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, maxTries, minTries, expectedResponse)
   246  }
   247  
   248  // diagnoseMissingEndpoints prints debug information about the endpoints that
   249  // are NOT in the given list of foundEndpoints. These are the endpoints we
   250  // expected a response from.
   251  func (config *NetworkingTestConfig) diagnoseMissingEndpoints(foundEndpoints sets.String) {
   252  	for _, e := range config.EndpointPods {
   253  		if foundEndpoints.Has(e.Name) {
   254  			continue
   255  		}
   256  		framework.Logf("\nOutput of kubectl describe pod %v/%v:\n", e.Namespace, e.Name)
   257  		desc, _ := e2ekubectl.RunKubectl(
   258  			e.Namespace, "describe", "pod", e.Name, fmt.Sprintf("--namespace=%v", e.Namespace))
   259  		framework.Logf(desc)
   260  	}
   261  }
   262  
   263  // EndpointHostnames returns a set of hostnames for existing endpoints.
   264  func (config *NetworkingTestConfig) EndpointHostnames() sets.String {
   265  	expectedEps := sets.NewString()
   266  	for _, p := range config.EndpointPods {
   267  		if config.EndpointsHostNetwork {
   268  			expectedEps.Insert(p.Spec.NodeSelector["kubernetes.io/hostname"])
   269  		} else {
   270  			expectedEps.Insert(p.Name)
   271  		}
   272  	}
   273  	return expectedEps
   274  }
   275  
   276  func makeCURLDialCommand(ipPort, dialCmd, protocol, targetIP string, targetPort int) string {
   277  	// The current versions of curl included in CentOS and RHEL distros
   278  	// misinterpret square brackets around IPv6 as globbing, so use the -g
   279  	// argument to disable globbing to handle the IPv6 case.
   280  	return fmt.Sprintf("curl -g -q -s 'http://%s/dial?request=%s&protocol=%s&host=%s&port=%d&tries=1'",
   281  		ipPort,
   282  		dialCmd,
   283  		protocol,
   284  		targetIP,
   285  		targetPort)
   286  }
   287  
   288  // DialFromContainer executes a curl via kubectl exec in a test container,
   289  // which might then translate to a tcp or udp request based on the protocol
   290  // argument in the url.
   291  //   - minTries is the minimum number of curl attempts required before declaring
   292  //     success. Set to 0 if you'd like to return as soon as all endpoints respond
   293  //     at least once.
   294  //   - maxTries is the maximum number of curl attempts. If this many attempts pass
   295  //     and we don't see all expected endpoints, the test fails.
   296  //   - targetIP is the source Pod IP that will dial the given dialCommand using the given protocol.
   297  //   - dialCommand is the command that the targetIP will send to the targetIP using the given protocol.
   298  //     the dialCommand should be formatted properly for the protocol (http: URL path+parameters,
   299  //     udp: command%20parameters, where parameters are optional)
   300  //   - expectedResponses is the unordered set of responses to wait for. The responses are based on
   301  //     the dialCommand; for example, for the dialCommand "hostname", the expectedResponses
   302  //     should contain the hostnames reported by each pod in the service through /hostName.
   303  //
   304  // maxTries == minTries will confirm that we see the expected endpoints and no
   305  // more for maxTries. Use this if you want to eg: fail a readiness check on a
   306  // pod and confirm it doesn't show up as an endpoint.
   307  // Returns nil if no error, or error message if failed after trying maxTries.
   308  func (config *NetworkingTestConfig) DialFromContainer(ctx context.Context, protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort, maxTries, minTries int, expectedResponses sets.String) error {
   309  	ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort))
   310  	cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort)
   311  
   312  	responses := sets.NewString()
   313  
   314  	for i := 0; i < maxTries; i++ {
   315  		resp, err := config.GetResponseFromContainer(ctx, protocol, dialCommand, containerIP, targetIP, containerHTTPPort, targetPort)
   316  		if err != nil {
   317  			// A failure to kubectl exec counts as a try, not a hard fail.
   318  			// Also note that we will keep failing for maxTries in tests where
   319  			// we confirm unreachability.
   320  			framework.Logf("GetResponseFromContainer: %s", err)
   321  			continue
   322  		}
   323  		for _, response := range resp.Responses {
   324  			trimmed := strings.TrimSpace(response)
   325  			if trimmed != "" {
   326  				responses.Insert(trimmed)
   327  			}
   328  		}
   329  		if responses.Difference(expectedResponses).Len() > 0 {
   330  			returnMsg := fmt.Errorf("received unexpected responses... \nAttempt %d\nCommand %v\nretrieved %v\nexpected %v", i, cmd, responses, expectedResponses)
   331  			// TODO(aojea) Remove once issues.k8s.io/123760 is solved
   332  			// Dump the nodes network routes and addresses for troubleshooting #123760
   333  			framework.Logf("encountered error during dial (%v)", returnMsg)
   334  			hostExec := storageutils.NewHostExec(config.f)
   335  			ginkgo.DeferCleanup(hostExec.Cleanup)
   336  			cmd := `echo "IP routes: " && ip route && echo "IP addresses:" && ip addr && echo "Open sockets: " && ss -anp --socket=tcp`
   337  			for _, node := range config.Nodes {
   338  				result, err := hostExec.IssueCommandWithResult(ctx, cmd, &node)
   339  				if err != nil {
   340  					framework.Logf("error occurred while executing command %s on node: %v", cmd, err)
   341  					continue
   342  				}
   343  				framework.Logf("Dump network information for node %s:\n%s", node.Name, result)
   344  			}
   345  			// Dump the node iptables rules and conntrack flows for troubleshooting #123760
   346  			podList, _ := config.f.ClientSet.CoreV1().Pods("kube-system").List(ctx, metav1.ListOptions{
   347  				LabelSelector: "k8s-app=kube-proxy",
   348  			})
   349  			for _, pod := range podList.Items {
   350  				// dump only for the node running test-container-pod
   351  				if pod.Status.HostIP == config.TestContainerPod.Status.HostIP {
   352  					output, _, _ := e2epod.ExecWithOptions(config.f, e2epod.ExecOptions{
   353  						Namespace:          "kube-system",
   354  						PodName:            pod.Name,
   355  						ContainerName:      "kube-proxy",
   356  						Command:            []string{"sh", "-c", fmt.Sprintf(`echo "IPTables Dump: " && iptables-save | grep "%s/%s:http" && echo "Conntrack flows: " && conntrack -Ln -p tcp | grep %d`, config.Namespace, config.NodePortService.Name, EndpointHTTPPort)},
   357  						Stdin:              nil,
   358  						CaptureStdout:      true,
   359  						CaptureStderr:      true,
   360  						PreserveWhitespace: false,
   361  					})
   362  					framework.Logf("Dump iptables and connntrack flows\n%s", output)
   363  					break
   364  				}
   365  			}
   366  			return returnMsg
   367  		}
   368  
   369  		framework.Logf("Waiting for responses: %v", expectedResponses.Difference(responses))
   370  
   371  		// Check against i+1 so we exit if minTries == maxTries.
   372  		if (responses.Equal(expectedResponses) || responses.Len() == 0 && expectedResponses.Len() == 0) && i+1 >= minTries {
   373  			framework.Logf("reached %v after %v/%v tries", targetIP, i, maxTries)
   374  			return nil
   375  		}
   376  		// TODO: get rid of this delay #36281
   377  		time.Sleep(hitEndpointRetryDelay)
   378  	}
   379  	if dialCommand == echoHostname {
   380  		config.diagnoseMissingEndpoints(responses)
   381  	}
   382  	returnMsg := fmt.Errorf("did not find expected responses... \nTries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, responses, expectedResponses)
   383  	framework.Logf("encountered error during dial (%v)", returnMsg)
   384  	return returnMsg
   385  
   386  }
   387  
   388  // GetEndpointsFromTestContainer executes a curl via kubectl exec in a test container.
   389  func (config *NetworkingTestConfig) GetEndpointsFromTestContainer(ctx context.Context, protocol, targetIP string, targetPort, tries int) (sets.String, error) {
   390  	return config.GetEndpointsFromContainer(ctx, protocol, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort, tries)
   391  }
   392  
   393  // GetEndpointsFromContainer executes a curl via kubectl exec in a test container,
   394  // which might then translate to a tcp or udp request based on the protocol argument
   395  // in the url. It returns all different endpoints from multiple retries.
   396  //   - tries is the number of curl attempts. If this many attempts pass and
   397  //     we don't see any endpoints, the test fails.
   398  func (config *NetworkingTestConfig) GetEndpointsFromContainer(ctx context.Context, protocol, containerIP, targetIP string, containerHTTPPort, targetPort, tries int) (sets.String, error) {
   399  	ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort))
   400  	cmd := makeCURLDialCommand(ipPort, "hostName", protocol, targetIP, targetPort)
   401  
   402  	eps := sets.NewString()
   403  
   404  	for i := 0; i < tries; i++ {
   405  		stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.TestContainerPod.Name, cmd)
   406  		if err != nil {
   407  			// A failure to kubectl exec counts as a try, not a hard fail.
   408  			// Also note that we will keep failing for maxTries in tests where
   409  			// we confirm unreachability.
   410  			framework.Logf("Failed to execute %q: %v, stdout: %q, stderr: %q", cmd, err, stdout, stderr)
   411  		} else {
   412  			podInfo := fmt.Sprintf("name: %v, namespace: %v, hostIp: %v, podIp: %v, conditions: %v", config.TestContainerPod.Name, config.TestContainerPod.Namespace, config.TestContainerPod.Status.HostIP, config.TestContainerPod.Status.PodIP, config.TestContainerPod.Status.Conditions)
   413  			framework.Logf("Tries: %d, in try: %d, stdout: %v, stderr: %v, command run in Pod { %#v }", tries, i, stdout, stderr, podInfo)
   414  
   415  			var output NetexecDialResponse
   416  			if err := json.Unmarshal([]byte(stdout), &output); err != nil {
   417  				framework.Logf("WARNING: Failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v",
   418  					cmd, config.TestContainerPod.Name, stdout, err)
   419  				continue
   420  			}
   421  
   422  			for _, hostName := range output.Responses {
   423  				trimmed := strings.TrimSpace(hostName)
   424  				if trimmed != "" {
   425  					eps.Insert(trimmed)
   426  				}
   427  			}
   428  			// TODO: get rid of this delay #36281
   429  			time.Sleep(hitEndpointRetryDelay)
   430  		}
   431  	}
   432  	return eps, nil
   433  }
   434  
   435  // GetResponseFromContainer executes a curl via kubectl exec in a container.
   436  func (config *NetworkingTestConfig) GetResponseFromContainer(ctx context.Context, protocol, dialCommand, containerIP, targetIP string, containerHTTPPort, targetPort int) (NetexecDialResponse, error) {
   437  	ipPort := net.JoinHostPort(containerIP, strconv.Itoa(containerHTTPPort))
   438  	cmd := makeCURLDialCommand(ipPort, dialCommand, protocol, targetIP, targetPort)
   439  
   440  	stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.TestContainerPod.Name, cmd)
   441  	if err != nil {
   442  		return NetexecDialResponse{}, fmt.Errorf("failed to execute %q: %v, stdout: %q, stderr: %q", cmd, err, stdout, stderr)
   443  	}
   444  
   445  	var output NetexecDialResponse
   446  	if err := json.Unmarshal([]byte(stdout), &output); err != nil {
   447  		return NetexecDialResponse{}, fmt.Errorf("failed to unmarshal curl response. Cmd %v run in %v, output: %s, err: %v",
   448  			cmd, config.TestContainerPod.Name, stdout, err)
   449  	}
   450  	return output, nil
   451  }
   452  
   453  // GetResponseFromTestContainer executes a curl via kubectl exec in a test container.
   454  func (config *NetworkingTestConfig) GetResponseFromTestContainer(ctx context.Context, protocol, dialCommand, targetIP string, targetPort int) (NetexecDialResponse, error) {
   455  	return config.GetResponseFromContainer(ctx, protocol, dialCommand, config.TestContainerPod.Status.PodIP, targetIP, testContainerHTTPPort, targetPort)
   456  }
   457  
   458  // GetHTTPCodeFromTestContainer executes a curl via kubectl exec in a test container and returns the status code.
   459  func (config *NetworkingTestConfig) GetHTTPCodeFromTestContainer(ctx context.Context, path, targetIP string, targetPort int) (int, error) {
   460  	cmd := fmt.Sprintf("curl -g -q -s -o /dev/null -w %%{http_code} http://%s:%d%s",
   461  		targetIP,
   462  		targetPort,
   463  		path)
   464  	stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.TestContainerPod.Name, cmd)
   465  	// We only care about the status code reported by curl,
   466  	// and want to return any other errors, such as cannot execute command in the Pod.
   467  	// If curl failed to connect to host, it would exit with code 7, which makes `ExecShellInPodWithFullOutput`
   468  	// return a non-nil error and output "000" to stdout.
   469  	if err != nil && len(stdout) == 0 {
   470  		return 0, fmt.Errorf("failed to execute %q: %v, stderr: %q", cmd, err, stderr)
   471  	}
   472  	code, err := strconv.Atoi(stdout)
   473  	if err != nil {
   474  		return 0, fmt.Errorf("failed to parse status code returned by healthz endpoint: %w, code: %s", err, stdout)
   475  	}
   476  	return code, nil
   477  }
   478  
   479  // DialFromNode executes a tcp/udp curl/nc request based on protocol via kubectl exec
   480  // in a test container running with host networking.
   481  //   - minTries is the minimum number of curl/nc attempts required before declaring
   482  //     success. If 0, then we return as soon as all endpoints succeed.
   483  //   - There is no logical change to test results if faillures happen AFTER endpoints have succeeded,
   484  //     hence over-padding minTries will NOT reverse a successful result and is thus not very useful yet
   485  //     (See the TODO about checking probability, which isn't implemented yet).
   486  //   - maxTries is the maximum number of curl/echo attempts before an error is returned.  The
   487  //     smaller this number is, the less 'slack' there is for declaring success.
   488  //   - if maxTries < expectedEps, this test is guaranteed to return an error, because all endpoints won't be hit.
   489  //   - maxTries == minTries will return as soon as all endpoints succeed (or fail once maxTries is reached without
   490  //     success on all endpoints).
   491  //     In general its prudent to have a high enough level of minTries to guarantee that all pods get a fair chance at receiving traffic.
   492  func (config *NetworkingTestConfig) DialFromNode(ctx context.Context, protocol, targetIP string, targetPort, maxTries, minTries int, expectedEps sets.String) error {
   493  	var cmd string
   494  	if protocol == "udp" {
   495  		cmd = fmt.Sprintf("echo hostName | nc -w 1 -u %s %d", targetIP, targetPort)
   496  	} else {
   497  		ipPort := net.JoinHostPort(targetIP, strconv.Itoa(targetPort))
   498  		// The current versions of curl included in CentOS and RHEL distros
   499  		// misinterpret square brackets around IPv6 as globbing, so use the -g
   500  		// argument to disable globbing to handle the IPv6 case.
   501  		cmd = fmt.Sprintf("curl -g -q -s --max-time 15 --connect-timeout 1 http://%s/hostName", ipPort)
   502  	}
   503  
   504  	// TODO: This simply tells us that we can reach the endpoints. Check that
   505  	// the probability of hitting a specific endpoint is roughly the same as
   506  	// hitting any other.
   507  	eps := sets.NewString()
   508  
   509  	filterCmd := fmt.Sprintf("%s | grep -v '^\\s*$'", cmd)
   510  	framework.Logf("Going to poll %v on port %v at least %v times, with a maximum of %v tries before failing", targetIP, targetPort, minTries, maxTries)
   511  	for i := 0; i < maxTries; i++ {
   512  		stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, config.f, config.HostTestContainerPod.Name, filterCmd)
   513  		if err != nil || len(stderr) > 0 {
   514  			// A failure to exec command counts as a try, not a hard fail.
   515  			// Also note that we will keep failing for maxTries in tests where
   516  			// we confirm unreachability.
   517  			framework.Logf("Failed to execute %q: %v, stdout: %q, stderr: %q", filterCmd, err, stdout, stderr)
   518  		} else {
   519  			trimmed := strings.TrimSpace(stdout)
   520  			if trimmed != "" {
   521  				eps.Insert(trimmed)
   522  			}
   523  		}
   524  
   525  		// Check against i+1 so we exit if minTries == maxTries.
   526  		if eps.Equal(expectedEps) && i+1 >= minTries {
   527  			framework.Logf("Found all %d expected endpoints: %+v", eps.Len(), eps.List())
   528  			return nil
   529  		}
   530  
   531  		framework.Logf("Waiting for %+v endpoints (expected=%+v, actual=%+v)", expectedEps.Difference(eps).List(), expectedEps.List(), eps.List())
   532  
   533  		// TODO: get rid of this delay #36281
   534  		time.Sleep(hitEndpointRetryDelay)
   535  	}
   536  
   537  	config.diagnoseMissingEndpoints(eps)
   538  	return fmt.Errorf("failed to find expected endpoints, \ntries %d\nCommand %v\nretrieved %v\nexpected %v", maxTries, cmd, eps, expectedEps)
   539  }
   540  
   541  // GetSelfURL executes a curl against the given path via kubectl exec into a
   542  // test container running with host networking, and fails if the output
   543  // doesn't match the expected string.
   544  func (config *NetworkingTestConfig) GetSelfURL(ctx context.Context, port int32, path string, expected string) {
   545  	cmd := fmt.Sprintf("curl -i -q -s --connect-timeout 1 http://localhost:%d%s", port, path)
   546  	ginkgo.By(fmt.Sprintf("Getting kube-proxy self URL %s", path))
   547  	config.executeCurlCmd(ctx, cmd, expected)
   548  }
   549  
   550  // GetSelfURLStatusCode executes a curl against the given path via kubectl exec into a
   551  // test container running with host networking, and fails if the returned status
   552  // code doesn't match the expected string.
   553  func (config *NetworkingTestConfig) GetSelfURLStatusCode(ctx context.Context, port int32, path string, expected string) {
   554  	// check status code
   555  	cmd := fmt.Sprintf("curl -o /dev/null -i -q -s -w %%{http_code} --connect-timeout 1 http://localhost:%d%s", port, path)
   556  	ginkgo.By(fmt.Sprintf("Checking status code against http://localhost:%d%s", port, path))
   557  	config.executeCurlCmd(ctx, cmd, expected)
   558  }
   559  
   560  func (config *NetworkingTestConfig) executeCurlCmd(ctx context.Context, cmd string, expected string) {
   561  	// These are arbitrary timeouts. The curl command should pass on first try,
   562  	// unless remote server is starved/bootstrapping/restarting etc.
   563  	const retryInterval = 1 * time.Second
   564  	const retryTimeout = 30 * time.Second
   565  	podName := config.HostTestContainerPod.Name
   566  	var msg string
   567  	if pollErr := wait.PollUntilContextTimeout(ctx, retryInterval, retryTimeout, true, func(ctx context.Context) (bool, error) {
   568  		stdout, err := e2epodoutput.RunHostCmd(config.Namespace, podName, cmd)
   569  		if err != nil {
   570  			msg = fmt.Sprintf("failed executing cmd %v in %v/%v: %v", cmd, config.Namespace, podName, err)
   571  			framework.Logf(msg)
   572  			return false, nil
   573  		}
   574  		if !strings.Contains(stdout, expected) {
   575  			msg = fmt.Sprintf("successfully executed %v in %v/%v, but output '%v' doesn't contain expected string '%v'", cmd, config.Namespace, podName, stdout, expected)
   576  			framework.Logf(msg)
   577  			return false, nil
   578  		}
   579  		return true, nil
   580  	}); pollErr != nil {
   581  		framework.Logf("\nOutput of kubectl describe pod %v/%v:\n", config.Namespace, podName)
   582  		desc, _ := e2ekubectl.RunKubectl(
   583  			config.Namespace, "describe", "pod", podName, fmt.Sprintf("--namespace=%v", config.Namespace))
   584  		framework.Logf("%s", desc)
   585  		framework.Failf("Timed out in %v: %v", retryTimeout, msg)
   586  	}
   587  }
   588  
   589  func (config *NetworkingTestConfig) createNetShellPodSpec(podName, hostname string) *v1.Pod {
   590  	netexecArgs := []string{
   591  		"netexec",
   592  		fmt.Sprintf("--http-port=%d", EndpointHTTPPort),
   593  		fmt.Sprintf("--udp-port=%d", EndpointUDPPort),
   594  	}
   595  	// In case of hostnetwork endpoints, we want to bind the udp listener to specific ip addresses.
   596  	// In order to cover legacy AND dualstack, we pass both the host ip and the two pod ips. Agnhost
   597  	// removes duplicates and so this will listen on both addresses (or on the single existing one).
   598  	if config.EndpointsHostNetwork {
   599  		netexecArgs = append(netexecArgs, "--udp-listen-addresses=$(HOST_IP),$(POD_IPS)")
   600  	}
   601  
   602  	probe := &v1.Probe{
   603  		InitialDelaySeconds: 10,
   604  		TimeoutSeconds:      30,
   605  		PeriodSeconds:       10,
   606  		SuccessThreshold:    1,
   607  		FailureThreshold:    3,
   608  		ProbeHandler: v1.ProbeHandler{
   609  			HTTPGet: &v1.HTTPGetAction{
   610  				Path: "/healthz",
   611  				Port: intstr.IntOrString{IntVal: EndpointHTTPPort},
   612  			},
   613  		},
   614  	}
   615  	pod := &v1.Pod{
   616  		TypeMeta: metav1.TypeMeta{
   617  			Kind:       "Pod",
   618  			APIVersion: "v1",
   619  		},
   620  		ObjectMeta: metav1.ObjectMeta{
   621  			Name:      podName,
   622  			Namespace: config.Namespace,
   623  		},
   624  		Spec: v1.PodSpec{
   625  			Containers: []v1.Container{
   626  				{
   627  					Name:            "webserver",
   628  					Image:           imageutils.GetE2EImage(imageutils.Agnhost),
   629  					ImagePullPolicy: v1.PullIfNotPresent,
   630  					Args:            netexecArgs,
   631  					Ports: []v1.ContainerPort{
   632  						{
   633  							Name:          "http",
   634  							ContainerPort: EndpointHTTPPort,
   635  						},
   636  						{
   637  							Name:          "udp",
   638  							ContainerPort: EndpointUDPPort,
   639  							Protocol:      v1.ProtocolUDP,
   640  						},
   641  					},
   642  					LivenessProbe:  probe,
   643  					ReadinessProbe: probe,
   644  				},
   645  			},
   646  			NodeSelector: map[string]string{
   647  				"kubernetes.io/hostname": hostname,
   648  			},
   649  		},
   650  	}
   651  	// we want sctp to be optional as it will load the sctp kernel module
   652  	if config.SCTPEnabled {
   653  		pod.Spec.Containers[0].Args = append(pod.Spec.Containers[0].Args, fmt.Sprintf("--sctp-port=%d", EndpointSCTPPort))
   654  		pod.Spec.Containers[0].Ports = append(pod.Spec.Containers[0].Ports, v1.ContainerPort{
   655  			Name:          "sctp",
   656  			ContainerPort: EndpointSCTPPort,
   657  			Protocol:      v1.ProtocolSCTP,
   658  		})
   659  	}
   660  
   661  	if config.EndpointsHostNetwork {
   662  		pod.Spec.Containers[0].Env = []v1.EnvVar{
   663  			{
   664  				Name: "HOST_IP",
   665  				ValueFrom: &v1.EnvVarSource{
   666  					FieldRef: &v1.ObjectFieldSelector{
   667  						FieldPath: "status.hostIP",
   668  					},
   669  				},
   670  			},
   671  			{
   672  				Name: "POD_IPS",
   673  				ValueFrom: &v1.EnvVarSource{
   674  					FieldRef: &v1.ObjectFieldSelector{
   675  						FieldPath: "status.podIPs",
   676  					},
   677  				},
   678  			},
   679  		}
   680  	}
   681  	return pod
   682  }
   683  
   684  func (config *NetworkingTestConfig) createTestPodSpec() *v1.Pod {
   685  	pod := &v1.Pod{
   686  		TypeMeta: metav1.TypeMeta{
   687  			Kind:       "Pod",
   688  			APIVersion: "v1",
   689  		},
   690  		ObjectMeta: metav1.ObjectMeta{
   691  			Name:      testPodName,
   692  			Namespace: config.Namespace,
   693  		},
   694  		Spec: v1.PodSpec{
   695  			Containers: []v1.Container{
   696  				{
   697  					Name:            "webserver",
   698  					Image:           imageutils.GetE2EImage(imageutils.Agnhost),
   699  					ImagePullPolicy: v1.PullIfNotPresent,
   700  					Args: []string{
   701  						"netexec",
   702  						fmt.Sprintf("--http-port=%d", testContainerHTTPPort),
   703  					},
   704  					Ports: []v1.ContainerPort{
   705  						{
   706  							Name:          "http",
   707  							ContainerPort: testContainerHTTPPort,
   708  						},
   709  					},
   710  				},
   711  			},
   712  		},
   713  	}
   714  	return pod
   715  }
   716  
   717  func (config *NetworkingTestConfig) createNodePortServiceSpec(svcName string, selector map[string]string, enableSessionAffinity bool) *v1.Service {
   718  	sessionAffinity := v1.ServiceAffinityNone
   719  	if enableSessionAffinity {
   720  		sessionAffinity = v1.ServiceAffinityClientIP
   721  	}
   722  	res := &v1.Service{
   723  		ObjectMeta: metav1.ObjectMeta{
   724  			Name: svcName,
   725  		},
   726  		Spec: v1.ServiceSpec{
   727  			Type: v1.ServiceTypeNodePort,
   728  			Ports: []v1.ServicePort{
   729  				{Port: ClusterHTTPPort, Name: "http", Protocol: v1.ProtocolTCP, TargetPort: intstr.FromInt32(EndpointHTTPPort)},
   730  				{Port: ClusterUDPPort, Name: "udp", Protocol: v1.ProtocolUDP, TargetPort: intstr.FromInt32(EndpointUDPPort)},
   731  			},
   732  			Selector:        selector,
   733  			SessionAffinity: sessionAffinity,
   734  		},
   735  	}
   736  
   737  	if config.SCTPEnabled {
   738  		res.Spec.Ports = append(res.Spec.Ports, v1.ServicePort{Port: ClusterSCTPPort, Name: "sctp", Protocol: v1.ProtocolSCTP, TargetPort: intstr.FromInt32(EndpointSCTPPort)})
   739  	}
   740  	if config.DualStackEnabled {
   741  		requireDual := v1.IPFamilyPolicyRequireDualStack
   742  		res.Spec.IPFamilyPolicy = &requireDual
   743  	}
   744  	return res
   745  }
   746  
   747  func (config *NetworkingTestConfig) createNodePortService(ctx context.Context, selector map[string]string) {
   748  	config.NodePortService = config.CreateService(ctx, config.createNodePortServiceSpec(nodePortServiceName, selector, false))
   749  }
   750  
   751  func (config *NetworkingTestConfig) createSessionAffinityService(ctx context.Context, selector map[string]string) {
   752  	config.SessionAffinityService = config.CreateService(ctx, config.createNodePortServiceSpec(sessionAffinityServiceName, selector, true))
   753  }
   754  
   755  // DeleteNodePortService deletes NodePort service.
   756  func (config *NetworkingTestConfig) DeleteNodePortService(ctx context.Context) {
   757  	err := config.getServiceClient().Delete(ctx, config.NodePortService.Name, metav1.DeleteOptions{})
   758  	framework.ExpectNoError(err, "error while deleting NodePortService. err:%v)", err)
   759  	time.Sleep(15 * time.Second) // wait for kube-proxy to catch up with the service being deleted.
   760  }
   761  
   762  func (config *NetworkingTestConfig) createTestPods(ctx context.Context) {
   763  	testContainerPod := config.createTestPodSpec()
   764  	hostTestContainerPod := e2epod.NewExecPodSpec(config.Namespace, hostTestPodName, config.HostNetwork)
   765  
   766  	config.createPod(ctx, testContainerPod)
   767  	if config.HostNetwork {
   768  		config.createPod(ctx, hostTestContainerPod)
   769  	}
   770  
   771  	framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, config.f.ClientSet, testContainerPod.Name, config.f.Namespace.Name))
   772  
   773  	var err error
   774  	config.TestContainerPod, err = config.getPodClient().Get(ctx, testContainerPod.Name, metav1.GetOptions{})
   775  	if err != nil {
   776  		framework.Failf("Failed to retrieve %s pod: %v", testContainerPod.Name, err)
   777  	}
   778  
   779  	if config.HostNetwork {
   780  		framework.ExpectNoError(e2epod.WaitForPodNameRunningInNamespace(ctx, config.f.ClientSet, hostTestContainerPod.Name, config.f.Namespace.Name))
   781  		config.HostTestContainerPod, err = config.getPodClient().Get(ctx, hostTestContainerPod.Name, metav1.GetOptions{})
   782  		if err != nil {
   783  			framework.Failf("Failed to retrieve %s pod: %v", hostTestContainerPod.Name, err)
   784  		}
   785  	}
   786  }
   787  
   788  // CreateService creates the provided service in config.Namespace and returns created service
   789  func (config *NetworkingTestConfig) CreateService(ctx context.Context, serviceSpec *v1.Service) *v1.Service {
   790  	_, err := config.getServiceClient().Create(ctx, serviceSpec, metav1.CreateOptions{})
   791  	framework.ExpectNoError(err, fmt.Sprintf("Failed to create %s service: %v", serviceSpec.Name, err))
   792  
   793  	err = WaitForService(ctx, config.f.ClientSet, config.Namespace, serviceSpec.Name, true, 5*time.Second, 45*time.Second)
   794  	framework.ExpectNoError(err, fmt.Sprintf("error while waiting for service:%s err: %v", serviceSpec.Name, err))
   795  
   796  	createdService, err := config.getServiceClient().Get(ctx, serviceSpec.Name, metav1.GetOptions{})
   797  	framework.ExpectNoError(err, fmt.Sprintf("Failed to create %s service: %v", serviceSpec.Name, err))
   798  
   799  	return createdService
   800  }
   801  
   802  // setupCore sets up the pods and core test config
   803  // mainly for simplified node e2e setup
   804  func (config *NetworkingTestConfig) setupCore(ctx context.Context, selector map[string]string) {
   805  	ginkgo.By("Creating the service pods in kubernetes")
   806  	podName := "netserver"
   807  	config.EndpointPods = config.createNetProxyPods(ctx, podName, selector)
   808  
   809  	ginkgo.By("Creating test pods")
   810  	config.createTestPods(ctx)
   811  
   812  	epCount := len(config.EndpointPods)
   813  
   814  	// Note that this is not O(n^2) in practice, because epCount SHOULD be < 10.  In cases that epCount is > 10, this would be prohibitively large.
   815  	// Check maxNetProxyPodsCount for details.
   816  	config.MaxTries = epCount*epCount + testTries
   817  	framework.Logf("Setting MaxTries for pod polling to %v for networking test based on endpoint count %v", config.MaxTries, epCount)
   818  }
   819  
   820  // setup includes setupCore and also sets up services
   821  func (config *NetworkingTestConfig) setup(ctx context.Context, selector map[string]string) {
   822  	config.setupCore(ctx, selector)
   823  
   824  	ginkgo.By("Getting node addresses")
   825  	framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, config.f.ClientSet, 10*time.Minute))
   826  	nodeList, err := e2enode.GetReadySchedulableNodes(ctx, config.f.ClientSet)
   827  	framework.ExpectNoError(err)
   828  
   829  	e2eskipper.SkipUnlessNodeCountIsAtLeast(2)
   830  	config.Nodes = nodeList.Items
   831  
   832  	ginkgo.By("Creating the service on top of the pods in kubernetes")
   833  	config.createNodePortService(ctx, selector)
   834  	config.createSessionAffinityService(ctx, selector)
   835  
   836  	for _, p := range config.NodePortService.Spec.Ports {
   837  		switch p.Protocol {
   838  		case v1.ProtocolUDP:
   839  			config.NodeUDPPort = int(p.NodePort)
   840  		case v1.ProtocolTCP:
   841  			config.NodeHTTPPort = int(p.NodePort)
   842  		case v1.ProtocolSCTP:
   843  			config.NodeSCTPPort = int(p.NodePort)
   844  		default:
   845  			continue
   846  		}
   847  	}
   848  
   849  	// obtain the ClusterIP
   850  	config.ClusterIP = config.NodePortService.Spec.ClusterIP
   851  	if config.DualStackEnabled {
   852  		config.SecondaryClusterIP = config.NodePortService.Spec.ClusterIPs[1]
   853  	}
   854  
   855  	// Obtain the primary IP family of the Cluster based on the first ClusterIP
   856  	// TODO: Eventually we should just be getting these from Spec.IPFamilies
   857  	// but for now that would only if the feature gate is enabled.
   858  	family := v1.IPv4Protocol
   859  	secondaryFamily := v1.IPv6Protocol
   860  	if netutils.IsIPv6String(config.ClusterIP) {
   861  		family = v1.IPv6Protocol
   862  		secondaryFamily = v1.IPv4Protocol
   863  	}
   864  	if config.PreferExternalAddresses {
   865  		// Get Node IPs from the cluster, ExternalIPs take precedence
   866  		config.NodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeExternalIP, family)
   867  	}
   868  	if config.NodeIP == "" {
   869  		config.NodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeInternalIP, family)
   870  	}
   871  	if config.DualStackEnabled {
   872  		if config.PreferExternalAddresses {
   873  			config.SecondaryNodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeExternalIP, secondaryFamily)
   874  		}
   875  		if config.SecondaryNodeIP == "" {
   876  			config.SecondaryNodeIP = e2enode.FirstAddressByTypeAndFamily(nodeList, v1.NodeInternalIP, secondaryFamily)
   877  		}
   878  	}
   879  
   880  	ginkgo.By("Waiting for NodePort service to expose endpoint")
   881  	err = framework.WaitForServiceEndpointsNum(ctx, config.f.ClientSet, config.Namespace, nodePortServiceName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout)
   882  	framework.ExpectNoError(err, "failed to validate endpoints for service %s in namespace: %s", nodePortServiceName, config.Namespace)
   883  	ginkgo.By("Waiting for Session Affinity service to expose endpoint")
   884  	err = framework.WaitForServiceEndpointsNum(ctx, config.f.ClientSet, config.Namespace, sessionAffinityServiceName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout)
   885  	framework.ExpectNoError(err, "failed to validate endpoints for service %s in namespace: %s", sessionAffinityServiceName, config.Namespace)
   886  }
   887  
   888  func (config *NetworkingTestConfig) createNetProxyPods(ctx context.Context, podName string, selector map[string]string) []*v1.Pod {
   889  	framework.ExpectNoError(e2enode.WaitForAllNodesSchedulable(ctx, config.f.ClientSet, 10*time.Minute))
   890  	nodeList, err := e2enode.GetBoundedReadySchedulableNodes(ctx, config.f.ClientSet, maxNetProxyPodsCount)
   891  	framework.ExpectNoError(err)
   892  	nodes := nodeList.Items
   893  
   894  	// create pods, one for each node
   895  	createdPods := make([]*v1.Pod, 0, len(nodes))
   896  	for i, n := range nodes {
   897  		podName := fmt.Sprintf("%s-%d", podName, i)
   898  		hostname, _ := n.Labels["kubernetes.io/hostname"]
   899  		pod := config.createNetShellPodSpec(podName, hostname)
   900  		pod.ObjectMeta.Labels = selector
   901  		pod.Spec.HostNetwork = config.EndpointsHostNetwork
   902  
   903  		// NOTE(claudiub): In order to use HostNetwork on Windows, we need to use Privileged Containers.
   904  		if pod.Spec.HostNetwork && framework.NodeOSDistroIs("windows") {
   905  			e2epod.WithWindowsHostProcess(pod, "")
   906  		}
   907  		createdPod := config.createPod(ctx, pod)
   908  		createdPods = append(createdPods, createdPod)
   909  	}
   910  
   911  	// wait that all of them are up
   912  	runningPods := make([]*v1.Pod, 0, len(nodes))
   913  	for _, p := range createdPods {
   914  		framework.ExpectNoError(e2epod.WaitTimeoutForPodReadyInNamespace(ctx, config.f.ClientSet, p.Name, config.f.Namespace.Name, framework.PodStartTimeout))
   915  		rp, err := config.getPodClient().Get(ctx, p.Name, metav1.GetOptions{})
   916  		framework.ExpectNoError(err)
   917  		runningPods = append(runningPods, rp)
   918  	}
   919  
   920  	return runningPods
   921  }
   922  
   923  // DeleteNetProxyPod deletes the first endpoint pod and waits for it being removed.
   924  func (config *NetworkingTestConfig) DeleteNetProxyPod(ctx context.Context) {
   925  	pod := config.EndpointPods[0]
   926  	framework.ExpectNoError(config.getPodClient().Delete(ctx, pod.Name, metav1.DeleteOptions{}))
   927  	config.EndpointPods = config.EndpointPods[1:]
   928  	// wait for pod being deleted.
   929  	err := e2epod.WaitForPodNotFoundInNamespace(ctx, config.f.ClientSet, pod.Name, config.Namespace, config.f.Timeouts.PodDelete)
   930  	if err != nil {
   931  		framework.Failf("Failed to delete %s pod: %v", pod.Name, err)
   932  	}
   933  	// wait for endpoint being removed.
   934  	err = framework.WaitForServiceEndpointsNum(ctx, config.f.ClientSet, config.Namespace, nodePortServiceName, len(config.EndpointPods), time.Second, wait.ForeverTestTimeout)
   935  	if err != nil {
   936  		framework.Failf("Failed to remove endpoint from service: %s", nodePortServiceName)
   937  	}
   938  	// wait for kube-proxy to catch up with the pod being deleted.
   939  	time.Sleep(5 * time.Second)
   940  }
   941  
   942  func (config *NetworkingTestConfig) createPod(ctx context.Context, pod *v1.Pod) *v1.Pod {
   943  	return config.getPodClient().Create(ctx, pod)
   944  }
   945  
   946  func (config *NetworkingTestConfig) getPodClient() *e2epod.PodClient {
   947  	if config.podClient == nil {
   948  		config.podClient = e2epod.NewPodClient(config.f)
   949  	}
   950  	return config.podClient
   951  }
   952  
   953  func (config *NetworkingTestConfig) getServiceClient() coreclientset.ServiceInterface {
   954  	return config.f.ClientSet.CoreV1().Services(config.Namespace)
   955  }
   956  
   957  // HTTPPokeParams is a struct for HTTP poke parameters.
   958  type HTTPPokeParams struct {
   959  	Timeout        time.Duration // default = 10 secs
   960  	ExpectCode     int           // default = 200
   961  	BodyContains   string
   962  	RetriableCodes []int
   963  	EnableHTTPS    bool
   964  }
   965  
   966  // HTTPPokeResult is a struct for HTTP poke result.
   967  type HTTPPokeResult struct {
   968  	Status HTTPPokeStatus
   969  	Code   int    // HTTP code: 0 if the connection was not made
   970  	Error  error  // if there was any error
   971  	Body   []byte // if code != 0
   972  }
   973  
   974  // HTTPPokeStatus is string for representing HTTP poke status.
   975  type HTTPPokeStatus string
   976  
   977  const (
   978  	// HTTPSuccess is HTTP poke status which is success.
   979  	HTTPSuccess HTTPPokeStatus = "Success"
   980  	// HTTPError is HTTP poke status which is error.
   981  	HTTPError HTTPPokeStatus = "UnknownError"
   982  	// HTTPTimeout is HTTP poke status which is timeout.
   983  	HTTPTimeout HTTPPokeStatus = "TimedOut"
   984  	// HTTPRefused is HTTP poke status which is connection refused.
   985  	HTTPRefused HTTPPokeStatus = "ConnectionRefused"
   986  	// HTTPRetryCode is HTTP poke status which is retry code.
   987  	HTTPRetryCode HTTPPokeStatus = "RetryCode"
   988  	// HTTPWrongCode is HTTP poke status which is wrong code.
   989  	HTTPWrongCode HTTPPokeStatus = "WrongCode"
   990  	// HTTPBadResponse is HTTP poke status which is bad response.
   991  	HTTPBadResponse HTTPPokeStatus = "BadResponse"
   992  	// Any time we add new errors, we should audit all callers of this.
   993  )
   994  
   995  // PokeHTTP tries to connect to a host on a port for a given URL path.  Callers
   996  // can specify additional success parameters, if desired.
   997  //
   998  // The result status will be characterized as precisely as possible, given the
   999  // known users of this.
  1000  //
  1001  // The result code will be zero in case of any failure to connect, or non-zero
  1002  // if the HTTP transaction completed (even if the other test params make this a
  1003  // failure).
  1004  //
  1005  // The result error will be populated for any status other than Success.
  1006  //
  1007  // The result body will be populated if the HTTP transaction was completed, even
  1008  // if the other test params make this a failure).
  1009  func PokeHTTP(host string, port int, path string, params *HTTPPokeParams) HTTPPokeResult {
  1010  	// Set default params.
  1011  	if params == nil {
  1012  		params = &HTTPPokeParams{}
  1013  	}
  1014  
  1015  	hostPort := net.JoinHostPort(host, strconv.Itoa(port))
  1016  	var url string
  1017  	if params.EnableHTTPS {
  1018  		url = fmt.Sprintf("https://%s%s", hostPort, path)
  1019  	} else {
  1020  		url = fmt.Sprintf("http://%s%s", hostPort, path)
  1021  	}
  1022  
  1023  	ret := HTTPPokeResult{}
  1024  
  1025  	// Sanity check inputs, because it has happened.  These are the only things
  1026  	// that should hard fail the test - they are basically ASSERT()s.
  1027  	if host == "" {
  1028  		framework.Failf("Got empty host for HTTP poke (%s)", url)
  1029  		return ret
  1030  	}
  1031  	if port == 0 {
  1032  		framework.Failf("Got port==0 for HTTP poke (%s)", url)
  1033  		return ret
  1034  	}
  1035  
  1036  	if params.ExpectCode == 0 {
  1037  		params.ExpectCode = http.StatusOK
  1038  	}
  1039  
  1040  	if params.Timeout == 0 {
  1041  		params.Timeout = 10 * time.Second
  1042  	}
  1043  
  1044  	framework.Logf("Poking %q", url)
  1045  
  1046  	resp, err := httpGetNoConnectionPoolTimeout(url, params.Timeout)
  1047  	if err != nil {
  1048  		ret.Error = err
  1049  		neterr, ok := err.(net.Error)
  1050  		if ok && neterr.Timeout() {
  1051  			ret.Status = HTTPTimeout
  1052  		} else if strings.Contains(err.Error(), "connection refused") {
  1053  			ret.Status = HTTPRefused
  1054  		} else {
  1055  			ret.Status = HTTPError
  1056  		}
  1057  		framework.Logf("Poke(%q): %v", url, err)
  1058  		return ret
  1059  	}
  1060  
  1061  	ret.Code = resp.StatusCode
  1062  
  1063  	defer resp.Body.Close()
  1064  	body, err := io.ReadAll(resp.Body)
  1065  	if err != nil {
  1066  		ret.Status = HTTPError
  1067  		ret.Error = fmt.Errorf("error reading HTTP body: %w", err)
  1068  		framework.Logf("Poke(%q): %v", url, ret.Error)
  1069  		return ret
  1070  	}
  1071  	ret.Body = make([]byte, len(body))
  1072  	copy(ret.Body, body)
  1073  
  1074  	if resp.StatusCode != params.ExpectCode {
  1075  		for _, code := range params.RetriableCodes {
  1076  			if resp.StatusCode == code {
  1077  				ret.Error = fmt.Errorf("retriable status code: %d", resp.StatusCode)
  1078  				ret.Status = HTTPRetryCode
  1079  				framework.Logf("Poke(%q): %v", url, ret.Error)
  1080  				return ret
  1081  			}
  1082  		}
  1083  		ret.Status = HTTPWrongCode
  1084  		ret.Error = fmt.Errorf("bad status code: %d", resp.StatusCode)
  1085  		framework.Logf("Poke(%q): %v", url, ret.Error)
  1086  		return ret
  1087  	}
  1088  
  1089  	if params.BodyContains != "" && !strings.Contains(string(body), params.BodyContains) {
  1090  		ret.Status = HTTPBadResponse
  1091  		ret.Error = fmt.Errorf("response does not contain expected substring: %q", string(body))
  1092  		framework.Logf("Poke(%q): %v", url, ret.Error)
  1093  		return ret
  1094  	}
  1095  
  1096  	ret.Status = HTTPSuccess
  1097  	framework.Logf("Poke(%q): success", url)
  1098  	return ret
  1099  }
  1100  
  1101  // Does an HTTP GET, but does not reuse TCP connections
  1102  // This masks problems where the iptables rule has changed, but we don't see it
  1103  func httpGetNoConnectionPoolTimeout(url string, timeout time.Duration) (*http.Response, error) {
  1104  	tr := utilnet.SetTransportDefaults(&http.Transport{
  1105  		DisableKeepAlives: true,
  1106  		TLSClientConfig:   &tls.Config{InsecureSkipVerify: true},
  1107  	})
  1108  	client := &http.Client{
  1109  		Transport: tr,
  1110  		Timeout:   timeout,
  1111  	}
  1112  
  1113  	return client.Get(url)
  1114  }
  1115  
  1116  // TestUnderTemporaryNetworkFailure blocks outgoing network traffic on 'node'. Then runs testFunc and returns its status.
  1117  // At the end (even in case of errors), the network traffic is brought back to normal.
  1118  // This function executes commands on a node so it will work only for some
  1119  // environments.
  1120  func TestUnderTemporaryNetworkFailure(ctx context.Context, c clientset.Interface, ns string, node *v1.Node, testFunc func(ctx context.Context)) {
  1121  	host, err := e2enode.GetSSHExternalIP(node)
  1122  	if err != nil {
  1123  		framework.Failf("Error getting node external ip : %v", err)
  1124  	}
  1125  	controlPlaneAddresses := framework.GetControlPlaneAddresses(ctx, c)
  1126  	ginkgo.By(fmt.Sprintf("block network traffic from node %s to the control plane", node.Name))
  1127  	defer func() {
  1128  		// This code will execute even if setting the iptables rule failed.
  1129  		// It is on purpose because we may have an error even if the new rule
  1130  		// had been inserted. (yes, we could look at the error code and ssh error
  1131  		// separately, but I prefer to stay on the safe side).
  1132  		ginkgo.By(fmt.Sprintf("Unblock network traffic from node %s to the control plane", node.Name))
  1133  		for _, instanceAddress := range controlPlaneAddresses {
  1134  			UnblockNetwork(ctx, host, instanceAddress)
  1135  		}
  1136  	}()
  1137  
  1138  	framework.Logf("Waiting %v to ensure node %s is ready before beginning test...", resizeNodeReadyTimeout, node.Name)
  1139  	if !e2enode.WaitConditionToBe(ctx, c, node.Name, v1.NodeReady, true, resizeNodeReadyTimeout) {
  1140  		framework.Failf("Node %s did not become ready within %v", node.Name, resizeNodeReadyTimeout)
  1141  	}
  1142  	for _, instanceAddress := range controlPlaneAddresses {
  1143  		BlockNetwork(ctx, host, instanceAddress)
  1144  	}
  1145  
  1146  	framework.Logf("Waiting %v for node %s to be not ready after simulated network failure", resizeNodeNotReadyTimeout, node.Name)
  1147  	if !e2enode.WaitConditionToBe(ctx, c, node.Name, v1.NodeReady, false, resizeNodeNotReadyTimeout) {
  1148  		framework.Failf("Node %s did not become not-ready within %v", node.Name, resizeNodeNotReadyTimeout)
  1149  	}
  1150  
  1151  	testFunc(ctx)
  1152  	// network traffic is unblocked in a deferred function
  1153  }
  1154  
  1155  // BlockNetwork blocks network between the given from value and the given to value.
  1156  // The following helper functions can block/unblock network from source
  1157  // host to destination host by manipulating iptable rules.
  1158  // This function assumes it can ssh to the source host.
  1159  //
  1160  // Caution:
  1161  // Recommend to input IP instead of hostnames. Using hostnames will cause iptables to
  1162  // do a DNS lookup to resolve the name to an IP address, which will
  1163  // slow down the test and cause it to fail if DNS is absent or broken.
  1164  //
  1165  // Suggested usage pattern:
  1166  //
  1167  //	func foo() {
  1168  //		...
  1169  //		defer UnblockNetwork(from, to)
  1170  //		BlockNetwork(from, to)
  1171  //		...
  1172  //	}
  1173  func BlockNetwork(ctx context.Context, from string, to string) {
  1174  	framework.Logf("block network traffic from %s to %s", from, to)
  1175  	iptablesRule := fmt.Sprintf("OUTPUT --destination %s --jump REJECT", to)
  1176  	dropCmd := fmt.Sprintf("sudo iptables --insert %s", iptablesRule)
  1177  	if result, err := e2essh.SSH(ctx, dropCmd, from, framework.TestContext.Provider); result.Code != 0 || err != nil {
  1178  		e2essh.LogResult(result)
  1179  		framework.Failf("Unexpected error: %v", err)
  1180  	}
  1181  }
  1182  
  1183  // UnblockNetwork unblocks network between the given from value and the given to value.
  1184  func UnblockNetwork(ctx context.Context, from string, to string) {
  1185  	framework.Logf("Unblock network traffic from %s to %s", from, to)
  1186  	iptablesRule := fmt.Sprintf("OUTPUT --destination %s --jump REJECT", to)
  1187  	undropCmd := fmt.Sprintf("sudo iptables --delete %s", iptablesRule)
  1188  	// Undrop command may fail if the rule has never been created.
  1189  	// In such case we just lose 30 seconds, but the cluster is healthy.
  1190  	// But if the rule had been created and removing it failed, the node is broken and
  1191  	// not coming back. Subsequent tests will run or fewer nodes (some of the tests
  1192  	// may fail). Manual intervention is required in such case (recreating the
  1193  	// cluster solves the problem too).
  1194  	err := wait.PollWithContext(ctx, time.Millisecond*100, time.Second*30, func(ctx context.Context) (bool, error) {
  1195  		result, err := e2essh.SSH(ctx, undropCmd, from, framework.TestContext.Provider)
  1196  		if result.Code == 0 && err == nil {
  1197  			return true, nil
  1198  		}
  1199  		e2essh.LogResult(result)
  1200  		if err != nil {
  1201  			framework.Logf("Unexpected error: %v", err)
  1202  		}
  1203  		return false, nil
  1204  	})
  1205  	if err != nil {
  1206  		framework.Failf("Failed to remove the iptable REJECT rule. Manual intervention is "+
  1207  			"required on host %s: remove rule %s, if exists", from, iptablesRule)
  1208  	}
  1209  }
  1210  
  1211  // WaitForService waits until the service appears (exist == true), or disappears (exist == false)
  1212  func WaitForService(ctx context.Context, c clientset.Interface, namespace, name string, exist bool, interval, timeout time.Duration) error {
  1213  	err := wait.PollUntilContextTimeout(ctx, interval, timeout, true, func(ctx context.Context) (bool, error) {
  1214  		_, err := c.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
  1215  		switch {
  1216  		case err == nil:
  1217  			framework.Logf("Service %s in namespace %s found.", name, namespace)
  1218  			return exist, nil
  1219  		case apierrors.IsNotFound(err):
  1220  			framework.Logf("Service %s in namespace %s disappeared.", name, namespace)
  1221  			return !exist, nil
  1222  		case err != nil:
  1223  			framework.Logf("Non-retryable failure while getting service.")
  1224  			return false, err
  1225  		default:
  1226  			framework.Logf("Get service %s in namespace %s failed: %v", name, namespace, err)
  1227  			return false, nil
  1228  		}
  1229  	})
  1230  	if err != nil {
  1231  		stateMsg := map[bool]string{true: "to appear", false: "to disappear"}
  1232  		return fmt.Errorf("error waiting for service %s/%s %s: %w", namespace, name, stateMsg[exist], err)
  1233  	}
  1234  	return nil
  1235  }