diff --git a/pkg/daemon/ovs_linux.go b/pkg/daemon/ovs_linux.go index da1f5546e33..ded3751421b 100644 --- a/pkg/daemon/ovs_linux.go +++ b/pkg/daemon/ovs_linux.go @@ -507,6 +507,14 @@ func (csh cniServerHandler) configureContainerNic(podName, podNamespace, nicName if gwCheckMode != gatewayCheckModeDisabled { underlayGateway := gwCheckMode == gatewayCheckModeArping || gwCheckMode == gatewayCheckModeArpingNotConcerned + + if util.CheckProtocol(ipAddr) == kubeovnv1.ProtocolIPv6 || util.CheckProtocol(ipAddr) == kubeovnv1.ProtocolDual { + if err := waitIPv6AddressPreferred(interfaceName, 10, 500*time.Millisecond, checkIPv6DAD); err != nil { + klog.Errorf("Some IPv6 addresses might not be in preferred state: %v", err) + return err + } + } + if u2oInterconnectionIP != "" { if err = csh.checkGatewayReady(podName, podNamespace, gwCheckMode, interfaceName, ipAddr, u2oInterconnectionIP, false, true); err != nil { klog.Error(err) @@ -519,24 +527,6 @@ func (csh cniServerHandler) configureContainerNic(podName, podNamespace, nicName } } - if checkIPv6DAD { - // check whether the ipv6 address has a dadfailed flag - addresses, err := netlink.AddrList(containerLink, netlink.FAMILY_V6) - if err != nil { - err = fmt.Errorf("failed to get ipv6 addresses of link %s: %w", interfaceName, err) - klog.Error(err) - return err - } - - for _, addr := range addresses { - if addr.Flags&syscall.IFA_F_DADFAILED != 0 { - err = fmt.Errorf("IPv6 address %s has a dadfailed flag, please check whether it has been used by another host", addr.IP.String()) - klog.Error(err) - return err - } - } - } - return nil }) @@ -1994,3 +1984,78 @@ func rollBackVethPair(nicName string) error { klog.Infof("rollback veth success %s", nicName) return nil } + +func waitIPv6AddressPreferred(interfaceName string, maxRetry int, retryInterval time.Duration, checkIPv6DAD bool) error { + var retry int + var errorMessages []string + + for retry < maxRetry { + link, err := netlink.LinkByName(interfaceName) + if err != nil { + klog.Errorf("failed to get link %s: %v", interfaceName, err) + return err + } + + addrs, err := netlink.AddrList(link, netlink.FAMILY_V6) + if err != nil { + klog.Errorf("failed to get IPv6 addresses on interface %s: %v", interfaceName, err) + return err + } + + var globalIPv6Found bool + var badStateIPv6Found bool + + for _, addr := range addrs { + // Skip link-local addresses + if addr.IP.IsLinkLocalUnicast() { + continue + } + + globalIPv6Found = true + // Check if the address is in a bad state + switch { + case (addr.Flags & unix.IFA_F_DEPRECATED) != 0: + badStateIPv6Found = true + errorMsg := fmt.Sprintf("IPv6 address %s on interface %s is deprecated", addr.IP.String(), interfaceName) + errorMessages = append(errorMessages, errorMsg) + case (addr.Flags & unix.IFA_F_DADFAILED) != 0: + if !checkIPv6DAD { + continue + } + badStateIPv6Found = true + errorMsg := fmt.Sprintf("IPv6 address %s has a dadfailed flag, please check whether it has been used by another host", addr.IP.String()) + errorMessages = append(errorMessages, errorMsg) + case (addr.Flags & unix.IFA_F_TENTATIVE) != 0: + badStateIPv6Found = true + errorMsg := fmt.Sprintf("IPv6 address %s on interface %s is in tentative state (DAD in progress)", addr.IP.String(), interfaceName) + errorMessages = append(errorMessages, errorMsg) + default: + klog.Infof("IPv6 address %s on interface %s is in preferred state", addr.IP.String(), interfaceName) + } + } + + if globalIPv6Found && !badStateIPv6Found { + klog.Infof("All non-link-local IPv6 addresses on interface %s are in preferred state", interfaceName) + return nil + } + + if !globalIPv6Found { + errorMsg := fmt.Sprintf("No non-link-local IPv6 addresses found on interface %s, retry %d/%d", interfaceName, retry+1, maxRetry) + errorMessages = append(errorMessages, errorMsg) + } else { + errorMsg := fmt.Sprintf("Some IPv6 addresses on interface %s are in bad state (deprecated, tentative, or DAD failed), retry %d/%d", interfaceName, retry+1, maxRetry) + errorMessages = append(errorMessages, errorMsg) + } + + retry++ + if retry < maxRetry { + time.Sleep(retryInterval) + } + } + + finalMsg := fmt.Sprintf("failed to find non-link-local IPv6 addresses in preferred state on interface %s after %d retries", interfaceName, maxRetry) + if len(errorMessages) > 0 { + finalMsg = fmt.Sprintf("%s. Errors: %s", finalMsg, strings.Join(errorMessages, "; ")) + } + return errors.New(finalMsg) +} diff --git a/test/e2e/kube-ovn/underlay/underlay.go b/test/e2e/kube-ovn/underlay/underlay.go index 0e0bc3d91ee..f940e063cdd 100644 --- a/test/e2e/kube-ovn/underlay/underlay.go +++ b/test/e2e/kube-ovn/underlay/underlay.go @@ -950,6 +950,103 @@ var _ = framework.SerialDescribe("[group:underlay]", func() { framework.ExpectEmpty(lrpIPv6) } }) + + framework.ConformanceIt(`should support IPv6 connectivity to node IPs when pod start immediately`, func() { + if !f.HasIPv6() { + ginkgo.Skip("This test requires IPv6 support") + } + + f.SkipVersionPriorTo(1, 13, "This feature was introduced in v1.13.8") + ginkgo.By("Creating provider network " + providerNetworkName) + pn := makeProviderNetwork(providerNetworkName, false, linkMap) + _ = providerNetworkClient.CreateSync(pn) + + ginkgo.By("Getting docker network " + dockerNetworkName) + network, err := docker.NetworkInspect(dockerNetworkName) + framework.ExpectNoError(err, "getting docker network "+dockerNetworkName) + + ginkgo.By("Creating vlan " + vlanName) + vlan := framework.MakeVlan(vlanName, providerNetworkName, 0) + _ = vlanClient.Create(vlan) + + ginkgo.By("Creating underlay subnet " + subnetName) + var cidrV4, cidrV6, gatewayV4, gatewayV6 string + for _, config := range dockerNetwork.IPAM.Config { + switch util.CheckProtocol(config.Subnet) { + case apiv1.ProtocolIPv4: + if f.HasIPv4() { + cidrV4 = config.Subnet + gatewayV4 = config.Gateway + } + case apiv1.ProtocolIPv6: + cidrV6 = config.Subnet + gatewayV6 = config.Gateway + } + } + + underlayCidr := make([]string, 0, 2) + gateway := make([]string, 0, 2) + if f.HasIPv4() { + underlayCidr = append(underlayCidr, cidrV4) + gateway = append(gateway, gatewayV4) + } + underlayCidr = append(underlayCidr, cidrV6) + gateway = append(gateway, gatewayV6) + + excludeIPs := make([]string, 0, len(network.Containers)*2) + for _, container := range network.Containers { + if container.IPv4Address != "" && f.HasIPv4() { + excludeIPs = append(excludeIPs, strings.Split(container.IPv4Address, "/")[0]) + } + if container.IPv6Address != "" { + excludeIPs = append(excludeIPs, strings.Split(container.IPv6Address, "/")[0]) + } + } + + ginkgo.By("Creating subnet " + subnetName) + subnet := framework.MakeSubnet(subnetName, vlanName, strings.Join(underlayCidr, ","), strings.Join(gateway, ","), "", "", excludeIPs, nil, []string{namespaceName}) + _ = subnetClient.CreateSync(subnet) + + ginkgo.By("Getting node IPv6 addresses") + nodes, err := kind.ListNodes(clusterName, "") + framework.ExpectNoError(err, "getting nodes in kind cluster") + + // Find a node with IPv6 address + var nodeIPv6 string + var selectedNode kind.Node + for _, n := range nodes { + for _, link := range linkMap { + for _, addr := range link.NonLinkLocalAddresses() { + if util.CheckProtocol(addr) == apiv1.ProtocolIPv6 { + nodeIPv6 = addr + selectedNode = n + break + } + } + if nodeIPv6 != "" { + break + } + } + if nodeIPv6 != "" { + break + } + } + framework.ExpectNotEmpty(nodeIPv6, "No node with IPv6 address found") + ipv6Addr := strings.Split(nodeIPv6, "/")[0] + + ginkgo.By(fmt.Sprintf("Creating pod %s that pings IPv6 node IP %s on node %s", podName, ipv6Addr, selectedNode.Name())) + // Use ping6 with one attempt and 1s timeout, checking the return code + pingCmd := []string{"sh", "-c", fmt.Sprintf("ping6 -c 1 -w 1 %s && sleep 600 || exit $?", ipv6Addr)} + annotations := map[string]string{ + util.LogicalSwitchAnnotation: subnetName, + } + + pod := framework.MakePod(namespaceName, podName, nil, annotations, framework.AgnhostImage, pingCmd, nil) + pod = podClient.CreateSync(pod) + + ginkgo.By("If pod is running, the ping was successful") + framework.ExpectEqual(pod.Status.Phase, corev1.PodRunning, "Pod should be in Running state, indicating successful ping with 1s timeout") + }) }) func checkU2OItems(f *framework.Framework, subnet *apiv1.Subnet, underlayPod, overlayPod *corev1.Pod, isU2OCustomVpc bool) {