Skip to content

add dad e2e #5146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 83 additions & 18 deletions pkg/daemon/ovs_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
})

Expand Down Expand Up @@ -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)
}
97 changes: 97 additions & 0 deletions test/e2e/kube-ovn/underlay/underlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading