diff --git a/go-controller/pkg/node/controllers/egressip/egressip.go b/go-controller/pkg/node/controllers/egressip/egressip.go index a21eac49ab..3045b8cf1d 100644 --- a/go-controller/pkg/node/controllers/egressip/egressip.go +++ b/go-controller/pkg/node/controllers/egressip/egressip.go @@ -24,6 +24,7 @@ import ( "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/node/routemanager" "github.com/ovn-org/ovn-kubernetes/go-controller/pkg/syncmap" "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" corev1 "k8s.io/api/core/v1" @@ -531,15 +532,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) @@ -552,7 +553,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 { selectedNamespaces.Insert(namespace.Name) pods, err := c.listPodsByNamespaceAndSelector(namespace.Name, &eip.Spec.PodSelector) @@ -577,13 +578,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) @@ -595,7 +596,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) @@ -603,7 +604,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) @@ -612,14 +613,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 } @@ -1454,14 +1455,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/ovn/egressip_test.go b/go-controller/pkg/ovn/egressip_test.go index 7f2c337e11..d001e0c1df 100644 --- a/go-controller/pkg/ovn/egressip_test.go +++ b/go-controller/pkg/ovn/egressip_test.go @@ -5070,7 +5070,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations", func() { } _, node1Subnet, _ := net.ParseCIDR(v6Node1Subnet) _, node2Subnet, _ := net.ParseCIDR(v6Node2Subnet) - egressIPServedPodsASv4, _ := buildEgressIPServedPodsAddressSets(nil, types.DefaultNetworkName, DefaultNetworkControllerName) + egressIPServedPodsASv4, _ := buildEgressIPServedPodsAddressSets(nil) dynamicNeighRouters := "true" if config.OVNKubernetesFeature.EnableInterconnect { dynamicNeighRouters = "false" @@ -5176,7 +5176,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations", func() { expectedNatLogicalPort := "k8s-node2" - egressIPServedPodsASv4, _ := buildEgressIPServedPodsAddressSets(nil) + egressIPServedPodsASv4, _ = buildEgressIPServedPodsAddressSets(nil) expectedDatabaseState := []libovsdbtest.TestData{ getReRoutePolicy(egressPod.Status.PodIP, "6", "reroute-UUID", node2LogicalRouterIPv6, eipExternalID), @@ -5924,7 +5924,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations", func() { egressNamespace := newNamespaceWithLabels(eipNamespace, egressPodLabel) _, node1Subnet, _ := net.ParseCIDR(v6Node1Subnet) _, node2Subnet, _ := net.ParseCIDR(v6Node2Subnet) - egressIPServedPodsASv4, _ := buildEgressIPServedPodsAddressSets(nil, types.DefaultNetworkName, DefaultNetworkControllerName) + egressIPServedPodsASv4, _ := buildEgressIPServedPodsAddressSets(nil) dynamicNeighRouters := "true" if config.OVNKubernetesFeature.EnableInterconnect { dynamicNeighRouters = "false" @@ -6040,7 +6040,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations", func() { "stateless": "false", }, } - egressIPServedPodsASv4, _ := buildEgressIPServedPodsAddressSets(nil) + egressIPServedPodsASv4, _ = buildEgressIPServedPodsAddressSets(nil) expectedDatabaseState := []libovsdbtest.TestData{ &nbdb.LogicalRouterPolicy{ Priority: types.EgressIPReroutePriority, @@ -10613,7 +10613,7 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations", func() { gomega.Expect(err).NotTo(gomega.HaveOccurred()) }) - ginkgo.DescribeTable( + ginkgotable.DescribeTable( "DualStack cluster with single stack egressIP removes the correct snat rule when DisableSNATMultipleGWs=true", func( egressIP net.IP, @@ -10884,11 +10884,11 @@ var _ = ginkgo.Describe("OVN master EgressIP Operations", func() { err := app.Run([]string{app.Name}) gomega.Expect(err).NotTo(gomega.HaveOccurred()) }, - ginkgo.Entry( + ginkgotable.Entry( "When EgressIP is ipv4", net.ParseIP("192.168.126.101"), ), - ginkgo.Entry( + ginkgotable.Entry( "When EgressIP is ipv6", net.ParseIP("fc00:f853:0ccd:e793:ffff:ffff:ffff:0000"), ), 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 +}