From 9636a3e338ff05b1bbdd40a1fb237b82a19f6575 Mon Sep 17 00:00:00 2001 From: Martynas Pumputis Date: Fri, 21 Jun 2024 10:32:47 +0200 Subject: [PATCH] connectivity: Add pod-to-pod-no-frag The new test case is intended to check whether MTU is properly set in pod netns. The check sends an ICMP Echo with DF set and payload. The payload size is derived from pod MTU. No reply means that MTU is misconfigured somewhere in the packet path. Signed-off-by: Martynas Pumputis --- connectivity/builder/builder.go | 1 + connectivity/builder/no_fragmentation.go | 20 +++++ connectivity/builder/pod_to_pod_encryption.go | 1 - connectivity/tests/pod.go | 90 +++++++++++++++++++ 4 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 connectivity/builder/no_fragmentation.go diff --git a/connectivity/builder/builder.go b/connectivity/builder/builder.go index 3cae8ebbf9..6321cdfa77 100644 --- a/connectivity/builder/builder.go +++ b/connectivity/builder/builder.go @@ -238,6 +238,7 @@ func concurrentTests(connTests []*check.ConnectivityTest) error { podToControlplaneHostCidr{}, podToK8sOnControlplaneCidr{}, localRedirectPolicy{}, + noFragmentation{}, } return injectTests(tests, connTests...) } diff --git a/connectivity/builder/no_fragmentation.go b/connectivity/builder/no_fragmentation.go new file mode 100644 index 0000000000..99efdf3deb --- /dev/null +++ b/connectivity/builder/no_fragmentation.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Cilium + +package builder + +import ( + "github.com/cilium/cilium-cli/connectivity/check" + "github.com/cilium/cilium-cli/connectivity/tests" +) + +type noFragmentation struct{} + +func (t noFragmentation) build(ct *check.ConnectivityTest, _ map[string]string) { + newTest("pod-to-pod-no-frag", ct). + WithCondition(func() bool { return !ct.Params().SingleNode }). + WithScenarios( + tests.PodToPodNoFrag(), + ) + +} diff --git a/connectivity/builder/pod_to_pod_encryption.go b/connectivity/builder/pod_to_pod_encryption.go index f946df8297..caa8e17430 100644 --- a/connectivity/builder/pod_to_pod_encryption.go +++ b/connectivity/builder/pod_to_pod_encryption.go @@ -38,5 +38,4 @@ func (t podToPodEncryption) build(ct *check.ConnectivityTest, _ map[string]strin WithScenarios( tests.PodToPodEncryption(features.RequireEnabled(features.EncryptionPod)), ) - } diff --git a/connectivity/tests/pod.go b/connectivity/tests/pod.go index bec2b5fcc0..d005f4184c 100644 --- a/connectivity/tests/pod.go +++ b/connectivity/tests/pod.go @@ -6,11 +6,19 @@ package tests import ( "context" "fmt" + "strconv" + "strings" "github.com/cilium/cilium-cli/connectivity/check" "github.com/cilium/cilium-cli/utils/features" ) +const ( + HdrSizeICMPEcho = 8 + HdrSizeIPv4 = 20 + HdrSizeIPv6 = 40 +) + // PodToPod generates one HTTP request from each client pod // to each echo (server) pod in the test context. The remote Pod is contacted // directly, no DNS is involved. @@ -165,3 +173,85 @@ func (s *podToPodWithEndpoints) curlEndpoints(ctx context.Context, t *check.Test } } } + +// PodToPodNoFrag is a test to check whether a correct MTU is set +// for pods. The check is performed by sending an ICMP Echo request with DF +// set ("do not fragment"). The ICMP payload size of the request: +// +// - For IPv4: $POD_MTU - 20 (IPv4 hdr) - 8 (ICMP Echo hdr) +// - For IPv6: $POD_MTU - 40 (IPv6 hdr) - 8 (ICMP Echo hdr) +func PodToPodNoFrag() check.Scenario { + return &podToPodNoFrag{} +} + +type podToPodNoFrag struct{} + +func (s *podToPodNoFrag) Name() string { + return "pod-to-pod-no-frag" +} + +func (s *podToPodNoFrag) Run(ctx context.Context, t *check.Test) { + ct := t.Context() + client := ct.RandomClientPod() + var mtu int + + cmd := []string{ + "/bin/sh", "-c", + "ip route show default | grep -oE 'mtu [^ ]*' | cut -d' ' -f2", + } + t.Debugf("Running %s", strings.Join(cmd, " ")) + mtuBytes, err := client.K8sClient.ExecInPod(ctx, client.Pod.Namespace, + client.Pod.Name, "", cmd) + if err != nil { + t.Fatalf("Failed to get route MTU in pod %s: %s", client, err) + } + mtuStr := strings.TrimSpace(mtuBytes.String()) + + // Derive MTU from pod iface instead + if mtuStr == "" { + cmd := []string{ + "/bin/sh", "-c", + "cat /sys/class/net/eth0/mtu", + } + t.Debugf("Running %s", strings.Join(cmd, " ")) + mtuBytes, err = client.K8sClient.ExecInPod(ctx, client.Pod.Namespace, + client.Pod.Name, "", cmd) + if err != nil { + t.Fatalf("Failed to get eth0 MTU in pod %s: %s", client, err) + } + + mtuStr = strings.TrimSpace(mtuBytes.String()) + } + + mtu, err = strconv.Atoi(mtuStr) + if err != nil { + t.Fatalf("Failed to parse MTU %s: %s", mtuStr, err) + } + t.Debugf("Derived MTU: %d", mtu) + + var server check.Pod + for _, pod := range ct.EchoPods() { + // Make sure that the server pod is on another node than client + if pod.Pod.Status.HostIP != client.Pod.Status.HostIP { + server = pod + break + } + } + + t.ForEachIPFamily(func(ipFam features.IPFamily) { + t.NewAction(s, fmt.Sprintf("ping-%s", ipFam), client, server, ipFam).Run(func(a *check.Action) { + payloadSize := mtu - HdrSizeICMPEcho + switch ipFam { + case features.IPFamilyV4: + payloadSize -= HdrSizeIPv4 + case features.IPFamilyV6: + payloadSize -= HdrSizeIPv6 + } + a.ExecInPod(ctx, t.Context().PingCommand(server, ipFam, + "-M", "do", // DF + "-s", strconv.Itoa(payloadSize), // payload size + )) + }) + + }) +}