diff --git a/go-controller/pkg/node/controllers/egressip/egressip.go b/go-controller/pkg/node/controllers/egressip/egressip.go index be769bc87a..63a8477122 100644 --- a/go-controller/pkg/node/controllers/egressip/egressip.go +++ b/go-controller/pkg/node/controllers/egressip/egressip.go @@ -45,6 +45,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/syncmap" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/types" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/egressip" utilerrors "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/errors" ) @@ -539,15 +540,15 @@ func (c *Controller) processEIP(eip *eipv1.EgressIP) (*eIPConfig, sets.Set[strin if isValid := isEIPStatusItemValid(status, c.nodeName); !isValid { continue } - eIPNet, err := util.GetIPNetFullMask(status.EgressIP) - if err != nil { + ip := net.ParseIP(status.EgressIP) + if ip == nil { return nil, selectedNamespaces, selectedPods, selectedNamespacesPodIPs, - fmt.Errorf("failed to generate mask for EgressIP %s IP %s: %v", eip.Name, status.EgressIP, err) + fmt.Errorf("failed to parse EgressIP %s IP %s", eip.Name, status.EgressIP) } - if util.IsOVNNetwork(parsedNodeEIPConfig, eIPNet.IP) { + if util.IsOVNNetwork(parsedNodeEIPConfig, ip) { continue } - found, link, err := findLinkOnSameNetworkAsIP(eIPNet.IP, c.v4, c.v6) + found, link, err := findLinkOnSameNetworkAsIP(ip, c.v4, c.v6) if err != nil { return nil, selectedNamespaces, selectedPods, selectedNamespacesPodIPs, fmt.Errorf("failed to find a network to host EgressIP %s IP %s: %v", eip.Name, status.EgressIP, err) @@ -560,7 +561,7 @@ func (c *Controller) processEIP(eip *eipv1.EgressIP) (*eIPConfig, sets.Set[strin if err != nil { return nil, selectedNamespaces, selectedPods, selectedNamespacesPodIPs, fmt.Errorf("failed to list namespaces: %w", err) } - isEIPV6 := utilnet.IsIPv6(eIPNet.IP) + isEIPV6 := utilnet.IsIPv6(ip) for _, namespace := range namespaces { netInfo, err := c.getActiveNetworkForNamespace(namespace.Name) if err != nil { @@ -593,13 +594,13 @@ func (c *Controller) processEIP(eip *eipv1.EgressIP) (*eIPConfig, sets.Set[strin if selectedNamespacesPodIPs[namespace.Name] == nil { selectedNamespacesPodIPs[namespace.Name] = make(map[ktypes.NamespacedName]*podIPConfigList) } - selectedNamespacesPodIPs[namespace.Name][podNamespaceName] = generatePodConfig(ips, link, eIPNet, isEIPV6) + selectedNamespacesPodIPs[namespace.Name][podNamespaceName] = generatePodConfig(ips, link, ip, isEIPV6) selectedPods.Insert(podNamespaceName) } } // ensure at least one pod is selected before generating config if len(selectedNamespacesPodIPs) > 0 { - eipSpecificConfig, err = generateEIPConfig(link, eIPNet, isEIPV6) + eipSpecificConfig, err = generateEIPConfig(link, ip, isEIPV6) if err != nil { return nil, selectedNamespaces, selectedPods, selectedNamespacesPodIPs, fmt.Errorf("failed to generate EIP configuration for EgressIP %s IP %s: %v", eip.Name, status.EgressIP, err) @@ -611,7 +612,7 @@ func (c *Controller) processEIP(eip *eipv1.EgressIP) (*eIPConfig, sets.Set[strin return eipSpecificConfig, selectedNamespaces, selectedPods, selectedNamespacesPodIPs, nil } -func generatePodConfig(podIPs []net.IP, link netlink.Link, eIPNet *net.IPNet, isEIPV6 bool) *podIPConfigList { +func generatePodConfig(podIPs []net.IP, link netlink.Link, eIP net.IP, isEIPV6 bool) *podIPConfigList { newPodIPConfigs := newPodIPConfigList() for _, podIP := range podIPs { isPodIPv6 := utilnet.IsIPv6(podIP) @@ -619,7 +620,7 @@ func generatePodConfig(podIPs []net.IP, link netlink.Link, eIPNet *net.IPNet, is continue } ipConfig := newPodIPConfig() - ipConfig.ipTableRule = generateIPTablesSNATRuleArg(podIP, isPodIPv6, link.Attrs().Name, eIPNet.IP.String()) + ipConfig.ipTableRule = generateIPTablesSNATRuleArg(podIP, isPodIPv6, link.Attrs().Name, eIP.String()) ipConfig.ipRule = generateIPRule(podIP, isPodIPv6, link.Attrs().Index) ipConfig.v6 = isPodIPv6 newPodIPConfigs.elems = append(newPodIPConfigs.elems, ipConfig) @@ -628,14 +629,14 @@ func generatePodConfig(podIPs []net.IP, link netlink.Link, eIPNet *net.IPNet, is } // generateEIPConfig generates configuration that isn't related to any pod EIPs to support config of a single EIP -func generateEIPConfig(link netlink.Link, eIPNet *net.IPNet, isEIPV6 bool) (*eIPConfig, error) { +func generateEIPConfig(link netlink.Link, eIP net.IP, isEIPV6 bool) (*eIPConfig, error) { eipConfig := newEIPConfig() linkRoutes, err := generateRoutesForLink(link, isEIPV6) if err != nil { return nil, err } eipConfig.routes = linkRoutes - eipConfig.addr = getNetlinkAddress(eIPNet, link.Attrs().Index) + eipConfig.addr = egressip.GetNetlinkAddress(eIP, link.Attrs().Index) return eipConfig, nil } @@ -1482,14 +1483,6 @@ func isLinkUp(flags string) bool { return strings.Contains(flags, "up") } -func getNetlinkAddress(addr *net.IPNet, ifindex int) *netlink.Addr { - return &netlink.Addr{ - IPNet: addr, - Scope: int(netlink.SCOPE_UNIVERSE), - LinkIndex: ifindex, - } -} - // generateIPRules generates IP rules at a predefined priority for each pod IP with a custom routing table based // from the links 'ifindex' func generateIPRule(srcIP net.IP, isIPv6 bool, ifIndex int) netlink.Rule { diff --git a/go-controller/pkg/node/egressip/gateway_egressip.go b/go-controller/pkg/node/egressip/gateway_egressip.go index 38bd2b058e..89f9c63224 100644 --- a/go-controller/pkg/node/egressip/gateway_egressip.go +++ b/go-controller/pkg/node/egressip/gateway_egressip.go @@ -3,13 +3,9 @@ package egressip import ( "encoding/json" "fmt" - "math" "net" "sync" - "github.com/vishvananda/netlink" - "golang.org/x/sys/unix" - "k8s.io/apimachinery/pkg/util/sets" corev1informers "k8s.io/client-go/informers/core/v1" corev1listers "k8s.io/client-go/listers/core/v1" @@ -23,6 +19,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/kube" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/linkmanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/egressip" ) // markIPs contains packet mark and associated EgressIP IP for IPv4 / IPv6. Key is packet mark, value egress IP @@ -451,7 +448,7 @@ func (g *BridgeEIPAddrManager) addIPBridge(ip net.IP) error { if err != nil { return fmt.Errorf("failed to get link obj by name %s: %v", g.bridgeName, err) } - return g.addrManager.AddAddress(getEIPBridgeNetlinkAddress(ip, link.Attrs().Index)) + return g.addrManager.AddAddress(*egressip.GetNetlinkAddress(ip, link.Attrs().Index)) } func (g *BridgeEIPAddrManager) deleteIPBridge(ip net.IP) error { @@ -459,7 +456,7 @@ func (g *BridgeEIPAddrManager) deleteIPBridge(ip net.IP) error { if err != nil { return fmt.Errorf("failed to get link obj by name %s: %v", g.bridgeName, err) } - return g.addrManager.DelAddress(getEIPBridgeNetlinkAddress(ip, link.Attrs().Index)) + return g.addrManager.DelAddress(*egressip.GetNetlinkAddress(ip, link.Attrs().Index)) } // getAnnotationIPs retrieves the egress IP annotation from the current node Nodes object. If multiple users, callers must synchronise. @@ -514,29 +511,3 @@ func getIPsStr(ips ...net.IP) []string { } return ipsStr } - -func getEIPBridgeNetlinkAddress(ip net.IP, ifindex int) netlink.Addr { - return netlink.Addr{ - IPNet: &net.IPNet{IP: ip, Mask: util.GetIPFullMask(ip)}, - Flags: getEIPNetlinkAddressFlag(ip), - Scope: int(netlink.SCOPE_UNIVERSE), - ValidLft: getEIPNetlinkAddressValidLft(ip), - LinkIndex: ifindex, - } -} - -func getEIPNetlinkAddressFlag(ip net.IP) int { - // isV6? - if ip.To4() == nil && ip.To16() != nil { - return unix.IFA_F_NODAD - } - return 0 -} - -func getEIPNetlinkAddressValidLft(ip net.IP) int { - // isV6? - if ip.To4() == nil && ip.To16() != nil { - return math.MaxUint32 - } - return 0 -} diff --git a/go-controller/pkg/node/egressip/gateway_egressip_test.go b/go-controller/pkg/node/egressip/gateway_egressip_test.go index 07a03a87b6..1fe48a6f5b 100644 --- a/go-controller/pkg/node/egressip/gateway_egressip_test.go +++ b/go-controller/pkg/node/egressip/gateway_egressip_test.go @@ -20,6 +20,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/linkmanager" netlink_mocks "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/testing/mocks/github.com/vishvananda/netlink" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/egressip" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util/mocks" ) @@ -63,7 +64,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -74,11 +75,11 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("doesn't configure or fail when annotation mark isn't found", func() { - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, "", ipV4Addr) @@ -89,11 +90,11 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertNotCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("fails when invalid annotation mark", func() { - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, "not-an-integer", ipV4Addr) @@ -104,7 +105,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertNotCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("configures annotations with existing entries", func() { @@ -113,7 +114,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, generateAnnotFromIPs(ipV4Addr2)) defer stopFn() eip := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -124,7 +125,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr, ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) }) @@ -135,7 +136,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() assignedEIP := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -147,7 +148,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("removes EgressIP previously assigned", func() { @@ -156,8 +157,8 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrDel", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrDel", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() assignedEIP := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -172,9 +173,9 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrDel", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("reconfigures from an old to a new IP", func() { @@ -183,9 +184,9 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr2), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrDel", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr2), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrDel", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() unassignedEIP := getEIPNotAssignedToNode(mark, ipV4Addr) @@ -201,11 +202,11 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr2), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr2), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrDel", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) }) @@ -216,8 +217,8 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrDel", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrDel", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eip := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -231,9 +232,9 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).ShouldNot(gomega.ConsistOf(ipV4Addr)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrDel", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("does not update when EIP is deleted that wasn't assigned to the node", func() { @@ -247,7 +248,7 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr2)) gomega.Expect(nlMock.AssertNotCalled(ginkgo.GinkgoT(), "AddrDel", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) }) }) @@ -258,8 +259,8 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr2), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr2), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, emptyAnnotation) defer stopFn() eipAssigned1 := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -271,9 +272,9 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr, ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr2), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr2), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("delete previous configuration", func() { @@ -282,9 +283,9 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { nlMock.On("LinkByIndex", bridgeLinkIndex).Return(nlLinkMock, nil) nlMock.On("LinkList").Return([]netlink.Link{nlLinkMock}, nil) nlMock.On("AddrList", nlLinkMock, 0).Return([]netlink.Addr{}, nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrAdd", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr2), bridgeLinkIndex)).Return(nil) - nlMock.On("AddrDel", nlLinkMock, getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr3), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrAdd", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr2), bridgeLinkIndex)).Return(nil) + nlMock.On("AddrDel", nlLinkMock, egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr3), bridgeLinkIndex)).Return(nil) addrMgr, stopFn := initBridgeEIPAddrManager(nodeName, bridgeName, generateAnnotFromIPs(ipV4Addr3)) // previously configured IP defer stopFn() eipAssigned1 := getEIPAssignedToNode(nodeName, mark, ipV4Addr) @@ -295,11 +296,11 @@ var _ = ginkgo.Describe("Gateway EgressIP", func() { gomega.Expect(err).ShouldNot(gomega.HaveOccurred(), "node should be present within kapi") gomega.Expect(parseEIPsFromAnnotation(node)).Should(gomega.ConsistOf(ipV4Addr, ipV4Addr2)) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrAdd", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr2), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr2), bridgeLinkIndex))).Should(gomega.BeTrue()) gomega.Expect(nlMock.AssertCalled(ginkgo.GinkgoT(), "AddrDel", nlLinkMock, - getEIPBridgeNetlinkAddressPtr(net.ParseIP(ipV4Addr3), bridgeLinkIndex))).Should(gomega.BeTrue()) + egressip.GetNetlinkAddress(net.ParseIP(ipV4Addr3), bridgeLinkIndex))).Should(gomega.BeTrue()) }) ginkgo.It("no update or failure when mark is not set", func() { @@ -387,11 +388,6 @@ func generateAnnotFromIPs(ips ...string) string { return fmt.Sprintf("[%s]", strings.Join(ipsWithQuotes, ",")) } -func getEIPBridgeNetlinkAddressPtr(ip net.IP, ifindex int) *netlink.Addr { - addr := getEIPBridgeNetlinkAddress(ip, ifindex) - return &addr -} - func parseEIPsFromAnnotation(node *corev1.Node) []string { ips, err := util.ParseNodeBridgeEgressIPsAnnotation(node) if err != nil { diff --git a/go-controller/pkg/util/egressip/net.go b/go-controller/pkg/util/egressip/net.go new file mode 100644 index 0000000000..018e6d27f6 --- /dev/null +++ b/go-controller/pkg/util/egressip/net.go @@ -0,0 +1,39 @@ +package egressip + +import ( + "math" + "net" + + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" + + "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/util" +) + +// GetNetlinkAddress returns a netlink address configured with specific +// egress ip parameters +func GetNetlinkAddress(ip net.IP, ifindex int) *netlink.Addr { + return &netlink.Addr{ + IPNet: &net.IPNet{IP: ip, Mask: util.GetIPFullMask(ip)}, + Flags: getNetlinkAddressFlag(ip), + Scope: int(netlink.SCOPE_UNIVERSE), + ValidLft: getNetlinkAddressValidLft(ip), + LinkIndex: ifindex, + } +} + +func getNetlinkAddressFlag(ip net.IP) int { + // isV6? + if ip != nil && ip.To4() == nil && ip.To16() != nil { + return unix.IFA_F_NODAD + } + return 0 +} + +func getNetlinkAddressValidLft(ip net.IP) int { + // isV6? + if ip != nil && ip.To4() == nil && ip.To16() != nil { + return math.MaxUint32 + } + return 0 +}