From b7bc0b9c37665470cdea9d6119672c3408b85102 Mon Sep 17 00:00:00 2001 From: Ori Amizur Date: Thu, 4 Apr 2024 14:12:23 +0300 Subject: [PATCH] OCPBUGS-31631: Deploy dual stack with IPv6 on top of bond/vlan fails IPv6 L2 connectivity check uses nmap. nmap uses NDP (Network Discovery Protocol) to check L2 connectivty to IPv6 hosts. There are cases that NDP response does not arrive or it is not captured by nmap which means that the system assumes there is no connectivity to the host. However, there might be L3 connectivity (ping) to the same host. So change adds fallback for IPv6 L2 connectivity. In case there is failure in L2 connectivity to IPv6 host and success in L3 connecivity to the same host, the host is assumed connected on L2, providing that the remote address is part of the L2 subnet being checked. --- internal/network/connectivity_groups.go | 16 +++++++ internal/network/connectivity_groups_test.go | 49 ++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/internal/network/connectivity_groups.go b/internal/network/connectivity_groups.go index 7c6afead847..73cde7233a0 100644 --- a/internal/network/connectivity_groups.go +++ b/internal/network/connectivity_groups.go @@ -376,6 +376,11 @@ type l2Query struct { connectivityReport models.ConnectivityReport } +func (l *l2Query) isIPv6() bool { + _, bits := l.parsedCidr.Mask.Size() + return bits == net.IPv6len*8 +} + func (l *l2Query) next() strfmt.UUID { for l.current != len(l.connectivityReport.RemoteHosts) { rh := l.connectivityReport.RemoteHosts[l.current] @@ -386,6 +391,17 @@ func (l *l2Query) next() strfmt.UUID { return rh.HostID } } + if l.isIPv6() { + // nmap uses NDP (Network Discovery Protocol) for IPv6 L2 discovery. There are cases that NDP responses do not + // arrive and parsed by nmap, but still there is connectivity between the hosts. In this case we fallback to L3 + // check and verify that the remote address is in the required subnet. + for _, l3 := range rh.L3Connectivity { + ip := net.ParseIP(l3.RemoteIPAddress) + if ip != nil && l.parsedCidr.Contains(ip) && l3.Successful { + return rh.HostID + } + } + } } return "" } diff --git a/internal/network/connectivity_groups_test.go b/internal/network/connectivity_groups_test.go index 6204c4a386a..5625e1ef2dd 100644 --- a/internal/network/connectivity_groups_test.go +++ b/internal/network/connectivity_groups_test.go @@ -515,6 +515,55 @@ func GenerateL2ConnectivityGroupTests(ipV4 bool, net1CIDR, net2CIDR string) { }) } +var _ = Describe("L2 Ipv6 with L3 fallback", func() { + var ( + nodes []*node + net1CIDR string + hosts []*models.Host + ) + + BeforeEach(func() { + net1CIDR = "2001:db8::/120" + nodes = generateIPv6Nodes(7, "2001:db8::/120", "fe80:5054::/120") + hosts = []*models.Host{ + { + ID: nodes[0].id, + Connectivity: createConnectivityReport( + createL2Remote(nodes[1], l2LinkNet1), + createL2Remote(nodes[2], l2LinkNet1)), + }, + { + ID: nodes[1].id, + Connectivity: createConnectivityReport( + createL2Remote(nodes[0], l2LinkNet1), + createL2Remote(nodes[2], l2LinkNet1)), + }, + { + ID: nodes[2].id, + Connectivity: createConnectivityReport( + createL2Remote(nodes[0], l2LinkNet1)), + }, + } + }) + It("Missing L2 connectivity", func() { + ret, err := CreateL2MajorityGroup(net1CIDR, hosts) + Expect(err).ToNot(HaveOccurred()) + Expect(ret).To(HaveLen(0)) + }) + + It("Missing L2 connectivity. Add L3 connectivity", func() { + hosts[2].Connectivity = createConnectivityReport( + createL2Remote(nodes[0], l2LinkNet1), + createL3Remote(nodes[1], l3LinkNet1)) + ret, err := CreateL2MajorityGroup(net1CIDR, hosts) + Expect(err).ToNot(HaveOccurred()) + Expect(ret).To(HaveLen(3)) + Expect(ret).To(ContainElement(*nodes[0].id)) + Expect(ret).To(ContainElement(*nodes[1].id)) + Expect(ret).To(ContainElement(*nodes[2].id)) + }) +}) + var _ = Describe("L3 connectivity groups all", func() { GenerateL3ConnectivityGroupTests(true, "1.2.3.0/24", "2.2.3.0/24") GenerateL3ConnectivityGroupTests(false, "2001:db8::/120", "fe80:5054::/120")