diff --git a/cmd/antrea-agent/agent.go b/cmd/antrea-agent/agent.go index fba691bdd76..fdb99444e69 100644 --- a/cmd/antrea-agent/agent.go +++ b/cmd/antrea-agent/agent.go @@ -206,6 +206,7 @@ func run(o *Options) error { AuthenticationMode: ipsecAuthenticationMode, }, EnableMulticlusterGW: enableMulticlusterGW, + MulticlusterConfig: o.config.Multicluster, } wireguardConfig := &config.WireGuardConfig{ diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 791e18b890f..86b41009869 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -1194,10 +1194,6 @@ func (i *Initializer) getInterfaceMTU(transportInterface *net.Interface) (int, e isIPv6 := i.nodeConfig.NodeIPv6Addr != nil mtu -= i.networkConfig.CalculateMTUDeduction(isIPv6) - - if i.networkConfig.TrafficEncryptionMode == config.TrafficEncryptionModeIPSec { - mtu -= config.IPSecESPOverhead - } return mtu, nil } diff --git a/pkg/agent/config/node_config.go b/pkg/agent/config/node_config.go index ebba7f3da9e..2cca9b1a10e 100644 --- a/pkg/agent/config/node_config.go +++ b/pkg/agent/config/node_config.go @@ -17,7 +17,9 @@ package config import ( "fmt" "net" + "strings" + agentConfig "antrea.io/antrea/pkg/config/agent" "antrea.io/antrea/pkg/ovs/ovsconfig" ) @@ -201,7 +203,7 @@ type NetworkConfig struct { TransportIfaceCIDRs []string IPv4Enabled bool IPv6Enabled bool - // MTUDeduction only counts IPv4 tunnel overhead, no IPsec and WireGuard overhead. + // MTUDeduction is the MTU deduction for encapsulation and encryption in cluster. MTUDeduction int // Set by the defaultMTU config option or auto discovered. // Auto discovery will use MTU value of the Node's transport interface. @@ -209,6 +211,7 @@ type NetworkConfig struct { // encap header. InterfaceMTU int EnableMulticlusterGW bool + MulticlusterConfig agentConfig.MulticlusterConfig } // IsIPv4Enabled returns true if the cluster network supports IPv4. Legal cases are: @@ -265,23 +268,33 @@ func (nc *NetworkConfig) NeedsDirectRoutingToPeer(peerIP net.IP, localIP *net.IP } func (nc *NetworkConfig) CalculateMTUDeduction(isIPv6 bool) int { - var mtuDeduction int + // When WireGuard is enabled, NetworkConfig.TunnelType will be ignored, so we deduct MTU based on WireGuardOverhead. + if nc.TrafficEncryptionMode == TrafficEncryptionModeWireGuard { + nc.MTUDeduction = WireGuardOverhead + return nc.MTUDeduction + } else if nc.TrafficEncryptionMode == TrafficEncryptionModeIPSec { + nc.MTUDeduction = IPSecESPOverhead + } + // When Multi-cluster Gateway is enabled, we need to reduce MTU for potential cross-cluster traffic. if nc.TrafficEncapMode.SupportsEncap() || nc.EnableMulticlusterGW { if nc.TunnelType == ovsconfig.VXLANTunnel { - mtuDeduction = vxlanOverhead + nc.MTUDeduction += vxlanOverhead } else if nc.TunnelType == ovsconfig.GeneveTunnel { - mtuDeduction = geneveOverhead + nc.MTUDeduction += geneveOverhead } else if nc.TunnelType == ovsconfig.GRETunnel { - mtuDeduction = greOverhead + nc.MTUDeduction += greOverhead } } if nc.TrafficEncapMode.SupportsEncap() && isIPv6 { - mtuDeduction += ipv6ExtraOverhead + nc.MTUDeduction += ipv6ExtraOverhead + } + // When multi-cluster WireGuard is enabled, we need to reduce MTU for potential cross-cluster traffic. + if nc.EnableMulticlusterGW && strings.EqualFold(nc.MulticlusterConfig.TrafficEncryptionMode, TrafficEncryptionModeWireGuard.String()) { + nc.MTUDeduction += WireGuardOverhead } - nc.MTUDeduction = mtuDeduction - return mtuDeduction + return nc.MTUDeduction } // ServiceConfig includes K8s Service CIDR and available IP addresses for NodePort. diff --git a/pkg/agent/config/node_config_test.go b/pkg/agent/config/node_config_test.go index 081ce053c7f..2b26f0622a9 100644 --- a/pkg/agent/config/node_config_test.go +++ b/pkg/agent/config/node_config_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" + agentConfig "antrea.io/antrea/pkg/config/agent" "antrea.io/antrea/pkg/ovs/ovsconfig" ) @@ -306,6 +307,40 @@ func TestCalculateMTUDeduction(t *testing.T) { isIPv6: true, expectedMTUDeduction: 70, }, + { + name: "WireGuard enabled", + nc: &NetworkConfig{TrafficEncryptionMode: TrafficEncryptionModeWireGuard}, + expectedMTUDeduction: 80, + }, + { + name: "Multicluster enabled with Geneve encap", + nc: &NetworkConfig{TunnelType: ovsconfig.GeneveTunnel, EnableMulticlusterGW: true}, + expectedMTUDeduction: 50, + }, + { + name: "Geneve encap with Multicluster WireGuard enabled", + nc: &NetworkConfig{ + TunnelType: ovsconfig.GeneveTunnel, + EnableMulticlusterGW: true, + MulticlusterConfig: agentConfig.MulticlusterConfig{TrafficEncryptionMode: "wireGuard"}, + }, + expectedMTUDeduction: 130, + }, + { + name: "Geneve encap with IPSec enabled", + nc: &NetworkConfig{TunnelType: ovsconfig.GeneveTunnel, TrafficEncryptionMode: TrafficEncryptionModeIPSec}, + expectedMTUDeduction: 88, + }, + { + name: "VXLan encap with IPSec enabled", + nc: &NetworkConfig{TunnelType: ovsconfig.VXLANTunnel, TrafficEncryptionMode: TrafficEncryptionModeIPSec}, + expectedMTUDeduction: 88, + }, + { + name: "GRE encap with IPSec enabled", + nc: &NetworkConfig{TunnelType: ovsconfig.GRETunnel, TrafficEncryptionMode: TrafficEncryptionModeIPSec}, + expectedMTUDeduction: 76, + }, } for _, tt := range tests { diff --git a/pkg/agent/multicluster/mc_route_controller.go b/pkg/agent/multicluster/mc_route_controller.go index 568371329d5..e053c698afe 100644 --- a/pkg/agent/multicluster/mc_route_controller.go +++ b/pkg/agent/multicluster/mc_route_controller.go @@ -129,7 +129,7 @@ func NewMCDefaultRouteController( controller.wireGuardConfig = &config.WireGuardConfig{ Port: multiclusterConfig.WireGuard.Port, Name: multiclusterWireGuardInterface, - MTU: controller.nodeConfig.NodeTransportInterfaceMTU - controller.networkConfig.MTUDeduction - config.WireGuardOverhead, + MTU: controller.nodeConfig.NodeTransportInterfaceMTU - controller.networkConfig.MTUDeduction, } } controller.gwInformer.Informer().AddEventHandlerWithResyncPeriod( diff --git a/test/e2e-secondary-network/secondary_network_test.go b/test/e2e-secondary-network/secondary_network_test.go index e5caff7cd77..b67d2974ce4 100644 --- a/test/e2e-secondary-network/secondary_network_test.go +++ b/test/e2e-secondary-network/secondary_network_test.go @@ -198,7 +198,7 @@ func (data *TestData) pingBetweenInterfaces(t *testing.T) error { } else { IPToPing = antreae2e.PodIPs{IPv6: &ip} } - err := data.e2eTestData.RunPingCommandFromTestPod(antreae2e.PodInfo{Name: podData[sourcePod].nameOfPods, OS: osType, NodeName: clusterInfo.controlPlaneNodeName, Namespace: nameSpace}, nameSpace, &IPToPing, ctrName, count, size) + err := data.e2eTestData.RunPingCommandFromTestPod(antreae2e.PodInfo{Name: podData[sourcePod].nameOfPods, OS: osType, NodeName: clusterInfo.controlPlaneNodeName, Namespace: nameSpace}, nameSpace, &IPToPing, ctrName, count, size, true) if err == nil { logs.Infof("Ping '%s' -> '%s'( Interface: %s, IP Address: %s): OK", podData[sourcePod].nameOfPods, podData[targetPod].nameOfPods, podData[targetPod].nameOfInterfacePerPod[targetInterface], secondaryIpAddress) } else { diff --git a/test/e2e/antreaipam_test.go b/test/e2e/antreaipam_test.go index f1cec366f73..da59aa5c179 100644 --- a/test/e2e/antreaipam_test.go +++ b/test/e2e/antreaipam_test.go @@ -282,7 +282,7 @@ func testAntreaIPAMPodConnectivitySameNode(t *testing.T, data *TestData) { defer deletePodWrapper(t, data, PodInfos[i].Namespace, PodInfos[i].Name) } - data.runPingMesh(t, PodInfos, agnhostContainerName) + data.runPingMesh(t, PodInfos, agnhostContainerName, 0) } func testAntreaIPAMPodConnectivityDifferentNodes(t *testing.T, data *TestData) { @@ -296,7 +296,7 @@ func testAntreaIPAMPodConnectivityDifferentNodes(t *testing.T, data *TestData) { } PodInfos = append(PodInfos, createdPodInfos...) } - data.runPingMesh(t, PodInfos, agnhostContainerName) + data.runPingMesh(t, PodInfos, agnhostContainerName, 0) } func testAntreaIPAMStatefulSet(t *testing.T, data *TestData, dedicatedIPPoolKey *string) { diff --git a/test/e2e/connectivity_test.go b/test/e2e/connectivity_test.go index f2d42363384..91d97f22a29 100644 --- a/test/e2e/connectivity_test.go +++ b/test/e2e/connectivity_test.go @@ -28,10 +28,29 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "antrea.io/antrea/pkg/agent/config" + agentconfig "antrea.io/antrea/pkg/config/agent" + "antrea.io/antrea/pkg/features" "antrea.io/antrea/pkg/util/k8s" ) -const pingCount = 5 +const ( + pingCount = 5 + + MTUWithGENEVE = 1450 + MTUWithVXLAN = 1450 + MTUWithGENEVEAndMulticlusterWireguard = 1370 + MTUWithVXLANAndMulticlusterWireguard = 1370 + MTUWithGREAndMulticlusterWireguard = 1382 + + IPHeaderSize = 20 + ICMPHeaderSize = 8 +) + +type trafficConfig struct { + tunnelType string + multiclusterTrafficEncryptionMode string + mtu int +} // TestConnectivity is the top-level test which contains all subtests for // Connectivity related test cases so they can share setup, teardown. @@ -42,34 +61,74 @@ func TestConnectivity(t *testing.T) { } defer teardownTest(t, data) - t.Run("testPodConnectivityOnSameNode", func(t *testing.T) { - testPodConnectivityOnSameNode(t, data) - }) - t.Run("testHostPortPodConnectivity", func(t *testing.T) { - skipIfHasWindowsNodes(t) - testHostPortPodConnectivity(t, data) - }) - t.Run("testPodConnectivityDifferentNodes", func(t *testing.T) { - skipIfNumNodesLessThan(t, 2) - testPodConnectivityDifferentNodes(t, data) - }) - t.Run("testPodConnectivityAfterAntreaRestart", func(t *testing.T) { - skipIfHasWindowsNodes(t) - testPodConnectivityAfterAntreaRestart(t, data, data.testNamespace) - }) - t.Run("testOVSRestartSameNode", func(t *testing.T) { - skipIfNotIPv4Cluster(t) - skipIfHasWindowsNodes(t) - testOVSRestartSameNode(t, data, data.testNamespace) - }) - t.Run("testOVSFlowReplay", func(t *testing.T) { - skipIfHasWindowsNodes(t) - testOVSFlowReplay(t, data, data.testNamespace) - }) - t.Run("testPingLargeMTU", func(t *testing.T) { - skipIfNumNodesLessThan(t, 2) - testPingLargeMTU(t, data) + t.Run("testPodConnectivityWithDifferentTrafficConfig", func(t *testing.T) { + trafficConfigs := []trafficConfig{ + {tunnelType: "geneve", multiclusterTrafficEncryptionMode: "none", mtu: MTUWithGENEVE}, + {tunnelType: "vxlan", multiclusterTrafficEncryptionMode: "none", mtu: MTUWithVXLAN}, + {tunnelType: "geneve", multiclusterTrafficEncryptionMode: "wireguard", mtu: MTUWithGENEVEAndMulticlusterWireguard}, + {tunnelType: "vxlan", multiclusterTrafficEncryptionMode: "wireguard", mtu: MTUWithVXLANAndMulticlusterWireguard}, + {tunnelType: "gre", multiclusterTrafficEncryptionMode: "wireguard", mtu: MTUWithGREAndMulticlusterWireguard}, + } + testPodConnectivityWithDifferentTrafficConfig(t, data, trafficConfigs) }) + //t.Run("testHostPortPodConnectivity", func(t *testing.T) { + // skipIfHasWindowsNodes(t) + // testHostPortPodConnectivity(t, data) + //}) + //t.Run("testPodConnectivityAfterAntreaRestart", func(t *testing.T) { + // skipIfHasWindowsNodes(t) + // testPodConnectivityAfterAntreaRestart(t, data, data.testNamespace) + //}) + //t.Run("testOVSRestartSameNode", func(t *testing.T) { + // skipIfNotIPv4Cluster(t) + // skipIfHasWindowsNodes(t) + // testOVSRestartSameNode(t, data, data.testNamespace) + //}) + //t.Run("testOVSFlowReplay", func(t *testing.T) { + // skipIfHasWindowsNodes(t) + // testOVSFlowReplay(t, data, data.testNamespace) + //}) + //t.Run("testPingLargeMTU", func(t *testing.T) { + // skipIfNumNodesLessThan(t, 2) + // testPingLargeMTU(t, data) + //}) +} + +func testPodConnectivityWithDifferentTrafficConfig(t *testing.T, data *TestData, trafficConfigs []trafficConfig) { + var previousTunnelType string + var previousMulticlusterTrafficEncryptionMode string + var previousEnableMulticlusterGW bool + var previousMulticlusterEnable bool + defer func() { + ac := func(config *agentconfig.AgentConfig) { + config.TunnelType = previousTunnelType + config.Multicluster.TrafficEncryptionMode = previousMulticlusterTrafficEncryptionMode + config.Multicluster.EnableGateway = previousEnableMulticlusterGW + config.FeatureGates[string(features.Multicluster)] = previousMulticlusterEnable + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Errorf("Failed to enable tunnelType %s, multicluster.TrafficEncryptionMode %s: %v", previousTunnelType, previousMulticlusterTrafficEncryptionMode, err) + } + }() + for _, conf := range trafficConfigs { + t.Logf("Testing connectivity with TunnelType %s and multicluster.TrafficEncrptionMode %s", conf.tunnelType, conf.multiclusterTrafficEncryptionMode) + ac := func(config *agentconfig.AgentConfig) { + config.TunnelType = conf.tunnelType + config.Multicluster.TrafficEncryptionMode = conf.multiclusterTrafficEncryptionMode + config.Multicluster.EnableGateway = true + config.FeatureGates[string(features.Multicluster)] = true + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Fatalf("Failed to enable tunnelType %s, multicluster.TrafficEncryptionMode %s: %v", conf.tunnelType, conf.multiclusterTrafficEncryptionMode, err) + } + t.Run("testPodConnectivityOnSameNode", func(t *testing.T) { + testPodConnectivityOnSameNode(t, data, conf.mtu-IPHeaderSize-ICMPHeaderSize) + }) + t.Run("testPodConnectivityDifferentNodes", func(t *testing.T) { + skipIfNumNodesLessThan(t, 2) + testPodConnectivityDifferentNodes(t, data, conf.mtu-IPHeaderSize-ICMPHeaderSize) + }) + } } func waitForPodIPs(t *testing.T, data *TestData, podInfos []PodInfo) map[string]*PodIPs { @@ -92,8 +151,9 @@ func waitForPodIPs(t *testing.T, data *TestData, podInfos []PodInfo) map[string] } // runPingMesh runs a ping mesh between all the provided Pods after first retrieving their IP -// addresses. -func (data *TestData) runPingMesh(t *testing.T, podInfos []PodInfo, ctrname string) { +// addresses. If mtu is non-zero, the ping command will be run with the provided MTU value. +// If mtu is zero, the ping command will be run with the default MTU value. +func (data *TestData) runPingMesh(t *testing.T, podInfos []PodInfo, ctrname string, mtu int) { podIPs := waitForPodIPs(t, data, podInfos) t.Logf("Ping mesh test between all Pods") @@ -110,16 +170,33 @@ func (data *TestData) runPingMesh(t *testing.T, podInfos []PodInfo, ctrname stri if pi2.Namespace != "" { pod2Namespace = pi2.Namespace } - if err := data.RunPingCommandFromTestPod(pi1, podNamespace, podIPs[pi2.Name], ctrname, pingCount, 0); err != nil { - t.Errorf("Ping '%s' -> '%s': ERROR (%v)", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name), err) + + if mtu == 0 { + if err := data.RunPingCommandFromTestPod(pi1, podNamespace, podIPs[pi2.Name], ctrname, pingCount, 0, true); err != nil { + t.Errorf("Ping '%s' -> '%s': ERROR (%v)", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name), err) + } else { + t.Logf("Ping '%s' -> '%s': OK", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name)) + } } else { - t.Logf("Ping '%s' -> '%s': OK", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name)) + if err := data.RunPingCommandFromTestPod(pi1, podNamespace, podIPs[pi2.Name], ctrname, pingCount, mtu, false); err != nil { + t.Errorf("Ping '%s' -> '%s': ERROR (%v)", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name), err) + } else { + t.Logf("Ping '%s' -> '%s': OK", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name)) + } + if err := data.RunPingCommandFromTestPod(pi1, podNamespace, podIPs[pi2.Name], ctrname, pingCount, mtu+1, false); err == nil { + t.Errorf("Ping '%s' -> '%s': ERROR (expected to fail)", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name)) + } else { + if !strings.Contains(err.Error(), "too long") { + t.Errorf("Ping '%s' -> '%s': ERROR (%v)", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name), err) + } + t.Logf("Ping '%s' -> '%s': OK (expected to fail)", k8s.NamespacedName(podNamespace, pi1.Name), k8s.NamespacedName(pod2Namespace, pi2.Name)) + } } } } } -func (data *TestData) testPodConnectivitySameNode(t *testing.T) { +func (data *TestData) testPodConnectivitySameNode(t *testing.T, mtu int) { numPods := 2 // can be increased podInfos := make([]PodInfo, numPods) for idx := range podInfos { @@ -131,22 +208,22 @@ func (data *TestData) testPodConnectivitySameNode(t *testing.T) { workerNode = workerNodeName(clusterInfo.windowsNodes[0]) } - t.Logf("Creating %d agnhost Pods on '%s'", numPods, workerNode) + t.Logf("Creating %d toolbox Pods on '%s'", numPods, workerNode) for i := range podInfos { podInfos[i].OS = clusterInfo.nodesOS[workerNode] - if err := data.createAgnhostPodOnNode(podInfos[i].Name, data.testNamespace, workerNode, false); err != nil { - t.Fatalf("Error when creating agnhost test Pod '%s': %v", podInfos[i], err) + if err := data.createToolboxPodOnNode(podInfos[i].Name, data.testNamespace, workerNode, false); err != nil { + t.Fatalf("Error when creating toolbox test Pod '%s': %v", podInfos[i], err) } defer deletePodWrapper(t, data, data.testNamespace, podInfos[i].Name) } - data.runPingMesh(t, podInfos, agnhostContainerName) + data.runPingMesh(t, podInfos, toolboxContainerName, mtu) } // testPodConnectivityOnSameNode checks that Pods running on the same Node can reach each other, by // creating multiple Pods on the same Node and having them ping each other. -func testPodConnectivityOnSameNode(t *testing.T, data *TestData) { - data.testPodConnectivitySameNode(t) +func testPodConnectivityOnSameNode(t *testing.T, data *TestData, mtu int) { + data.testPodConnectivitySameNode(t, mtu) } func (data *TestData) testHostPortPodConnectivity(t *testing.T, clientNamespace, serverNamespace string) { @@ -185,13 +262,13 @@ func testHostPortPodConnectivity(t *testing.T, data *TestData) { data.testHostPortPodConnectivity(t, data.testNamespace, data.testNamespace) } -// createPodsOnDifferentNodes creates agnhost Pods through a DaemonSet. This function returns information of the created +// createPodsOnDifferentNodes creates toolbox Pods through a DaemonSet. This function returns information of the created // Pods as well as a function which will delete the Pods when called. Since Pods can be on Nodes of different OSes, podInfo // slice instead of PodName slice is used to inform caller of correct commands and options. Linux and Windows Pods are // alternating in this podInfo slice so that the test can cover different connectivity cases between different OSes. func createPodsOnDifferentNodes(t *testing.T, data *TestData, namespace, tag string) (podInfos []PodInfo, cleanup func() error) { dsName := "connectivity-test" + tag - _, deleteDaemonSet, err := data.createDaemonSet(dsName, namespace, agnhostContainerName, agnhostImage, []string{"sleep", "3600"}, nil) + _, deleteDaemonSet, err := data.createDaemonSet(dsName, namespace, toolboxContainerName, toolboxImage, []string{"sleep", "3600"}, nil) if err != nil { t.Fatalf("Error when creating DaemonSet '%s': %v", dsName, err) } @@ -247,7 +324,7 @@ func createPodsOnDifferentNodes(t *testing.T, data *TestData, namespace, tag str return podInfos, cleanup } -func (data *TestData) testPodConnectivityDifferentNodes(t *testing.T) { +func (data *TestData) testPodConnectivityDifferentNodes(t *testing.T, mtu int) { numPods, maxPods := 2, 3 encapMode, err := data.GetEncapMode() if err != nil { @@ -264,13 +341,13 @@ func (data *TestData) testPodConnectivityDifferentNodes(t *testing.T) { if len(podInfos) > maxPods { podInfos = podInfos[:maxPods] } - data.runPingMesh(t, podInfos[:numPods], agnhostContainerName) + data.runPingMesh(t, podInfos[:numPods], toolboxContainerName, mtu) } // testPodConnectivityDifferentNodes checks that Pods running on different Nodes can reach each // other, by creating multiple Pods across distinct Nodes and having them ping each other. -func testPodConnectivityDifferentNodes(t *testing.T, data *TestData) { - data.testPodConnectivityDifferentNodes(t) +func testPodConnectivityDifferentNodes(t *testing.T, data *TestData, mtu int) { + data.testPodConnectivityDifferentNodes(t, mtu) } func (data *TestData) redeployAntrea(t *testing.T, option deployAntreaOptions) { @@ -315,11 +392,11 @@ func testPodConnectivityAfterAntreaRestart(t *testing.T, data *TestData, namespa podInfos, deletePods := createPodsOnDifferentNodes(t, data, namespace, "antrearestart") defer deletePods() - data.runPingMesh(t, podInfos[:numPods], agnhostContainerName) + data.runPingMesh(t, podInfos[:numPods], agnhostContainerName, 0) data.redeployAntrea(t, deployAntreaDefault) - data.runPingMesh(t, podInfos[:numPods], agnhostContainerName) + data.runPingMesh(t, podInfos[:numPods], agnhostContainerName, 0) } // testOVSRestartSameNode verifies that datapath flows are not removed when the Antrea Agent Pod is @@ -405,7 +482,7 @@ func testOVSFlowReplay(t *testing.T, data *TestData, namespace string) { defer deletePodWrapper(t, data, namespace, podInfos[i].Name) } - data.runPingMesh(t, podInfos, busyboxContainerName) + data.runPingMesh(t, podInfos, busyboxContainerName, 0) var antreaPodName string var err error @@ -487,7 +564,7 @@ func testOVSFlowReplay(t *testing.T, data *TestData, namespace string) { // This should give Antrea ~10s to restore flows, since we generate 10 "pings" with a 1s // interval. t.Logf("Running second ping mesh to check that flows have been restored") - data.runPingMesh(t, podInfos, busyboxContainerName) + data.runPingMesh(t, podInfos, busyboxContainerName, 0) flows2, groups2 := dumpFlows(), dumpGroups() numFlows2, numGroups2 := len(flows2), len(groups2) @@ -515,7 +592,7 @@ func testPingLargeMTU(t *testing.T, data *TestData) { pingSize := 2000 t.Logf("Running ping with size %d between Pods %s and %s", pingSize, podInfos[0].Name, podInfos[1].Name) - if err := data.RunPingCommandFromTestPod(podInfos[0], data.testNamespace, podIPs[podInfos[1].Name], agnhostContainerName, pingCount, pingSize); err != nil { + if err := data.RunPingCommandFromTestPod(podInfos[0], data.testNamespace, podIPs[podInfos[1].Name], agnhostContainerName, pingCount, pingSize, true); err != nil { t.Error(err) } } diff --git a/test/e2e/framework.go b/test/e2e/framework.go index 8eba90cd07f..7696da66e0a 100644 --- a/test/e2e/framework.go +++ b/test/e2e/framework.go @@ -2232,18 +2232,18 @@ func parseArpingStdout(out string) (sent uint32, received uint32, loss float32, return sent, received, loss, nil } -func (data *TestData) RunPingCommandFromTestPod(podInfo PodInfo, ns string, targetPodIPs *PodIPs, ctrName string, count int, size int) error { +func (data *TestData) RunPingCommandFromTestPod(podInfo PodInfo, ns string, targetPodIPs *PodIPs, ctrName string, count int, size int, fragment bool) error { if podInfo.OS != "windows" && podInfo.OS != "linux" { return fmt.Errorf("OS of Pod '%s' is not clear", podInfo.Name) } if targetPodIPs.IPv4 != nil { - cmdV4 := getPingCommand(count, size, podInfo.OS, targetPodIPs.IPv4) + cmdV4 := getPingCommand(count, size, podInfo.OS, targetPodIPs.IPv4, fragment) if stdout, stderr, err := data.RunCommandFromPod(ns, podInfo.Name, ctrName, cmdV4); err != nil { return fmt.Errorf("error when running ping command '%s': %v - stdout: %s - stderr: %s", strings.Join(cmdV4, " "), err, stdout, stderr) } } if targetPodIPs.IPv6 != nil { - cmdV6 := getPingCommand(count, size, podInfo.OS, targetPodIPs.IPv6) + cmdV6 := getPingCommand(count, size, podInfo.OS, targetPodIPs.IPv6, fragment) if stdout, stderr, err := data.RunCommandFromPod(ns, podInfo.Name, ctrName, cmdV6); err != nil { return fmt.Errorf("error when running ping command '%s': %v - stdout: %s - stderr: %s", strings.Join(cmdV6, " "), err, stdout, stderr) } @@ -2481,6 +2481,47 @@ func (data *TestData) GetTransportInterface() (string, error) { return "", fmt.Errorf("no interface was assigned with Node IP %s", nodeIP) } +func (data *TestData) GetPodInterfaceName(nodeName string, podName string) (string, error) { + antreaPod, err := data.getAntreaPodOnNode(nodeName) + if err != nil { + return "", fmt.Errorf("failed to get Antrea Pod on Node %s: %v", nodeName, err) + } + cmd := []string{"ip", "-br", "addr", "show"} + stdout, stderr, err := data.RunCommandFromPod(antreaNamespace, antreaPod, agentContainerName, cmd) + if stdout == "" || stderr != "" || err != nil { + return "", fmt.Errorf("failed to show ip address, stdout: %s, stderr: %s, err: %v", stdout, stderr, err) + } + + lines := strings.Split(strings.TrimSpace(stdout), "\n") + for _, line := range lines { + if strings.Contains(line, podName) { + fields := strings.Fields(line) + name, _, _ := strings.Cut(fields[0], "@") + return name, nil + } + } + + return "", fmt.Errorf("no interface was assigned with Pod %s", podName) +} + +func (data *TestData) GetInterfaceMTU(nodeName string, interfaceName string) (int, error) { + antreaPod, err := data.getAntreaPodOnNode(nodeName) + if err != nil { + return 0, fmt.Errorf("failed to get Antrea Pod on Node %s: %v", nodeName, err) + } + cmd := []string{"cat", "/sys/class/net/" + interfaceName + "/mtu"} + stdout, stderr, err := data.RunCommandFromPod(antreaNamespace, antreaPod, agentContainerName, cmd) + if stdout == "" || stderr != "" || err != nil { + return 0, fmt.Errorf("failed to get interface MTU, stdout: %s, stderr: %s, err: %v", stdout, stderr, err) + } + + mtu, err := strconv.Atoi(strings.TrimSpace(stdout)) + if err != nil { + return 0, fmt.Errorf("failed to convert MTU to int: %v", err) + } + return mtu, nil +} + func (data *TestData) GetNodeMACAddress(node, device string) (string, error) { antreaPod, err := data.getAntreaPodOnNode(node) if err != nil { @@ -3059,8 +3100,8 @@ func (data *TestData) checkAntreaAgentInfo(interval time.Duration, timeout time. return err } -func getPingCommand(count int, size int, os string, ip *net.IP) []string { - countOption, sizeOption := "-c", "-s" +func getPingCommand(count int, size int, os string, ip *net.IP, fragment bool) []string { + countOption, sizeOption, fragmentOption := "-c", "-s", "-M" if os == "windows" { countOption = "-n" sizeOption = "-l" @@ -3069,6 +3110,10 @@ func getPingCommand(count int, size int, os string, ip *net.IP) []string { if size != 0 { cmd = append(cmd, sizeOption, strconv.Itoa(size)) } + if fragment == false { + cmd = append(cmd, fragmentOption) + cmd = append(cmd, "do") + } if ip.To4() != nil { cmd = append(cmd, "-4", ip.String()) } else { diff --git a/test/e2e/ipsec_test.go b/test/e2e/ipsec_test.go index c51cd5375da..81560f0911e 100644 --- a/test/e2e/ipsec_test.go +++ b/test/e2e/ipsec_test.go @@ -27,6 +27,11 @@ import ( "antrea.io/antrea/pkg/features" ) +var ( + MTUWithIPsecAndGENEVE = 1412 + MTUWithIPsecAndVXLAN = 1412 +) + // TestIPSec is the top-level test which contains all subtests for // IPsec related test cases so they can share setup, teardown. func TestIPSec(t *testing.T) { @@ -79,6 +84,13 @@ func TestIPSec(t *testing.T) { }) t.Run("testIPSecDeleteStaleTunnelPorts", func(t *testing.T) { testIPSecDeleteStaleTunnelPorts(t, data) }) + t.Run("testPodConnectivityWithDifferentTrafficConfig", func(t *testing.T) { + trafficConfigs := []trafficConfig{ + {tunnelType: "geneve", multiclusterTrafficEncryptionMode: "none", mtu: MTUWithIPsecAndGENEVE}, + {tunnelType: "vxlan", multiclusterTrafficEncryptionMode: "none", mtu: MTUWithIPsecAndVXLAN}, + } + testPodConnectivityWithDifferentTrafficConfig(t, data, trafficConfigs) + }) } func (data *TestData) readSecurityAssociationsStatus(nodeName string) (up int, connecting int, isCertAuth bool, err error) { @@ -137,7 +149,7 @@ func testIPSecTunnelConnectivity(t *testing.T, data *TestData, certAuth bool) { podInfos, deletePods := createPodsOnDifferentNodes(t, data, data.testNamespace, tag) defer deletePods() t.Logf("Executing ping tests across Nodes: '%s' <-> '%s'", podInfos[0].NodeName, podInfos[1].NodeName) - data.runPingMesh(t, podInfos[:2], agnhostContainerName) + data.runPingMesh(t, podInfos[:2], agnhostContainerName, 0) // Check that there is at least one 'up' Security Association on the Node nodeName := podInfos[0].NodeName diff --git a/test/e2e/mtu_test.go b/test/e2e/mtu_test.go new file mode 100644 index 00000000000..aa1232dcef0 --- /dev/null +++ b/test/e2e/mtu_test.go @@ -0,0 +1,211 @@ +// Copyright 2024 Antrea 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 e2e + +import ( + "testing" + + agentconfig "antrea.io/antrea/pkg/config/agent" +) + +func TestPodMTU(t *testing.T) { + data, err := setupTest(t) + if err != nil { + t.Fatalf("Error when setting up test: %v", err) + } + defer teardownTest(t, data) + + t.Run("testPodMTUWithGeneveTunnel", func(t *testing.T) { + testPodMTUWithGeneveTunnel(t, data) + }) + t.Run("testPodMTUWithVXLANTunnel", func(t *testing.T) { + testPodMTUWithVXLANTunnel(t, data) + }) + t.Run("testPodMTUWithGRETunnel", func(t *testing.T) { + testPodMTUWithGRETunnel(t, data) + }) + t.Run("testPodMTUWithWireGuard", func(t *testing.T) { + testPodMTUWithWireGuard(t, data) + }) +} + +func testPodMTUWithGeneveTunnel(t *testing.T, data *TestData) { + var previousTunnelType string + ac := func(config *agentconfig.AgentConfig) { + previousTunnelType = config.TunnelType + config.TunnelType = "geneve" + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Fatalf("Failed to enable geneve tunnel: %v", err) + } + defer func() { + ac := func(config *agentconfig.AgentConfig) { + config.TunnelType = previousTunnelType + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Errorf("Failed to enable %s tunnel: %v", previousTunnelType, err) + } + }() + + podName := "geneve" + err := data.createBusyboxPodOnNode(podName, data.testNamespace, workerNodeName(1), false) + if err != nil { + t.Fatalf("Failed to create busybox pod: %v", err) + } + defer deletePodWrapper(t, data, data.testNamespace, podName) + if err := data.podWaitForRunning(defaultTimeout, podName, data.testNamespace); err != nil { + t.Fatalf("Error when waiting for Pod %s: %v", podName, err) + } + + podInterface, err := data.GetPodInterfaceName(workerNodeName(1), podName) + if err != nil { + t.Fatalf("Failed to get Pod interface: %v", err) + } + mtu, err := data.GetInterfaceMTU(workerNodeName(1), podInterface) + if err != nil { + t.Fatalf("Failed to get interface MTU: %v", err) + } + if mtu != 1450 { + t.Errorf("Interface MTU is not 1450: %d", mtu) + } +} + +func testPodMTUWithVXLANTunnel(t *testing.T, data *TestData) { + var previousTunnelType string + ac := func(config *agentconfig.AgentConfig) { + previousTunnelType = config.TunnelType + config.TunnelType = "vxlan" + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Fatalf("Failed to enable vxlan tunnel: %v", err) + } + defer func() { + ac := func(config *agentconfig.AgentConfig) { + config.TunnelType = previousTunnelType + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Errorf("Failed to enable %s tunnel: %v", previousTunnelType, err) + } + }() + + podName := "vxlanpod" + err := data.createBusyboxPodOnNode(podName, data.testNamespace, workerNodeName(1), false) + if err != nil { + t.Fatalf("Failed to create busybox pod: %v", err) + } + defer deletePodWrapper(t, data, data.testNamespace, podName) + if err := data.podWaitForRunning(defaultTimeout, podName, data.testNamespace); err != nil { + t.Fatalf("Error when waiting for Pod %s: %v", podName, err) + } + + podInterface, err := data.GetPodInterfaceName(workerNodeName(1), podName) + if err != nil { + t.Fatalf("Failed to get Pod interface: %v", err) + } + mtu, err := data.GetInterfaceMTU(workerNodeName(1), podInterface) + if err != nil { + t.Fatalf("Failed to get interface MTU: %v", err) + } + if mtu != 1450 { + t.Errorf("Interface MTU is not 1450: %d", mtu) + } +} + +func testPodMTUWithGRETunnel(t *testing.T, data *TestData) { + skipIfIPv6Cluster(t) + var previousTunnelType string + ac := func(config *agentconfig.AgentConfig) { + previousTunnelType = config.TunnelType + config.TunnelType = "gre" + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Fatalf("Failed to enable gre tunnel: %v", err) + } + defer func() { + ac := func(config *agentconfig.AgentConfig) { + config.TunnelType = previousTunnelType + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Errorf("Failed to enable %s tunnel: %v", previousTunnelType, err) + } + }() + + podName := "grepod" + err := data.createBusyboxPodOnNode(podName, data.testNamespace, workerNodeName(1), false) + if err != nil { + t.Fatalf("Failed to create busybox pod: %v", err) + } + defer deletePodWrapper(t, data, data.testNamespace, podName) + if err := data.podWaitForRunning(defaultTimeout, podName, data.testNamespace); err != nil { + t.Fatalf("Error when waiting for Pod %s: %v", podName, err) + } + + podInterface, err := data.GetPodInterfaceName(workerNodeName(1), podName) + if err != nil { + t.Fatalf("Failed to get Pod interface: %v", err) + } + mtu, err := data.GetInterfaceMTU(workerNodeName(1), podInterface) + if err != nil { + t.Fatalf("Failed to get interface MTU: %v", err) + } + if mtu != 1462 { + t.Errorf("Interface MTU is not 1462: %d", mtu) + } +} + +func testPodMTUWithWireGuard(t *testing.T, data *TestData) { + var previousTunnelType string + var previousTrafficEncryptionMode string + ac := func(config *agentconfig.AgentConfig) { + previousTunnelType = config.TunnelType + previousTrafficEncryptionMode = config.TrafficEncryptionMode + config.TrafficEncryptionMode = "wireguard" + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Fatalf("Failed to enable wireguard tunnel: %v", err) + } + defer func() { + ac := func(config *agentconfig.AgentConfig) { + config.TunnelType = previousTunnelType + config.TrafficEncryptionMode = previousTrafficEncryptionMode + } + if err := data.mutateAntreaConfigMap(nil, ac, false, true); err != nil { + t.Errorf("Failed to enable %s tunnel: %v", previousTunnelType, err) + } + }() + + podName := "wgpod" + err := data.createBusyboxPodOnNode(podName, data.testNamespace, workerNodeName(1), false) + if err != nil { + t.Fatalf("Failed to create busybox pod: %v", err) + } + defer deletePodWrapper(t, data, data.testNamespace, podName) + + if err := data.podWaitForRunning(defaultTimeout, podName, data.testNamespace); err != nil { + t.Fatalf("Error when waiting for Pod %s: %v", podName, err) + } + + podInterface, err := data.GetPodInterfaceName(workerNodeName(1), podName) + if err != nil { + t.Fatalf("Failed to get Pod interface: %v", err) + } + mtu, err := data.GetInterfaceMTU(workerNodeName(1), podInterface) + if err != nil { + t.Fatalf("Failed to get interface MTU: %v", err) + } + if mtu != 1420 { + t.Errorf("Interface MTU is not 1420: %d", mtu) + } +} diff --git a/test/e2e/traceflow_test.go b/test/e2e/traceflow_test.go index 6b3f069a986..95a8c074ac2 100644 --- a/test/e2e/traceflow_test.go +++ b/test/e2e/traceflow_test.go @@ -1167,7 +1167,7 @@ func testTraceflowInterNode(t *testing.T, data *TestData) { podInfos[1].Name = node2Pods[2] podInfos[1].Namespace = data.testNamespace podInfos[1].OS = "windows" - data.runPingMesh(t, podInfos, agnhostContainerName) + data.runPingMesh(t, podInfos, agnhostContainerName, 0) } // Setup 2 NetworkPolicies: @@ -2507,7 +2507,7 @@ func runTestTraceflow(t *testing.T, data *TestData, tc testcase) { // Give a little time for Nodes to install OVS flows. time.Sleep(time.Second * 2) // Send an ICMP echo packet from the source Pod to the destination. - if err := data.RunPingCommandFromTestPod(PodInfo{srcPod, osString, "", ""}, data.testNamespace, dstPodIPs, agnhostContainerName, 2, 0); err != nil { + if err := data.RunPingCommandFromTestPod(PodInfo{srcPod, osString, "", ""}, data.testNamespace, dstPodIPs, agnhostContainerName, 2, 0, true); err != nil { t.Logf("Ping '%s' -> '%v' failed: ERROR (%v)", srcPod, *dstPodIPs, err) } } diff --git a/test/e2e/upgrade_test.go b/test/e2e/upgrade_test.go index 255e8153af5..03fcabadd35 100644 --- a/test/e2e/upgrade_test.go +++ b/test/e2e/upgrade_test.go @@ -70,8 +70,8 @@ func TestUpgrade(t *testing.T) { } defer data.DeleteNamespace(namespace, defaultTimeout) - data.testPodConnectivitySameNode(t) - data.testPodConnectivityDifferentNodes(t) + data.testPodConnectivitySameNode(t, 0) + data.testPodConnectivityDifferentNodes(t, 0) // We test NetworkPolicy with 2 scenarios: // 1. The NetworkPolicy is created before upgrading controller. // 2. The NetworkPolicy is created after upgrading controller. @@ -99,8 +99,8 @@ func TestUpgrade(t *testing.T) { } } - data.testPodConnectivitySameNode(t) - data.testPodConnectivityDifferentNodes(t) + data.testPodConnectivitySameNode(t, 0) + data.testPodConnectivityDifferentNodes(t, 0) // Verify that the NetworkPolicy created before upgrading still works. checkFn() // Verify that the NetworkPolicy created after upgrading works. diff --git a/test/e2e/vmagent_test.go b/test/e2e/vmagent_test.go index 6036a88b2de..59a491d00a8 100644 --- a/test/e2e/vmagent_test.go +++ b/test/e2e/vmagent_test.go @@ -656,7 +656,7 @@ func createANPWithFQDN(t *testing.T, data *TestData, name string, namespace stri func runPingCommandOnVM(data *TestData, dstVM vmInfo, connected bool) error { dstIP := net.ParseIP(dstVM.ip) - cmd := getPingCommand(pingCount, 0, strings.ToLower(linuxOS), &dstIP) + cmd := getPingCommand(pingCount, 0, strings.ToLower(linuxOS), &dstIP, true) cmdStr := strings.Join(cmd, " ") expCount := pingCount if !connected { diff --git a/test/e2e/wireguard_test.go b/test/e2e/wireguard_test.go index b746ffe07a7..287e55f9160 100644 --- a/test/e2e/wireguard_test.go +++ b/test/e2e/wireguard_test.go @@ -27,6 +27,10 @@ import ( agentconfig "antrea.io/antrea/pkg/config/agent" ) +const ( + MTUWithWireguard = 1420 +) + // TestWireGuard checks that Pod traffic across two Nodes over the WireGuard tunnel by creating // multiple Pods across distinct Nodes and having them ping each other. It will also verify that // the handshake was established when the wg command line is available. @@ -69,7 +73,7 @@ func testPodConnectivity(t *testing.T, data *TestData) { podInfos, deletePods := createPodsOnDifferentNodes(t, data, data.testNamespace, "differentnodes") defer deletePods() numPods := 2 - data.runPingMesh(t, podInfos[:numPods], agnhostContainerName) + data.runPingMesh(t, podInfos[:numPods], toolboxContainerName, MTUWithWireguard-IPHeaderSize-ICMPHeaderSize) // Make sure that route to Pod on peer Node and route to peer gateway is targeting the WireGuard device. srcPod, err := data.getAntreaPodOnNode(nodeName(0))