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 + )) + }) + + }) +}