From b611ffd04f3448e70bc6a7744cb228e4f408615a Mon Sep 17 00:00:00 2001 From: Quan Tian Date: Wed, 20 Jul 2022 01:07:33 +0800 Subject: [PATCH] Add unit test for pkg/agent/route Signed-off-by: Quan Tian --- hack/update-codegen-dockerized.sh | 39 +- pkg/agent/nodeportlocal/rules/iptable_rule.go | 2 +- pkg/agent/route/route_linux.go | 169 +- pkg/agent/route/route_linux_test.go | 1632 +++++++++++++++++ pkg/agent/util/ipset/ipset.go | 26 +- pkg/agent/util/ipset/testing/mock_ipset.go | 106 ++ pkg/agent/util/iptables/iptables.go | 24 +- .../iptables/testing/mock_iptables_linux.go | 178 ++ pkg/agent/util/net_linux.go | 10 - pkg/agent/util/netlink/netlink_linux.go | 95 + .../netlink/testing/mock_netlink_linux.go | 208 +++ test/integration/agent/route_test.go | 4 + 12 files changed, 2385 insertions(+), 108 deletions(-) create mode 100644 pkg/agent/route/route_linux_test.go create mode 100644 pkg/agent/util/ipset/testing/mock_ipset.go create mode 100644 pkg/agent/util/iptables/testing/mock_iptables_linux.go create mode 100644 pkg/agent/util/netlink/netlink_linux.go create mode 100644 pkg/agent/util/netlink/testing/mock_netlink_linux.go diff --git a/hack/update-codegen-dockerized.sh b/hack/update-codegen-dockerized.sh index 9f51f365dd6..4984b5024ad 100755 --- a/hack/update-codegen-dockerized.sh +++ b/hack/update-codegen-dockerized.sh @@ -53,6 +53,9 @@ function generate_mocks { "pkg/agent/ipassigner IPAssigner testing" "pkg/agent/secondarynetwork/podwatch InterfaceConfigurator testing" "pkg/agent/secondarynetwork/ipam IPAMDelegator testing" + "pkg/agent/util/ipset Interface testing" + "pkg/agent/util/iptables Interface testing mock_iptables_linux.go" + "pkg/agent/util/netlink Interface testing mock_netlink_linux.go" "pkg/antctl AntctlClient ." "pkg/controller/networkpolicy EndpointQuerier testing" "pkg/controller/querier ControllerQuerier testing" @@ -71,21 +74,29 @@ function generate_mocks { current_year=$(date +"%Y") sed -i "s/YEAR/${current_year}/g" hack/boilerplate/license_header.raw.txt for target in "${MOCKGEN_TARGETS[@]}"; do - read -r package interfaces mock_package <<<"${target}" - package_name=$(basename "${package}") - if [[ "${mock_package}" == "." ]]; then # generate mocks in same package as src - $GOPATH/bin/mockgen \ - -copyright_file hack/boilerplate/license_header.raw.txt \ - -destination "${package}/mock_${package_name}_test.go" \ - -package="${package_name}" \ - "${ANTREA_PKG}/${package}" "${interfaces}" - else # generate mocks in subpackage - $GOPATH/bin/mockgen \ - -copyright_file hack/boilerplate/license_header.raw.txt \ - -destination "${package}/${mock_package}/mock_${package_name}.go" \ - -package="${mock_package}" \ - "${ANTREA_PKG}/${package}" "${interfaces}" + read -r src_package interfaces dst_package_name dst_file_name <<<"${target}" + src_package_name=$(basename "${src_package}") + # Generate mocks in the same package as src if dst_file_name is ".", otherwise create a sub package. + if [[ "${dst_package_name}" == "." ]]; then + package="${src_package_name}" + if [ -n "${dst_file_name}" ]; then + destination="${src_package}/${dst_file_name}" + else + destination="${src_package}/mock_${src_package_name}_test.go" + fi + else + package="${dst_package_name}" + if [ -n "${dst_file_name}" ]; then + destination="${src_package}/${dst_package_name}/${dst_file_name}" + else + destination="${src_package}/${dst_package_name}/mock_${src_package_name}.go" + fi fi + $GOPATH/bin/mockgen \ + -copyright_file hack/boilerplate/license_header.raw.txt \ + -destination "${destination}" \ + -package "${package}" \ + "${ANTREA_PKG}/${src_package}" "${interfaces}" done git checkout HEAD -- hack/boilerplate/license_header.raw.txt } diff --git a/pkg/agent/nodeportlocal/rules/iptable_rule.go b/pkg/agent/nodeportlocal/rules/iptable_rule.go index def5bca618d..5d68bb7c527 100644 --- a/pkg/agent/nodeportlocal/rules/iptable_rule.go +++ b/pkg/agent/nodeportlocal/rules/iptable_rule.go @@ -110,7 +110,7 @@ func (ipt *iptablesRules) AddAllRules(nplList []PodNodePort) error { } } writeLine(iptablesData, "COMMIT") - if err := ipt.table.Restore(iptablesData.Bytes(), false, false); err != nil { + if err := ipt.table.Restore(iptablesData.String(), false, false); err != nil { return err } return nil diff --git a/pkg/agent/route/route_linux.go b/pkg/agent/route/route_linux.go index e7c4791d6e7..50ab0dc0d46 100644 --- a/pkg/agent/route/route_linux.go +++ b/pkg/agent/route/route_linux.go @@ -36,6 +36,7 @@ import ( "antrea.io/antrea/pkg/agent/util" "antrea.io/antrea/pkg/agent/util/ipset" "antrea.io/antrea/pkg/agent/util/iptables" + utilnetlink "antrea.io/antrea/pkg/agent/util/netlink" "antrea.io/antrea/pkg/agent/util/sysctl" binding "antrea.io/antrea/pkg/ovs/openflow" "antrea.io/antrea/pkg/ovs/ovsconfig" @@ -94,7 +95,9 @@ type Client struct { nodeConfig *config.NodeConfig networkConfig *config.NetworkConfig noSNAT bool - ipt *iptables.Client + ipt iptables.Interface + ipset ipset.Interface + netlink utilnetlink.Interface // nodeRoutes caches ip routes to remote Pods. It's a map of podCIDR to routes. nodeRoutes sync.Map // nodeNeighbors caches IPv6 Neighbors to remote host gateway @@ -106,6 +109,7 @@ type Client struct { proxyAll bool connectUplinkToBridge bool multicastEnabled bool + isCloudEKS bool // serviceRoutes caches ip routes about Services. serviceRoutes sync.Map // serviceNeighbors caches neighbors. @@ -132,6 +136,9 @@ func NewClient(networkConfig *config.NetworkConfig, noSNAT, proxyAll, connectUpl proxyAll: proxyAll, multicastEnabled: multicastEnabled, connectUplinkToBridge: connectUplinkToBridge, + ipset: ipset.NewClient(), + netlink: utilnetlink.NewClient(), + isCloudEKS: env.IsCloudEKS(), }, nil } @@ -141,11 +148,17 @@ func (c *Client) Initialize(nodeConfig *config.NodeConfig, done func()) error { c.nodeConfig = nodeConfig c.iptablesInitialized = make(chan struct{}) + var err error // Sets up the ipset that will be used in iptables. - if err := c.syncIPSet(); err != nil { + if err = c.syncIPSet(); err != nil { return fmt.Errorf("failed to initialize ipset: %v", err) } + // TODO: check whether can be executed without lock + c.ipt, err = iptables.New(c.networkConfig.IPv4Enabled, c.networkConfig.IPv6Enabled) + if err != nil { + return fmt.Errorf("error creating IPTables instance: %v", err) + } // Sets up the iptables infrastructure required to route packets in host network. // It's called in a goroutine because xtables lock may not be acquired immediately. go func() { @@ -216,7 +229,7 @@ func (c *Client) syncIPInfra() { } func (c *Client) syncRoutes() error { - routeList, err := netlink.RouteList(nil, netlink.FAMILY_ALL) + routeList, err := c.netlink.RouteList(nil, netlink.FAMILY_ALL) if err != nil { return err } @@ -233,7 +246,7 @@ func (c *Client) syncRoutes() error { if ok && routeEqual(route, r) { return true } - if err := netlink.RouteReplace(route); err != nil { + if err := c.netlink.RouteReplace(route); err != nil { klog.Errorf("Failed to add route to the gateway: %v", err) return false } @@ -296,10 +309,10 @@ func (c *Client) syncIPSet() error { if c.networkConfig.TrafficEncapMode.IsNetworkPolicyOnly() { return nil } - if err := ipset.CreateIPSet(antreaPodIPSet, ipset.HashNet, false); err != nil { + if err := c.ipset.CreateIPSet(antreaPodIPSet, ipset.HashNet, false); err != nil { return err } - if err := ipset.CreateIPSet(antreaPodIP6Set, ipset.HashNet, true); err != nil { + if err := c.ipset.CreateIPSet(antreaPodIP6Set, ipset.HashNet, true); err != nil { return err } @@ -307,7 +320,7 @@ func (c *Client) syncIPSet() error { for _, podCIDR := range []*net.IPNet{c.nodeConfig.PodIPv4CIDR, c.nodeConfig.PodIPv6CIDR} { if podCIDR != nil { ipsetName := getIPSetName(podCIDR.IP) - if err := ipset.AddEntry(ipsetName, podCIDR.String()); err != nil { + if err := c.ipset.AddEntry(ipsetName, podCIDR.String()); err != nil { return err } } @@ -315,23 +328,23 @@ func (c *Client) syncIPSet() error { // If proxy full is enabled, create NodePort ipset. if c.proxyAll { - if err := ipset.CreateIPSet(antreaNodePortIPSet, ipset.HashIPPort, false); err != nil { + if err := c.ipset.CreateIPSet(antreaNodePortIPSet, ipset.HashIPPort, false); err != nil { return err } - if err := ipset.CreateIPSet(antreaNodePortIP6Set, ipset.HashIPPort, true); err != nil { + if err := c.ipset.CreateIPSet(antreaNodePortIP6Set, ipset.HashIPPort, true); err != nil { return err } c.nodePortsIPv4.Range(func(k, _ interface{}) bool { ipSetEntry := k.(string) - if err := ipset.AddEntry(antreaNodePortIPSet, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(antreaNodePortIPSet, ipSetEntry); err != nil { return false } return true }) c.nodePortsIPv6.Range(func(k, _ interface{}) bool { ipSetEntry := k.(string) - if err := ipset.AddEntry(antreaNodePortIP6Set, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(antreaNodePortIP6Set, ipSetEntry); err != nil { return false } return true @@ -339,31 +352,31 @@ func (c *Client) syncIPSet() error { } if c.connectUplinkToBridge { - if err := ipset.CreateIPSet(localAntreaFlexibleIPAMPodIPSet, ipset.HashIP, false); err != nil { + if err := c.ipset.CreateIPSet(localAntreaFlexibleIPAMPodIPSet, ipset.HashIP, false); err != nil { return err } - if err := ipset.CreateIPSet(localAntreaFlexibleIPAMPodIP6Set, ipset.HashIP, true); err != nil { + if err := c.ipset.CreateIPSet(localAntreaFlexibleIPAMPodIP6Set, ipset.HashIP, true); err != nil { return err } } if c.multicastEnabled && c.networkConfig.TrafficEncapMode.SupportsEncap() { - if err := ipset.CreateIPSet(clusterNodeIPSet, ipset.HashIP, false); err != nil { + if err := c.ipset.CreateIPSet(clusterNodeIPSet, ipset.HashIP, false); err != nil { return err } - if err := ipset.CreateIPSet(clusterNodeIP6Set, ipset.HashIP, true); err != nil { + if err := c.ipset.CreateIPSet(clusterNodeIP6Set, ipset.HashIP, true); err != nil { return err } c.clusterNodeIPs.Range(func(_, v interface{}) bool { ipsetEntry := v.(string) - if err := ipset.AddEntry(clusterNodeIPSet, ipsetEntry); err != nil { + if err := c.ipset.AddEntry(clusterNodeIPSet, ipsetEntry); err != nil { return false } return true }) c.clusterNodeIP6s.Range(func(_, v interface{}) bool { ipSetEntry := v.(string) - if err := ipset.AddEntry(clusterNodeIP6Set, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(clusterNodeIP6Set, ipSetEntry); err != nil { return false } return true @@ -459,12 +472,6 @@ func (c *Client) writeEKSNATRules(iptablesData *bytes.Buffer) { // syncIPTables ensure that the iptables infrastructure we use is set up. // It's idempotent and can safely be called on every startup. func (c *Client) syncIPTables() error { - var err error - - c.ipt, err = iptables.New(c.networkConfig.IPv4Enabled, c.networkConfig.IPv6Enabled) - if err != nil { - return fmt.Errorf("error creating IPTables instance: %v", err) - } // Create the antrea managed chains and link them to built-in chains. // We cannot use iptables-restore for these jump rules because there // are non antrea managed rules in built-in chains. @@ -482,7 +489,7 @@ func (c *Client) syncIPTables() error { {iptables.MangleTable, iptables.PreRoutingChain, antreaMangleChain, "Antrea: jump to Antrea mangle rules"}, // TODO: unify the chain naming style {iptables.MangleTable, iptables.OutputChain, antreaOutputChain, "Antrea: jump to Antrea output rules"}, } - if c.proxyAll || env.IsCloudEKS() { + if c.proxyAll || c.isCloudEKS { jumpRules = append(jumpRules, jumpRule{iptables.NATTable, iptables.PreRoutingChain, antreaPreRoutingChain, "Antrea: jump to Antrea prerouting rules"}) } if c.proxyAll { @@ -524,7 +531,7 @@ func (c *Client) syncIPTables() error { false) // Setting --noflush to keep the previous contents (i.e. non antrea managed chains) of the tables. - if err := c.ipt.Restore(iptablesData.Bytes(), false, false); err != nil { + if err := c.ipt.Restore(iptablesData.String(), false, false); err != nil { return err } } @@ -541,7 +548,7 @@ func (c *Client) syncIPTables() error { snatMarkToIPv6, true) // Setting --noflush to keep the previous contents (i.e. non antrea managed chains) of the tables. - if err := c.ipt.Restore(iptablesData.Bytes(), false, true); err != nil { + if err := c.ipt.Restore(iptablesData.String(), false, true); err != nil { return err } } @@ -617,7 +624,7 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, // When Antrea is used to enforce NetworkPolicies in EKS, additional iptables // mangle rules are required. See https://github.com/antrea-io/antrea/issues/678. // These rules are only needed for IPv4. - if env.IsCloudEKS() && !isIPv6 { + if c.isCloudEKS && !isIPv6 { c.writeEKSMangleRules(iptablesData) } @@ -677,7 +684,7 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, writeLine(iptablesData, "COMMIT") writeLine(iptablesData, "*nat") - if c.proxyAll || env.IsCloudEKS() { + if c.proxyAll || c.isCloudEKS { writeLine(iptablesData, iptables.MakeChainLine(antreaPreRoutingChain)) } if c.proxyAll { @@ -784,7 +791,7 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, // When Antrea is used to enforce NetworkPolicies in EKS, additional iptables // nat rules are required. See https://github.com/antrea-io/antrea/issues/3946. // These rules are only needed for IPv4. - if env.IsCloudEKS() && !isIPv6 { + if c.isCloudEKS && !isIPv6 { c.writeEKSNATRules(iptablesData) } @@ -794,16 +801,20 @@ func (c *Client) restoreIptablesData(podCIDR *net.IPNet, func (c *Client) initIPRoutes() error { if c.networkConfig.TrafficEncapMode.IsNetworkPolicyOnly() { - gwLink := util.GetNetLink(c.nodeConfig.GatewayConfig.Name) + //gwLink := util.GetNetLink(c.nodeConfig.GatewayConfig.Name) + gwLink, err := c.netlink.LinkByName(c.nodeConfig.GatewayConfig.Name) + if err != nil { + return fmt.Errorf("error getting link %s: %v", c.nodeConfig.GatewayConfig.Name, err) + } if c.nodeConfig.NodeTransportIPv4Addr != nil { _, gwIP, _ := net.ParseCIDR(fmt.Sprintf("%s/32", c.nodeConfig.NodeTransportIPv4Addr.IP.String())) - if err := netlink.AddrReplace(gwLink, &netlink.Addr{IPNet: gwIP}); err != nil { + if err := c.netlink.AddrReplace(gwLink, &netlink.Addr{IPNet: gwIP}); err != nil { return fmt.Errorf("failed to add address %s to gw %s: %v", gwIP, gwLink.Attrs().Name, err) } } if c.nodeConfig.NodeTransportIPv6Addr != nil { _, gwIP, _ := net.ParseCIDR(fmt.Sprintf("%s/128", c.nodeConfig.NodeTransportIPv6Addr.IP.String())) - if err := netlink.AddrReplace(gwLink, &netlink.Addr{IPNet: gwIP}); err != nil { + if err := c.netlink.AddrReplace(gwLink, &netlink.Addr{IPNet: gwIP}); err != nil { return fmt.Errorf("failed to add address %s to gw %s: %v", gwIP, gwLink.Attrs().Name, err) } } @@ -840,7 +851,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error { // Remove orphaned podCIDRs from ipset. for _, ipsetName := range []string{antreaPodIPSet, antreaPodIP6Set} { - entries, err := ipset.ListEntries(ipsetName) + entries, err := c.ipset.ListEntries(ipsetName) if err != nil { return err } @@ -849,7 +860,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error { continue } klog.Infof("Deleting orphaned Pod IP %s from ipset and route table", entry) - if err := ipset.DelEntry(ipsetName, entry); err != nil { + if err := c.ipset.DelEntry(ipsetName, entry); err != nil { return err } _, cidr, err := net.ParseCIDR(entry) @@ -857,12 +868,11 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error { return err } route := &netlink.Route{Dst: cidr} - if err := netlink.RouteDel(route); err != nil && err != unix.ESRCH { + if err := c.netlink.RouteDel(route); err != nil && err != unix.ESRCH { return err } } } - // Remove any unknown routes on Antrea gateway. routes, err := c.listIPRoutesOnGW() if err != nil { @@ -887,7 +897,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error { } klog.Infof("Deleting unknown route %v", route) - if err := netlink.RouteDel(&route); err != nil && err != unix.ESRCH { + if err := c.netlink.RouteDel(&route); err != nil && err != unix.ESRCH { return err } } @@ -911,7 +921,7 @@ func (c *Client) Reconcile(podCIDRs []string, svcIPs map[string]bool) error { continue } klog.V(4).Infof("Deleting orphaned IPv6 neighbor %v", actualNeigh) - if err := netlink.NeighDel(actualNeigh); err != nil { + if err := c.netlink.NeighDel(actualNeigh); err != nil { return err } } @@ -931,11 +941,11 @@ func (c *Client) isServiceRoute(route *netlink.Route) bool { func (c *Client) listIPRoutesOnGW() ([]netlink.Route, error) { filter := &netlink.Route{ LinkIndex: c.nodeConfig.GatewayConfig.LinkIndex} - routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, filter, netlink.RT_FILTER_OIF) + routes, err := c.netlink.RouteListFiltered(netlink.FAMILY_V4, filter, netlink.RT_FILTER_OIF) if err != nil { return nil, err } - ipv6Routes, err := netlink.RouteListFiltered(netlink.FAMILY_V6, filter, netlink.RT_FILTER_OIF) + ipv6Routes, err := c.netlink.RouteListFiltered(netlink.FAMILY_V6, filter, netlink.RT_FILTER_OIF) if err != nil { return nil, err } @@ -958,7 +968,7 @@ func getIPv6Gateways(podCIDRs []string) sets.String { } func (c *Client) listIPv6NeighborsOnGateway() (map[string]*netlink.Neigh, error) { - neighs, err := netlink.NeighList(c.nodeConfig.GatewayConfig.LinkIndex, netlink.FAMILY_V6) + neighs, err := c.netlink.NeighList(c.nodeConfig.GatewayConfig.LinkIndex, netlink.FAMILY_V6) if err != nil { return nil, err } @@ -984,7 +994,7 @@ func (c *Client) AddRoutes(podCIDR *net.IPNet, nodeName string, nodeIP, nodeGwIP podCIDRStr := podCIDR.String() ipsetName := getIPSetName(podCIDR.IP) // Add this podCIDR to antreaPodIPSet so that packets to them won't be masqueraded when they leave the host. - if err := ipset.AddEntry(ipsetName, podCIDRStr); err != nil { + if err := c.ipset.AddEntry(ipsetName, podCIDRStr); err != nil { return err } // Install routes to this Node. @@ -1030,7 +1040,7 @@ func (c *Client) AddRoutes(podCIDR *net.IPNet, nodeName string, nodeIP, nodeGwIP klog.InfoS("Skip adding routes to peer", "node", nodeName, "ip", nodeIP, "podCIDR", podCIDR) } for _, route := range routes { - if err := netlink.RouteReplace(route); err != nil { + if err := c.netlink.RouteReplace(route); err != nil { return fmt.Errorf("failed to install route to peer %s (%s) with netlink. Route config: %s. Error: %v", nodeName, nodeIP, route.String(), err) } } @@ -1039,7 +1049,7 @@ func (c *Client) AddRoutes(podCIDR *net.IPNet, nodeName string, nodeIP, nodeGwIP routeToNodeGwIPNetv6 := &netlink.Route{ Dst: &net.IPNet{IP: nodeGwIP, Mask: net.CIDRMask(128, 128)}, } - if err := netlink.RouteDel(routeToNodeGwIPNetv6); err == nil { + if err := c.netlink.RouteDel(routeToNodeGwIPNetv6); err == nil { klog.InfoS("Deleted route to peer gateway", "node", nodeName, "nodeIP", nodeIP, "nodeGatewayIP", nodeGwIP) } else if err != unix.ESRCH { return fmt.Errorf("failed to delete route to peer gateway on Node %s (%s) with netlink. Route config: %s. Error: %v", @@ -1050,7 +1060,7 @@ func (c *Client) AddRoutes(podCIDR *net.IPNet, nodeName string, nodeIP, nodeGwIP Family: netlink.FAMILY_V6, IP: nodeGwIP, } - if err := netlink.NeighDel(neigh); err == nil { + if err := c.netlink.NeighDel(neigh); err == nil { klog.InfoS("Deleted neigh to peer gateway", "node", nodeName, "nodeIP", nodeIP, "nodeGatewayIP", nodeGwIP) c.nodeNeighbors.Delete(podCIDRStr) } else if err != unix.ENOENT { @@ -1066,7 +1076,7 @@ func (c *Client) AddRoutes(podCIDR *net.IPNet, nodeName string, nodeIP, nodeGwIP IP: nodeGwIP, HardwareAddr: globalVMAC, } - if err := netlink.NeighSet(neigh); err != nil { + if err := c.netlink.NeighSet(neigh); err != nil { return fmt.Errorf("failed to add neigh %v to gw %s: %v", neigh, c.nodeConfig.GatewayConfig.Name, err) } c.nodeNeighbors.Store(podCIDRStr, neigh) @@ -1085,7 +1095,7 @@ func (c *Client) DeleteRoutes(podCIDR *net.IPNet) error { podCIDRStr := podCIDR.String() ipsetName := getIPSetName(podCIDR.IP) // Delete this podCIDR from antreaPodIPSet as the CIDR is no longer for Pods. - if err := ipset.DelEntry(ipsetName, podCIDRStr); err != nil { + if err := c.ipset.DelEntry(ipsetName, podCIDRStr); err != nil { return err } @@ -1094,7 +1104,7 @@ func (c *Client) DeleteRoutes(podCIDR *net.IPNet) error { c.nodeRoutes.Delete(podCIDRStr) for _, r := range routes.([]*netlink.Route) { klog.V(4).Infof("Deleting route %v", r) - if err := netlink.RouteDel(r); err != nil && err != unix.ESRCH { + if err := c.netlink.RouteDel(r); err != nil && err != unix.ESRCH { c.nodeRoutes.Store(podCIDRStr, routes) return err } @@ -1106,7 +1116,7 @@ func (c *Client) DeleteRoutes(podCIDR *net.IPNet) error { if podCIDR.IP.To4() == nil { neigh, exists := c.nodeNeighbors.Load(podCIDRStr) if exists { - if err := netlink.NeighDel(neigh.(*netlink.Neigh)); err != nil { + if err := c.netlink.NeighDel(neigh.(*netlink.Neigh)); err != nil { return err } c.nodeNeighbors.Delete(podCIDRStr) @@ -1135,28 +1145,31 @@ func writeLine(buf *bytes.Buffer, words ...string) { // MigrateRoutesToGw moves routes (including assigned IP addresses if any) from link linkName to // host gateway. func (c *Client) MigrateRoutesToGw(linkName string) error { - gwLink := util.GetNetLink(c.nodeConfig.GatewayConfig.Name) - link, err := netlink.LinkByName(linkName) + gwLink, err := c.netlink.LinkByName(c.nodeConfig.GatewayConfig.Name) + if err != nil { + return fmt.Errorf("failed to get link %s: %w", c.nodeConfig.GatewayConfig.Name, err) + } + link, err := c.netlink.LinkByName(linkName) if err != nil { return fmt.Errorf("failed to get link %s: %w", linkName, err) } for _, family := range []int{netlink.FAMILY_V4, netlink.FAMILY_V6} { // Swap route first then address, otherwise route gets removed when address is removed. - routes, err := netlink.RouteList(link, family) + routes, err := c.netlink.RouteList(link, family) if err != nil { return fmt.Errorf("failed to get routes for link %s: %w", linkName, err) } for i := range routes { route := routes[i] route.LinkIndex = gwLink.Attrs().Index - if err = netlink.RouteReplace(&route); err != nil { + if err = c.netlink.RouteReplace(&route); err != nil { return fmt.Errorf("failed to add route %v to link %s: %w", &route, gwLink.Attrs().Name, err) } } // Swap address if any. - addrs, err := netlink.AddrList(link, family) + addrs, err := c.netlink.AddrList(link, family) if err != nil { return fmt.Errorf("failed to get addresses for %s: %w", linkName, err) } @@ -1165,11 +1178,11 @@ func (c *Client) MigrateRoutesToGw(linkName string) error { if addr.IP.IsLinkLocalMulticast() || addr.IP.IsLinkLocalUnicast() { continue } - if err = netlink.AddrDel(link, &addr); err != nil { + if err = c.netlink.AddrDel(link, &addr); err != nil { klog.Errorf("failed to delete addr %v from %s: %v", addr, link, err) } tmpAddr := &netlink.Addr{IPNet: addr.IPNet} - if err = netlink.AddrReplace(gwLink, tmpAddr); err != nil { + if err = c.netlink.AddrReplace(gwLink, tmpAddr); err != nil { return fmt.Errorf("failed to add addr %v to gw %s: %w", addr, gwLink.Attrs().Name, err) } } @@ -1179,16 +1192,18 @@ func (c *Client) MigrateRoutesToGw(linkName string) error { // UnMigrateRoutesFromGw moves route from gw to link linkName if provided; otherwise route is deleted func (c *Client) UnMigrateRoutesFromGw(route *net.IPNet, linkName string) error { - gwLink := util.GetNetLink(c.nodeConfig.GatewayConfig.Name) + gwLink, err := c.netlink.LinkByName(c.nodeConfig.GatewayConfig.Name) + if err != nil { + return fmt.Errorf("failed to get link %s: %w", c.nodeConfig.GatewayConfig.Name, err) + } var link netlink.Link - var err error if len(linkName) > 0 { - link, err = netlink.LinkByName(linkName) + link, err = c.netlink.LinkByName(linkName) if err != nil { return fmt.Errorf("failed to get link %s: %w", linkName, err) } } - routes, err := netlink.RouteList(gwLink, netlink.FAMILY_V4) + routes, err := c.netlink.RouteList(gwLink, netlink.FAMILY_V4) if err != nil { return fmt.Errorf("failed to get routes for link %s: %w", gwLink.Attrs().Name, err) } @@ -1197,9 +1212,9 @@ func (c *Client) UnMigrateRoutesFromGw(route *net.IPNet, linkName string) error if route.String() == rt.Dst.String() { if link != nil { rt.LinkIndex = link.Attrs().Index - return netlink.RouteReplace(&rt) + return c.netlink.RouteReplace(&rt) } - return netlink.RouteDel(&rt) + return c.netlink.RouteDel(&rt) } } return nil @@ -1252,13 +1267,13 @@ func (c *Client) addVirtualServiceIPRoute(isIPv6 bool) error { } neigh := generateNeigh(svcIP, linkIndex) - if err := netlink.NeighSet(neigh); err != nil { + if err := c.netlink.NeighSet(neigh); err != nil { return fmt.Errorf("failed to add new IP neighbour for %s: %w", svcIP, err) } c.serviceNeighbors.Store(svcIP.String(), neigh) route := generateRoute(svcIP, mask, nil, linkIndex, netlink.SCOPE_LINK) - if err := netlink.RouteReplace(route); err != nil { + if err := c.netlink.RouteReplace(route); err != nil { return fmt.Errorf("failed to install route for virtual Service IP %s: %w", svcIP.String(), err) } c.serviceRoutes.Store(svcIP.String(), route) @@ -1276,7 +1291,7 @@ func (c *Client) AddNodePort(nodePortAddresses []net.IP, port uint16, protocol b for i := range nodePortAddresses { ipSetEntry := fmt.Sprintf("%s,%s:%d", nodePortAddresses[i], transProtocol, port) - if err := ipset.AddEntry(ipSetName, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(ipSetName, ipSetEntry); err != nil { return err } if isIPv6 { @@ -1297,7 +1312,7 @@ func (c *Client) DeleteNodePort(nodePortAddresses []net.IP, port uint16, protoco for i := range nodePortAddresses { ipSetEntry := fmt.Sprintf("%s,%s:%d", nodePortAddresses[i], transProtocol, port) - if err := ipset.DelEntry(ipSetName, ipSetEntry); err != nil { + if err := c.ipset.DelEntry(ipSetName, ipSetEntry); err != nil { return err } if isIPv6 { @@ -1347,7 +1362,7 @@ func (c *Client) AddClusterIPRoute(svcIP net.IP) error { // Generate a route with the new destination CIDR and install it. newClusterIPCIDRMask, _ := newClusterIPCIDR.Mask.Size() route := generateRoute(newClusterIPCIDR.IP, newClusterIPCIDRMask, gw, linkIndex, scope) - if err = netlink.RouteReplace(route); err != nil { + if err = c.netlink.RouteReplace(route); err != nil { return fmt.Errorf("failed to install new ClusterIP route: %w", err) } // Store the new destination CIDR. @@ -1382,7 +1397,7 @@ func (c *Client) AddClusterIPRoute(svcIP net.IP) error { // Remove stale routes. for _, rt := range staleRoutes { - if err = netlink.RouteDel(rt); err != nil { + if err = c.netlink.RouteDel(rt); err != nil { return fmt.Errorf("failed to uninstall stale ClusterIP route %s: %w", rt.String(), err) } klog.V(4).InfoS("Uninstalled stale ClusterIP route successfully", "stale route", rt) @@ -1402,7 +1417,7 @@ func (c *Client) addVirtualNodePortDNATIPRoute(isIPv6 bool) error { mask = ipv6AddrLength } route := generateRoute(vIP, mask, gw, linkIndex, netlink.SCOPE_UNIVERSE) - if err := netlink.RouteReplace(route); err != nil { + if err := c.netlink.RouteReplace(route); err != nil { return fmt.Errorf("failed to install routing entry for virtual NodePort DNAT IP %s: %w", vIP.String(), err) } klog.V(4).InfoS("Added virtual NodePort DNAT IP route", "route", route) @@ -1428,7 +1443,7 @@ func (c *Client) addLoadBalancerIngressIPRoute(svcIPStr string) error { } route := generateRoute(svcIP, mask, gw, linkIndex, netlink.SCOPE_UNIVERSE) - if err := netlink.RouteReplace(route); err != nil { + if err := c.netlink.RouteReplace(route); err != nil { return fmt.Errorf("failed to install routing entry for LoadBalancer ingress IP %s: %w", svcIP.String(), err) } klog.V(4).InfoS("Added LoadBalancer ingress IP route", "route", route) @@ -1454,7 +1469,7 @@ func (c *Client) deleteLoadBalancerIngressIPRoute(svcIPStr string) error { } route := generateRoute(svcIP, mask, gw, linkIndex, netlink.SCOPE_UNIVERSE) - if err := netlink.RouteDel(route); err != nil { + if err := c.netlink.RouteDel(route); err != nil { if err.Error() == "no such process" { klog.InfoS("Failed to delete LoadBalancer ingress IP route since the route has been deleted", "route", route) } else { @@ -1509,7 +1524,7 @@ func (c *Client) AddLocalAntreaFlexibleIPAMPodRule(podAddresses []net.IP) error } ipSetEntry := podAddresses[i].String() ipSetName := getLocalAntreaFlexibleIPAMPodIPSetName(isIPv6) - if err := ipset.AddEntry(ipSetName, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(ipSetName, ipSetEntry); err != nil { return err } } @@ -1525,7 +1540,7 @@ func (c *Client) DeleteLocalAntreaFlexibleIPAMPodRule(podAddresses []net.IP) err isIPv6 := podAddresses[i].To4() == nil ipSetEntry := podAddresses[i].String() ipSetName := getLocalAntreaFlexibleIPAMPodIPSetName(isIPv6) - if err := ipset.DelEntry(ipSetName, ipSetEntry); err != nil { + if err := c.ipset.DelEntry(ipSetName, ipSetEntry); err != nil { return err } } @@ -1543,12 +1558,12 @@ func (c *Client) addNodeIP(podCIDR *net.IPNet, nodeIP net.IP) error { } ipSetEntry := nodeIP.String() if nodeIP.To4() != nil { - if err := ipset.AddEntry(clusterNodeIPSet, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(clusterNodeIPSet, ipSetEntry); err != nil { return err } c.clusterNodeIPs.Store(podCIDR.String(), ipSetEntry) } else { - if err := ipset.AddEntry(clusterNodeIP6Set, ipSetEntry); err != nil { + if err := c.ipset.AddEntry(clusterNodeIP6Set, ipSetEntry); err != nil { return err } c.clusterNodeIP6s.Store(podCIDR.String(), ipSetEntry) @@ -1570,7 +1585,7 @@ func (c *Client) deleteNodeIP(podCIDR *net.IPNet) error { return nil } ipSetEntry := obj.(string) - if err := ipset.DelEntry(clusterNodeIPSet, ipSetEntry); err != nil { + if err := c.ipset.DelEntry(clusterNodeIPSet, ipSetEntry); err != nil { return err } c.clusterNodeIPs.Delete(podCIDRStr) @@ -1580,7 +1595,7 @@ func (c *Client) deleteNodeIP(podCIDR *net.IPNet) error { return nil } ipSetEntry := obj.(string) - if err := ipset.DelEntry(clusterNodeIP6Set, ipSetEntry); err != nil { + if err := c.ipset.DelEntry(clusterNodeIP6Set, ipSetEntry); err != nil { return err } c.clusterNodeIP6s.Delete(podCIDRStr) diff --git a/pkg/agent/route/route_linux_test.go b/pkg/agent/route/route_linux_test.go new file mode 100644 index 00000000000..045e5b36323 --- /dev/null +++ b/pkg/agent/route/route_linux_test.go @@ -0,0 +1,1632 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package route + +import ( + "fmt" + "net" + "sync" + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/assert" + "github.com/vishvananda/netlink" + + "antrea.io/antrea/pkg/agent/config" + "antrea.io/antrea/pkg/agent/types" + "antrea.io/antrea/pkg/agent/util/ipset" + ipsettest "antrea.io/antrea/pkg/agent/util/ipset/testing" + "antrea.io/antrea/pkg/agent/util/iptables" + iptablestest "antrea.io/antrea/pkg/agent/util/iptables/testing" + netlinktest "antrea.io/antrea/pkg/agent/util/netlink/testing" + "antrea.io/antrea/pkg/ovs/openflow" + "antrea.io/antrea/pkg/ovs/ovsconfig" +) + +func getIPNet(cidr string) *net.IPNet { + _, ipNet, _ := net.ParseCIDR(cidr) + return ipNet +} + +func TestSyncRoutes(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + + nodeRoute1 := &netlink.Route{Dst: getIPNet("192.168.1.0/24"), Gw: net.ParseIP("1.1.1.1")} + nodeRoute2 := &netlink.Route{Dst: getIPNet("192.168.2.0/24"), Gw: net.ParseIP("1.1.1.2")} + serviceRoute1 := &netlink.Route{Dst: getIPNet("169.254.0.253/32"), LinkIndex: 10} + serviceRoute2 := &netlink.Route{Dst: getIPNet("169.254.0.252/32"), Gw: net.ParseIP("169.254.0.253")} + mockNetlink.EXPECT().RouteList(nil, netlink.FAMILY_ALL).Return([]netlink.Route{*nodeRoute1, *serviceRoute1}, nil) + mockNetlink.EXPECT().RouteReplace(nodeRoute2) + mockNetlink.EXPECT().RouteReplace(serviceRoute2) + mockNetlink.EXPECT().RouteReplace(&netlink.Route{ + LinkIndex: 10, + Dst: getIPNet("192.168.0.0/24"), + Src: net.ParseIP("192.168.0.1"), + Scope: netlink.SCOPE_LINK, + }) + mockNetlink.EXPECT().RouteReplace(&netlink.Route{ + LinkIndex: 10, + Dst: getIPNet("aabb:ccdd::/64"), + Src: net.ParseIP("aabb:ccdd::1"), + Scope: netlink.SCOPE_LINK, + }) + + c := &Client{ + netlink: mockNetlink, + proxyAll: true, + nodeRoutes: sync.Map{}, + serviceRoutes: sync.Map{}, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{LinkIndex: 10, IPv4: net.ParseIP("192.168.0.1"), IPv6: net.ParseIP("aabb:ccdd::1")}, + PodIPv4CIDR: getIPNet("192.168.0.0/24"), + PodIPv6CIDR: getIPNet("aabb:ccdd::/64"), + }, + } + c.nodeRoutes.Store("192.168.1.0/24", []*netlink.Route{nodeRoute1}) + c.nodeRoutes.Store("192.168.2.0/24", []*netlink.Route{nodeRoute2}) + c.serviceRoutes.Store("169.254.0.253/32", serviceRoute1) + c.serviceRoutes.Store("169.254.0.252/32", serviceRoute2) + + assert.NoError(t, c.syncRoutes()) +} + +func TestSyncIPSet(t *testing.T) { + podCIDRStr := "172.16.10.0/24" + _, podCIDR, _ := net.ParseCIDR(podCIDRStr) + podCIDRv6Str := "2001:ab03:cd04:55ef::/64" + _, podCIDRv6, _ := net.ParseCIDR(podCIDRv6Str) + tests := []struct { + name string + proxyAll bool + multicastEnabled bool + connectUplinkToBridge bool + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + nodePortsIPv4 []string + nodePortsIPv6 []string + clusterNodeIPs map[string]string + clusterNodeIP6s map[string]string + expectedCalls func(ipset *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "networkPolicyOnly", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeNetworkPolicyOnly, + }, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) {}, + }, + { + name: "noencap", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeNoEncap, + IPv4Enabled: true, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: podCIDR, + PodIPv6CIDR: podCIDRv6, + }, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.CreateIPSet(antreaPodIPSet, ipset.HashNet, false) + mockIPSet.CreateIPSet(antreaPodIP6Set, ipset.HashNet, true) + mockIPSet.AddEntry(antreaPodIPSet, podCIDRStr) + mockIPSet.AddEntry(antreaPodIP6Set, podCIDRv6Str) + }, + }, + { + name: "encap, proxyAll=true, multicastEnabled=true", + proxyAll: true, + multicastEnabled: true, + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + IPv4Enabled: true, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: podCIDR, + PodIPv6CIDR: podCIDRv6, + }, + nodePortsIPv4: []string{"192.168.0.2,tcp:10000", "127.0.0.1,tcp:10000"}, + nodePortsIPv6: []string{"fe80::e643:4bff:fe44:ee,tcp:10000", "::1,tcp:10000"}, + clusterNodeIPs: map[string]string{"172.16.3.0/24": "192.168.0.3", "172.16.4.0/24": "192.168.0.4"}, + clusterNodeIP6s: map[string]string{"2001:ab03:cd04:5503::/64": "fe80::e643:4bff:fe03", "2001:ab03:cd04:5504::/64": "fe80::e643:4bff:fe04"}, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.CreateIPSet(antreaPodIPSet, ipset.HashNet, false) + mockIPSet.CreateIPSet(antreaPodIP6Set, ipset.HashNet, true) + mockIPSet.AddEntry(antreaPodIPSet, podCIDRStr) + mockIPSet.AddEntry(antreaPodIP6Set, podCIDRv6Str) + mockIPSet.CreateIPSet(antreaNodePortIPSet, ipset.HashIPPort, false) + mockIPSet.CreateIPSet(antreaNodePortIP6Set, ipset.HashIPPort, true) + mockIPSet.AddEntry(antreaNodePortIPSet, "192.168.0.2,tcp:10000") + mockIPSet.AddEntry(antreaNodePortIPSet, "127.0.0.1,tcp:10000") + mockIPSet.AddEntry(antreaNodePortIP6Set, "fe80::e643:4bff:fe44:ee,tcp:10000") + mockIPSet.AddEntry(antreaNodePortIP6Set, "::1,tcp:10000") + mockIPSet.CreateIPSet(clusterNodeIPSet, ipset.HashIP, false) + mockIPSet.CreateIPSet(clusterNodeIP6Set, ipset.HashIP, true) + mockIPSet.AddEntry(clusterNodeIPSet, "192.168.0.3") + mockIPSet.AddEntry(clusterNodeIPSet, "192.168.0.4") + mockIPSet.AddEntry(clusterNodeIP6Set, "fe80::e643:4bff:fe03") + mockIPSet.AddEntry(clusterNodeIP6Set, "fe80::e643:4bff:fe04") + }, + }, + { + name: "noencap, connectUplinkToBridge=true", + connectUplinkToBridge: true, + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeNoEncap, + IPv4Enabled: true, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: podCIDR, + PodIPv6CIDR: podCIDRv6, + }, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.CreateIPSet(antreaPodIPSet, ipset.HashNet, false) + mockIPSet.CreateIPSet(antreaPodIP6Set, ipset.HashNet, true) + mockIPSet.AddEntry(antreaPodIPSet, podCIDRStr) + mockIPSet.AddEntry(antreaPodIP6Set, podCIDRv6Str) + mockIPSet.CreateIPSet(localAntreaFlexibleIPAMPodIPSet, ipset.HashIP, false) + mockIPSet.CreateIPSet(localAntreaFlexibleIPAMPodIP6Set, ipset.HashIP, true) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + ipset := ipsettest.NewMockInterface(ctrl) + c := &Client{ipset: ipset, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + proxyAll: tt.proxyAll, + multicastEnabled: tt.multicastEnabled, + connectUplinkToBridge: tt.connectUplinkToBridge, + nodePortsIPv4: sync.Map{}, + nodePortsIPv6: sync.Map{}, + clusterNodeIPs: sync.Map{}, + clusterNodeIP6s: sync.Map{}, + } + for _, nodePortIPv4 := range tt.nodePortsIPv4 { + c.nodePortsIPv4.Store(nodePortIPv4, struct{}{}) + } + for _, nodePortIPv6 := range tt.nodePortsIPv6 { + c.nodePortsIPv6.Store(nodePortIPv6, struct{}{}) + } + for cidr, nodeIP := range tt.clusterNodeIPs { + c.clusterNodeIPs.Store(cidr, nodeIP) + } + for cidr, nodeIP := range tt.clusterNodeIP6s { + c.clusterNodeIP6s.Store(cidr, nodeIP) + } + tt.expectedCalls(ipset.EXPECT()) + assert.NoError(t, c.syncIPSet()) + }) + } +} + +func TestSyncIPTables(t *testing.T) { + tests := []struct { + name string + isCloudEKS bool + proxyAll bool + multicastEnabled bool + connectUplinkToBridge bool + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + nodePortsIPv4 []string + nodePortsIPv6 []string + markToSNATIP map[uint32]string + expectedCalls func(iptables *iptablestest.MockInterfaceMockRecorder) + }{ + { + name: "encap,egress=true,multicastEnabled=true,proxyAll=true", + proxyAll: true, + multicastEnabled: true, + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + TunnelType: ovsconfig.GeneveTunnel, + IPv4Enabled: true, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: getIPNet("172.16.10.0/24"), + PodIPv6CIDR: getIPNet("2001:ab03:cd04:55ef::/64"), + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + }, + }, + markToSNATIP: map[uint32]string{ + 1: "1.1.1.1", + 2: "fe80::e643:4bff:fe02", + }, + expectedCalls: func(mockIPTables *iptablestest.MockInterfaceMockRecorder) { + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.RawTable, antreaPreRoutingChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.RawTable, iptables.PreRoutingChain, []string{"-j", antreaPreRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea prerouting rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.RawTable, antreaOutputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.RawTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.FilterTable, antreaForwardChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.FilterTable, iptables.ForwardChain, []string{"-j", antreaForwardChain, "-m", "comment", "--comment", "Antrea: jump to Antrea forwarding rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.NATTable, antreaPostRoutingChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.PostRoutingChain, []string{"-j", antreaPostRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea postrouting rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.MangleTable, antreaMangleChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.MangleTable, iptables.PreRoutingChain, []string{"-j", antreaMangleChain, "-m", "comment", "--comment", "Antrea: jump to Antrea mangle rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.MangleTable, antreaOutputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.MangleTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.NATTable, antreaPreRoutingChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.PreRoutingChain, []string{"-j", antreaPreRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea prerouting rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.NATTable, antreaOutputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) + mockIPTables.Restore(`*raw +:ANTREA-PREROUTING - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-PREROUTING -m comment --comment "Antrea: do not track incoming encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --dst-type LOCAL -j NOTRACK +-A ANTREA-OUTPUT -m comment --comment "Antrea: do not track outgoing encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --src-type LOCAL -j NOTRACK +-A ANTREA-PREROUTING -m comment --comment "Antrea: drop Pod multicast traffic forwarded via underlay network" -m set --match-set CLUSTER-NODE-IP src -d 224.0.0.0/4 -j DROP +COMMIT +*mangle +:ANTREA-MANGLE - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-OUTPUT -m comment --comment "Antrea: mark LOCAL output packets" -m addrtype --src-type LOCAL -o antrea-gw0 -j MARK --or-mark 0x80000000 +COMMIT +*filter +:ANTREA-FORWARD - [0:0] +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets from local Pods" -i antrea-gw0 -j ACCEPT +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets to local Pods" -o antrea-gw0 -j ACCEPT +COMMIT +*nat +:ANTREA-PREROUTING - [0:0] +-A ANTREA-PREROUTING -m comment --comment "Antrea: DNAT external to NodePort packets" -m set --match-set ANTREA-NODEPORT-IP dst,dst -j DNAT --to-destination 169.254.0.252 +:ANTREA-OUTPUT - [0:0] +-A ANTREA-OUTPUT -m comment --comment "Antrea: DNAT local to NodePort packets" -m set --match-set ANTREA-NODEPORT-IP dst,dst -j DNAT --to-destination 169.254.0.252 +:ANTREA-POSTROUTING - [0:0] +-A ANTREA-POSTROUTING -m comment --comment "Antrea: SNAT Pod to external packets" ! -o antrea-gw0 -m mark --mark 0x00000001/0x000000ff -j SNAT --to 1.1.1.1 +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade Pod to external packets" -s 172.16.10.0/24 -m set ! --match-set ANTREA-POD-IP dst ! -o antrea-gw0 -j MASQUERADE +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade LOCAL traffic" -o antrea-gw0 -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade OVS virtual source IP" -s 169.254.0.253 -j MASQUERADE +COMMIT +`, false, false) + mockIPTables.Restore(`*raw +:ANTREA-PREROUTING - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-PREROUTING -m comment --comment "Antrea: do not track incoming encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --dst-type LOCAL -j NOTRACK +-A ANTREA-OUTPUT -m comment --comment "Antrea: do not track outgoing encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --src-type LOCAL -j NOTRACK +-A ANTREA-PREROUTING -m comment --comment "Antrea: drop Pod multicast traffic forwarded via underlay network" -m set --match-set CLUSTER-NODE-IP6 src -d 224.0.0.0/4 -j DROP +COMMIT +*mangle +:ANTREA-MANGLE - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-OUTPUT -m comment --comment "Antrea: mark LOCAL output packets" -m addrtype --src-type LOCAL -o antrea-gw0 -j MARK --or-mark 0x80000000 +COMMIT +*filter +:ANTREA-FORWARD - [0:0] +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets from local Pods" -i antrea-gw0 -j ACCEPT +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets to local Pods" -o antrea-gw0 -j ACCEPT +COMMIT +*nat +:ANTREA-PREROUTING - [0:0] +-A ANTREA-PREROUTING -m comment --comment "Antrea: DNAT external to NodePort packets" -m set --match-set ANTREA-NODEPORT-IP6 dst,dst -j DNAT --to-destination fc01::aabb:ccdd:eefe +:ANTREA-OUTPUT - [0:0] +-A ANTREA-OUTPUT -m comment --comment "Antrea: DNAT local to NodePort packets" -m set --match-set ANTREA-NODEPORT-IP6 dst,dst -j DNAT --to-destination fc01::aabb:ccdd:eefe +:ANTREA-POSTROUTING - [0:0] +-A ANTREA-POSTROUTING -m comment --comment "Antrea: SNAT Pod to external packets" ! -o antrea-gw0 -m mark --mark 0x00000002/0x000000ff -j SNAT --to fe80::e643:4bff:fe02 +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade Pod to external packets" -s 2001:ab03:cd04:55ef::/64 -m set ! --match-set ANTREA-POD-IP6 dst ! -o antrea-gw0 -j MASQUERADE +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade LOCAL traffic" -o antrea-gw0 -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade OVS virtual source IP" -s fc01::aabb:ccdd:eeff -j MASQUERADE +COMMIT +`, false, true) + }, + }, + { + name: "encap,eks", + isCloudEKS: true, + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + TunnelType: ovsconfig.GeneveTunnel, + IPv4Enabled: true, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: getIPNet("172.16.10.0/24"), + PodIPv6CIDR: getIPNet("2001:ab03:cd04:55ef::/64"), + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + }, + }, + expectedCalls: func(mockIPTables *iptablestest.MockInterfaceMockRecorder) { + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.RawTable, antreaPreRoutingChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.RawTable, iptables.PreRoutingChain, []string{"-j", antreaPreRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea prerouting rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.RawTable, antreaOutputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.RawTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.FilterTable, antreaForwardChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.FilterTable, iptables.ForwardChain, []string{"-j", antreaForwardChain, "-m", "comment", "--comment", "Antrea: jump to Antrea forwarding rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.NATTable, antreaPostRoutingChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.PostRoutingChain, []string{"-j", antreaPostRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea postrouting rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.MangleTable, antreaMangleChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.MangleTable, iptables.PreRoutingChain, []string{"-j", antreaMangleChain, "-m", "comment", "--comment", "Antrea: jump to Antrea mangle rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.MangleTable, antreaOutputChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.MangleTable, iptables.OutputChain, []string{"-j", antreaOutputChain, "-m", "comment", "--comment", "Antrea: jump to Antrea output rules"}) + mockIPTables.EnsureChain(iptables.ProtocolDual, iptables.NATTable, antreaPreRoutingChain) + mockIPTables.AppendRule(iptables.ProtocolDual, iptables.NATTable, iptables.PreRoutingChain, []string{"-j", antreaPreRoutingChain, "-m", "comment", "--comment", "Antrea: jump to Antrea prerouting rules"}) + mockIPTables.Restore(`*raw +:ANTREA-PREROUTING - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-PREROUTING -m comment --comment "Antrea: do not track incoming encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --dst-type LOCAL -j NOTRACK +-A ANTREA-OUTPUT -m comment --comment "Antrea: do not track outgoing encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --src-type LOCAL -j NOTRACK +COMMIT +*mangle +:ANTREA-MANGLE - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-MANGLE -m comment --comment "Antrea: AWS, primary ENI" -i antrea-gw0 -j CONNMARK --restore-mark --nfmask 0x80 --ctmask 0x80 +-A ANTREA-OUTPUT -m comment --comment "Antrea: mark LOCAL output packets" -m addrtype --src-type LOCAL -o antrea-gw0 -j MARK --or-mark 0x80000000 +COMMIT +*filter +:ANTREA-FORWARD - [0:0] +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets from local Pods" -i antrea-gw0 -j ACCEPT +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets to local Pods" -o antrea-gw0 -j ACCEPT +COMMIT +*nat +:ANTREA-PREROUTING - [0:0] +:ANTREA-POSTROUTING - [0:0] +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade Pod to external packets" -s 172.16.10.0/24 -m set ! --match-set ANTREA-POD-IP dst ! -o antrea-gw0 -j MASQUERADE +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade LOCAL traffic" -o antrea-gw0 -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully +-A ANTREA-PREROUTING -i antrea-gw0 -m comment --comment "Antrea: AWS, outbound connections" -j AWS-CONNMARK-CHAIN-0 +-A ANTREA-PREROUTING -m comment --comment "Antrea: AWS, CONNMARK (first packet)" -j CONNMARK --restore-mark --nfmask 0x80 --ctmask 0x80 +COMMIT +`, false, false) + mockIPTables.Restore(`*raw +:ANTREA-PREROUTING - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-PREROUTING -m comment --comment "Antrea: do not track incoming encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --dst-type LOCAL -j NOTRACK +-A ANTREA-OUTPUT -m comment --comment "Antrea: do not track outgoing encapsulation packets" -m udp -p udp --dport 6081 -m addrtype --src-type LOCAL -j NOTRACK +COMMIT +*mangle +:ANTREA-MANGLE - [0:0] +:ANTREA-OUTPUT - [0:0] +-A ANTREA-OUTPUT -m comment --comment "Antrea: mark LOCAL output packets" -m addrtype --src-type LOCAL -o antrea-gw0 -j MARK --or-mark 0x80000000 +COMMIT +*filter +:ANTREA-FORWARD - [0:0] +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets from local Pods" -i antrea-gw0 -j ACCEPT +-A ANTREA-FORWARD -m comment --comment "Antrea: accept packets to local Pods" -o antrea-gw0 -j ACCEPT +COMMIT +*nat +:ANTREA-PREROUTING - [0:0] +:ANTREA-POSTROUTING - [0:0] +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade Pod to external packets" -s 2001:ab03:cd04:55ef::/64 -m set ! --match-set ANTREA-POD-IP6 dst ! -o antrea-gw0 -j MASQUERADE +-A ANTREA-POSTROUTING -m comment --comment "Antrea: masquerade LOCAL traffic" -o antrea-gw0 -m addrtype ! --src-type LOCAL --limit-iface-out -m addrtype --src-type LOCAL -j MASQUERADE --random-fully +COMMIT +`, false, true) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIPTables := iptablestest.NewMockInterface(ctrl) + c := &Client{ipt: mockIPTables, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + proxyAll: tt.proxyAll, + isCloudEKS: tt.isCloudEKS, + multicastEnabled: tt.multicastEnabled, + connectUplinkToBridge: tt.connectUplinkToBridge, + markToSNATIP: sync.Map{}, + } + for mark, snatIP := range tt.markToSNATIP { + c.markToSNATIP.Store(mark, net.ParseIP(snatIP)) + } + tt.expectedCalls(mockIPTables.EXPECT()) + assert.NoError(t, c.syncIPTables()) + }) + } +} + +func TestInitIPRoutes(t *testing.T) { + ipv4, nodeTransPortIPv4Addr, _ := net.ParseCIDR("172.16.10.2/24") + nodeTransPortIPv4Addr.IP = ipv4 + ipv6, nodeTransPortIPv6Addr, _ := net.ParseCIDR("fe80::e643:4bff:fe44:ee/64") + nodeTransPortIPv6Addr.IP = ipv6 + + tests := []struct { + name string + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + expectedCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "networkPolicyOnly", + networkConfig: &config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeNetworkPolicyOnly}, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{Name: "antrea-gw0"}, + NodeTransportIPv4Addr: nodeTransPortIPv4Addr, + NodeTransportIPv6Addr: nodeTransPortIPv6Addr, + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.LinkByName("antrea-gw0") + _, ipv4, _ := net.ParseCIDR("172.16.10.2/32") + mockNetlink.AddrReplace(gomock.Any(), &netlink.Addr{IPNet: ipv4}) + _, ipv6, _ := net.ParseCIDR("fe80::e643:4bff:fe44:ee/128") + mockNetlink.AddrReplace(gomock.Any(), &netlink.Addr{IPNet: ipv6}) + }, + }, + { + name: "encap", + networkConfig: &config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{Name: "antrea-gw0"}, + NodeTransportIPv4Addr: nodeTransPortIPv4Addr, + NodeTransportIPv6Addr: nodeTransPortIPv6Addr, + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) {}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + c := &Client{netlink: mockNetlink, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + } + tt.expectedCalls(mockNetlink.EXPECT()) + assert.NoError(t, c.initIPRoutes()) + }) + } +} + +func TestInitServiceIPRoutes(t *testing.T) { + tests := []struct { + name string + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + expectedCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "encap", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + IPv4Enabled: true, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{Name: "antrea-gw0", LinkIndex: 10}, + }, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.NeighSet(&netlink.Neigh{ + LinkIndex: 10, + Family: netlink.FAMILY_V4, + State: netlink.NUD_PERMANENT, + IP: config.VirtualServiceIPv4, + HardwareAddr: globalVMAC, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{ + IP: config.VirtualServiceIPv4, + Mask: net.CIDRMask(32, 32), + }, + Scope: netlink.SCOPE_LINK, + LinkIndex: 10, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{ + IP: config.VirtualNodePortDNATIPv4, + Mask: net.CIDRMask(32, 32), + }, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.NeighSet(&netlink.Neigh{ + LinkIndex: 10, + Family: netlink.FAMILY_V6, + State: netlink.NUD_PERMANENT, + IP: config.VirtualServiceIPv6, + HardwareAddr: globalVMAC, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{ + IP: config.VirtualServiceIPv6, + Mask: net.CIDRMask(128, 128), + }, + Scope: netlink.SCOPE_LINK, + LinkIndex: 10, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{ + IP: config.VirtualNodePortDNATIPv6, + Mask: net.CIDRMask(128, 128), + }, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + c := &Client{netlink: mockNetlink, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + } + tt.expectedCalls(mockNetlink.EXPECT()) + assert.NoError(t, c.initServiceIPRoutes()) + }) + } +} + +func TestReconcile(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + mockIPSet := ipsettest.NewMockInterface(ctrl) + c := &Client{netlink: mockNetlink, + ipset: mockIPSet, + proxyAll: true, + networkConfig: &config.NetworkConfig{}, + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: getIPNet("192.168.10.0/24"), + PodIPv6CIDR: getIPNet("2001:ab03:cd04:55ee:100a::/80"), + GatewayConfig: &config.GatewayConfig{LinkIndex: 10}, + }, + } + podCIDRs := []string{"192.168.0.0/24", "192.168.1.0/24", "2001:ab03:cd04:55ee:1001::/80", "2001:ab03:cd04:55ee:1002::/80"} + + mockIPSet.EXPECT().ListEntries(antreaPodIPSet).Return([]string{ + "192.168.0.0/24", // existing podCIDR, should not be deleted. + "192.168.2.0/24", // non-existing podCIDR, should be deleted. + }, nil) + mockIPSet.EXPECT().ListEntries(antreaPodIP6Set).Return([]string{ + "2001:ab03:cd04:55ee:1001::/80", // existing podCIDR, should not be deleted. + "2001:ab03:cd04:55ee:1003::/80", // non-existing podCIDR, should be deleted. + }, nil) + mockIPSet.EXPECT().DelEntry(antreaPodIPSet, "192.168.2.0/24") + mockIPSet.EXPECT().DelEntry(antreaPodIP6Set, "2001:ab03:cd04:55ee:1003::/80") + mockNetlink.EXPECT().RouteDel(&netlink.Route{Dst: getIPNet("192.168.2.0/24")}) + mockNetlink.EXPECT().RouteDel(&netlink.Route{Dst: getIPNet("2001:ab03:cd04:55ee:1003::/80")}) + + mockNetlink.EXPECT().RouteListFiltered(netlink.FAMILY_V4, &netlink.Route{LinkIndex: 10}, netlink.RT_FILTER_OIF).Return([]netlink.Route{ + {Dst: getIPNet("192.168.10.0/24")}, // local podCIDR, should not be deleted. + {Dst: getIPNet("192.168.1.0/24")}, // existing podCIDR, should not be deleted. + {Dst: getIPNet("169.254.0.253/32")}, // service route, should not be deleted. + {Dst: getIPNet("192.168.11.0/24")}, // non-existing podCIDR, should be deleted. + }, nil) + mockNetlink.EXPECT().RouteListFiltered(netlink.FAMILY_V6, &netlink.Route{LinkIndex: 10}, netlink.RT_FILTER_OIF).Return([]netlink.Route{ + {Dst: getIPNet("2001:ab03:cd04:55ee:100a::/80")}, // local podCIDR, should not be deleted. + {Dst: getIPNet("2001:ab03:cd04:55ee:1001::1/128")}, // existing podCIDR, should not be deleted. + {Dst: getIPNet("fc01::aabb:ccdd:eeff/128")}, // service route, should not be deleted. + {Dst: getIPNet("2001:ab03:cd04:55ee:100b::/80")}, // non-existing podCIDR, should be deleted. + }, nil) + mockNetlink.EXPECT().RouteDel(&netlink.Route{Dst: getIPNet("192.168.11.0/24")}) + mockNetlink.EXPECT().RouteDel(&netlink.Route{Dst: getIPNet("2001:ab03:cd04:55ee:100b::/80")}) + + mockNetlink.EXPECT().NeighList(10, netlink.FAMILY_V6).Return([]netlink.Neigh{ + {IP: net.ParseIP("2001:ab03:cd04:55ee:1001::1")}, // existing podCIDR, should not be deleted. + {IP: net.ParseIP("fc01::aabb:ccdd:eeff")}, // virtual service IP, should not be deleted. + {IP: net.ParseIP("2001:ab03:cd04:55ee:100b::1")}, // non-existing podCIDR, should be deleted. + }, nil) + mockNetlink.EXPECT().NeighDel(&netlink.Neigh{IP: net.ParseIP("2001:ab03:cd04:55ee:100b::1")}) + assert.NoError(t, c.Reconcile(podCIDRs, nil)) +} + +func TestAddRoutes(t *testing.T) { + ipv4, nodeTransPortIPv4Addr, _ := net.ParseCIDR("172.16.10.2/24") + nodeTransPortIPv4Addr.IP = ipv4 + ipv6, nodeTransPortIPv6Addr, _ := net.ParseCIDR("fe80::e643:4bff:fe44:ee/64") + nodeTransPortIPv6Addr.IP = ipv6 + + tests := []struct { + name string + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + podCIDR *net.IPNet + nodeName string + nodeIP net.IP + nodeGwIP net.IP + expectedIPSetCalls func(mockNetlink *ipsettest.MockInterfaceMockRecorder) + expectedNetlinkCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "wireGuard IPv4", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + TrafficEncryptionMode: config.TrafficEncryptionModeWireGuard, + IPv4Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + IPv4: net.ParseIP("1.1.1.1"), + LinkIndex: 10, + }, + WireGuardConfig: &config.WireGuardConfig{LinkIndex: 11}, + NodeTransportIPv4Addr: nodeTransPortIPv4Addr, + }, + podCIDR: getIPNet("192.168.10.0/24"), + nodeName: "node0", + nodeIP: net.ParseIP("1.1.1.10"), + nodeGwIP: net.ParseIP("192.168.10.1"), + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(antreaPodIPSet, "192.168.10.0/24") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Src: net.ParseIP("1.1.1.1"), + Dst: getIPNet("192.168.10.0/24"), + Scope: netlink.SCOPE_LINK, + LinkIndex: 11, + }) + }, + }, + { + name: "wireGuard IPv6", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + TrafficEncryptionMode: config.TrafficEncryptionModeWireGuard, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + IPv6: net.ParseIP("fe80::e643:4bff:fe44:1"), + LinkIndex: 10, + }, + WireGuardConfig: &config.WireGuardConfig{LinkIndex: 11}, + NodeTransportIPv6Addr: nodeTransPortIPv6Addr, + }, + podCIDR: getIPNet("2001:ab03:cd04:55ee:1001::/80"), + nodeName: "node0", + nodeIP: net.ParseIP("fe80::e643:4bff:fe44:2"), + nodeGwIP: net.ParseIP("2001:ab03:cd04:55ee:1001::1"), + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(antreaPodIP6Set, "2001:ab03:cd04:55ee:1001::/80") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Src: net.ParseIP("fe80::e643:4bff:fe44:1"), + Dst: getIPNet("2001:ab03:cd04:55ee:1001::/80"), + Scope: netlink.SCOPE_LINK, + LinkIndex: 11, + }) + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("2001:ab03:cd04:55ee:1001::1"), Mask: net.CIDRMask(128, 128)}, + }) + mockNetlink.NeighDel(&netlink.Neigh{ + LinkIndex: 10, + Family: netlink.FAMILY_V6, + IP: net.ParseIP("2001:ab03:cd04:55ee:1001::1"), + }) + }, + }, + { + name: "encap IPv4", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + IPv4Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + IPv4: net.ParseIP("1.1.1.1"), + LinkIndex: 10, + }, + NodeTransportIPv4Addr: nodeTransPortIPv4Addr, + }, + podCIDR: getIPNet("192.168.10.0/24"), + nodeName: "node0", + nodeIP: net.ParseIP("1.1.1.10"), + nodeGwIP: net.ParseIP("192.168.10.1"), + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(antreaPodIPSet, "192.168.10.0/24") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Gw: net.ParseIP("192.168.10.1"), + Dst: getIPNet("192.168.10.0/24"), + Flags: int(netlink.FLAG_ONLINK), + LinkIndex: 10, + }) + }, + }, + { + name: "encap IPv6", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeEncap, + IPv6Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + IPv6: net.ParseIP("fe80::e643:4bff:fe44:1"), + LinkIndex: 10, + }, + NodeTransportIPv6Addr: nodeTransPortIPv6Addr, + }, + podCIDR: getIPNet("2001:ab03:cd04:55ee:1001::/80"), + nodeName: "node0", + nodeIP: net.ParseIP("fe80::e643:4bff:fe44:2"), + nodeGwIP: net.ParseIP("2001:ab03:cd04:55ee:1001::1"), + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(antreaPodIP6Set, "2001:ab03:cd04:55ee:1001::/80") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Dst: getIPNet("2001:ab03:cd04:55ee:1001::1/128"), + LinkIndex: 10, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Gw: net.ParseIP("2001:ab03:cd04:55ee:1001::1"), + Dst: getIPNet("2001:ab03:cd04:55ee:1001::/80"), + LinkIndex: 10, + }) + mockNetlink.NeighSet(&netlink.Neigh{ + LinkIndex: 10, + Family: netlink.FAMILY_V6, + State: netlink.NUD_PERMANENT, + IP: net.ParseIP("2001:ab03:cd04:55ee:1001::1"), + HardwareAddr: globalVMAC, + }) + }, + }, + { + name: "noencap IPv4, direct routing", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeNoEncap, + IPv4Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + IPv4: net.ParseIP("192.168.1.1"), + LinkIndex: 10, + }, + NodeTransportIPv4Addr: nodeTransPortIPv4Addr, + }, + podCIDR: getIPNet("192.168.10.0/24"), + nodeName: "node0", + nodeIP: net.ParseIP("172.16.10.3"), // In the same subnet as local Node IP. + nodeGwIP: net.ParseIP("192.168.10.1"), + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(antreaPodIPSet, "192.168.10.0/24") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Gw: net.ParseIP("172.16.10.3"), + Dst: getIPNet("192.168.10.0/24"), + }) + }, + }, + { + name: "noencap IPv4, no direct routing", + networkConfig: &config.NetworkConfig{ + TrafficEncapMode: config.TrafficEncapModeNoEncap, + IPv4Enabled: true, + }, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + IPv4: net.ParseIP("192.168.1.1"), + LinkIndex: 10, + }, + NodeTransportIPv4Addr: nodeTransPortIPv4Addr, + }, + podCIDR: getIPNet("192.168.10.0/24"), + nodeName: "node0", + nodeIP: net.ParseIP("172.16.11.3"), // In different subnet from local Node IP. + nodeGwIP: net.ParseIP("192.168.10.1"), + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(antreaPodIPSet, "192.168.10.0/24") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) {}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + mockIPSet := ipsettest.NewMockInterface(ctrl) + c := &Client{netlink: mockNetlink, + ipset: mockIPSet, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + } + tt.expectedIPSetCalls(mockIPSet.EXPECT()) + tt.expectedNetlinkCalls(mockNetlink.EXPECT()) + assert.NoError(t, c.AddRoutes(tt.podCIDR, tt.nodeName, tt.nodeIP, tt.nodeGwIP)) + }) + } +} + +func TestDeleteRoutes(t *testing.T) { + tests := []struct { + name string + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + podCIDR *net.IPNet + existingNodeRoutes map[string][]*netlink.Route + existingNodeNeighbors map[string]*netlink.Neigh + nodeName string + expectedIPSetCalls func(mockNetlink *ipsettest.MockInterfaceMockRecorder) + expectedNetlinkCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + podCIDR: getIPNet("192.168.10.0/24"), + existingNodeRoutes: map[string][]*netlink.Route{ + "192.168.10.0/24": {{Gw: net.ParseIP("172.16.10.3"), Dst: getIPNet("192.168.10.0/24")}}, + "192.168.11.0/24": {{Gw: net.ParseIP("172.16.10.4"), Dst: getIPNet("192.168.11.0/24")}}, + }, + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.DelEntry(antreaPodIPSet, "192.168.10.0/24") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteDel(&netlink.Route{Gw: net.ParseIP("172.16.10.3"), Dst: getIPNet("192.168.10.0/24")}) + }, + }, + { + name: "IPv6", + podCIDR: getIPNet("2001:ab03:cd04:55ee:1001::/80"), + existingNodeRoutes: map[string][]*netlink.Route{ + "2001:ab03:cd04:55ee:1001::/80": {{Gw: net.ParseIP("fe80::e643:4bff:fe44:1"), Dst: getIPNet("2001:ab03:cd04:55ee:1001::/80")}}, + "2001:ab03:cd04:55ee:1002::/80": {{Gw: net.ParseIP("fe80::e643:4bff:fe44:2"), Dst: getIPNet("2001:ab03:cd04:55ee:1002::/80")}}, + }, + existingNodeNeighbors: map[string]*netlink.Neigh{}, + expectedIPSetCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.DelEntry(antreaPodIP6Set, "2001:ab03:cd04:55ee:1001::/80") + }, + expectedNetlinkCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteDel(&netlink.Route{Gw: net.ParseIP("fe80::e643:4bff:fe44:1"), Dst: getIPNet("2001:ab03:cd04:55ee:1001::/80")}) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + mockIPSet := ipsettest.NewMockInterface(ctrl) + c := &Client{netlink: mockNetlink, + ipset: mockIPSet, + networkConfig: tt.networkConfig, + nodeConfig: tt.nodeConfig, + nodeRoutes: sync.Map{}, + nodeNeighbors: sync.Map{}, + } + for podCIDR, nodeRoute := range tt.existingNodeRoutes { + c.nodeRoutes.Store(podCIDR, nodeRoute) + } + for podCIDR, nodeNeighbor := range tt.existingNodeNeighbors { + c.nodeNeighbors.Store(podCIDR, nodeNeighbor) + } + tt.expectedIPSetCalls(mockIPSet.EXPECT()) + tt.expectedNetlinkCalls(mockNetlink.EXPECT()) + assert.NoError(t, c.DeleteRoutes(tt.podCIDR)) + }) + } +} + +func TestMigrateRoutesToGw(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + mockIPSet := ipsettest.NewMockInterface(ctrl) + + gwLinkName := "antrea-gw0" + gwLink := &netlink.Device{LinkAttrs: netlink.LinkAttrs{Index: 11}} + linkName := "eth0" + link := &netlink.Device{LinkAttrs: netlink.LinkAttrs{Index: 10}} + linkAddr1, _ := netlink.ParseAddr("192.168.10.1/32") + linkAddr2, _ := netlink.ParseAddr("169.254.0.2/32") // LinkLocalUnicast address should not be migrated. + linkAddr3, _ := netlink.ParseAddr("2001:ab03:cd04:55ee:1001::1/80") + linkAddr4, _ := netlink.ParseAddr("fe80:ab03:cd04:55ee:1001::1/80") // LinkLocalUnicast address should not be migrated. + + mockNetlink.EXPECT().LinkByName(gwLinkName).Return(gwLink, nil) + mockNetlink.EXPECT().LinkByName(linkName).Return(link, nil) + mockNetlink.EXPECT().RouteList(link, netlink.FAMILY_V4).Return([]netlink.Route{ + {Gw: net.ParseIP("172.16.1.10"), Dst: getIPNet("192.168.10.0/24"), LinkIndex: 10}, + }, nil) + mockNetlink.EXPECT().RouteList(link, netlink.FAMILY_V6).Return([]netlink.Route{ + {Gw: net.ParseIP("fe80::e643:4bff:fe44:1"), Dst: getIPNet("2001:ab03:cd04:55ee:1001::/80"), LinkIndex: 10}, + }, nil) + mockNetlink.EXPECT().RouteReplace(&netlink.Route{Gw: net.ParseIP("172.16.1.10"), Dst: getIPNet("192.168.10.0/24"), LinkIndex: 11}) + mockNetlink.EXPECT().RouteReplace(&netlink.Route{Gw: net.ParseIP("fe80::e643:4bff:fe44:1"), Dst: getIPNet("2001:ab03:cd04:55ee:1001::/80"), LinkIndex: 11}) + mockNetlink.EXPECT().AddrList(link, netlink.FAMILY_V4).Return([]netlink.Addr{*linkAddr1, *linkAddr2}, nil) + mockNetlink.EXPECT().AddrList(link, netlink.FAMILY_V6).Return([]netlink.Addr{*linkAddr3, *linkAddr4}, nil) + mockNetlink.EXPECT().AddrDel(link, linkAddr1) + mockNetlink.EXPECT().AddrReplace(gwLink, linkAddr1) + mockNetlink.EXPECT().AddrDel(link, linkAddr3) + mockNetlink.EXPECT().AddrReplace(gwLink, linkAddr3) + + c := &Client{ + netlink: mockNetlink, + ipset: mockIPSet, + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{Name: gwLinkName}, + }, + } + c.MigrateRoutesToGw(linkName) +} + +func TestUnMigrateRoutesToGw(t *testing.T) { + gwLink := &netlink.Device{LinkAttrs: netlink.LinkAttrs{Index: 11}} + link := &netlink.Device{LinkAttrs: netlink.LinkAttrs{Index: 10}} + tests := []struct { + name string + nodeConfig *config.NodeConfig + route string + link string + expectedCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "link provided", + route: "192.168.10.0/24", + link: "eth0", + nodeConfig: &config.NodeConfig{GatewayConfig: &config.GatewayConfig{Name: "antrea-gw0"}}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.LinkByName("antrea-gw0").Return(gwLink, nil) + mockNetlink.LinkByName("eth0").Return(link, nil) + mockNetlink.RouteList(gwLink, netlink.FAMILY_V4).Return([]netlink.Route{ + {Gw: net.ParseIP("172.16.1.10"), Dst: getIPNet("192.168.10.0/24"), LinkIndex: 11}, + }, nil) + mockNetlink.RouteReplace(&netlink.Route{Gw: net.ParseIP("172.16.1.10"), Dst: getIPNet("192.168.10.0/24"), LinkIndex: 10}) + }, + }, + { + name: "link not provided", + route: "192.168.10.0/24", + link: "", + nodeConfig: &config.NodeConfig{GatewayConfig: &config.GatewayConfig{Name: "antrea-gw0"}}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.LinkByName("antrea-gw0").Return(gwLink, nil) + mockNetlink.RouteList(gwLink, netlink.FAMILY_V4).Return([]netlink.Route{ + {Gw: net.ParseIP("172.16.1.10"), Dst: getIPNet("192.168.10.0/24"), LinkIndex: 11}, + }, nil) + mockNetlink.RouteDel(&netlink.Route{Gw: net.ParseIP("172.16.1.10"), Dst: getIPNet("192.168.10.0/24"), LinkIndex: 11}) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + c := &Client{ + netlink: mockNetlink, + nodeConfig: tt.nodeConfig, + } + tt.expectedCalls(mockNetlink.EXPECT()) + c.UnMigrateRoutesFromGw(getIPNet(tt.route), tt.link) + }) + } +} + +func TestAddSNATRule(t *testing.T) { + tests := []struct { + name string + networkConfig *config.NetworkConfig + nodeConfig *config.NodeConfig + snatIP net.IP + mark uint32 + expectedCalls func(mockIPTables *iptablestest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + }, + }, + snatIP: net.ParseIP("1.1.1.1"), + mark: 10, + expectedCalls: func(mockIPTables *iptablestest.MockInterfaceMockRecorder) { + mockIPTables.InsertRule(iptables.ProtocolIPv4, iptables.NATTable, antreaPostRoutingChain, []string{ + "-m", "comment", "--comment", "Antrea: SNAT Pod to external packets", + "!", "-o", "antrea-gw0", + "-m", "mark", "--mark", fmt.Sprintf("%#08x/%#08x", 10, types.SNATIPMarkMask), + "-j", iptables.SNATTarget, "--to", "1.1.1.1", + }) + }, + }, + { + name: "IPv6", + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + }, + }, + snatIP: net.ParseIP("fe80::e643:4bff:fe44:1"), + mark: 11, + expectedCalls: func(mockIPTables *iptablestest.MockInterfaceMockRecorder) { + mockIPTables.InsertRule(iptables.ProtocolIPv6, iptables.NATTable, antreaPostRoutingChain, []string{ + "-m", "comment", "--comment", "Antrea: SNAT Pod to external packets", + "!", "-o", "antrea-gw0", + "-m", "mark", "--mark", fmt.Sprintf("%#08x/%#08x", 11, types.SNATIPMarkMask), + "-j", iptables.SNATTarget, "--to", "fe80::e643:4bff:fe44:1", + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIPTables := iptablestest.NewMockInterface(ctrl) + c := &Client{ipt: mockIPTables, + nodeConfig: tt.nodeConfig, + } + tt.expectedCalls(mockIPTables.EXPECT()) + assert.NoError(t, c.AddSNATRule(tt.snatIP, tt.mark)) + }) + } +} + +func TestDeleteSNATRule(t *testing.T) { + tests := []struct { + name string + networkConfig *config.NetworkConfig + markToSNATIP map[uint32]net.IP + nodeConfig *config.NodeConfig + mark uint32 + expectedCalls func(mockIPTables *iptablestest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + }, + }, + markToSNATIP: map[uint32]net.IP{ + 10: net.ParseIP("1.1.1.1"), + 11: net.ParseIP("1.1.1.2"), + }, + mark: 10, + expectedCalls: func(mockIPTables *iptablestest.MockInterfaceMockRecorder) { + mockIPTables.DeleteRule(iptables.ProtocolIPv4, iptables.NATTable, antreaPostRoutingChain, []string{ + "-m", "comment", "--comment", "Antrea: SNAT Pod to external packets", + "!", "-o", "antrea-gw0", + "-m", "mark", "--mark", fmt.Sprintf("%#08x/%#08x", 10, types.SNATIPMarkMask), + "-j", iptables.SNATTarget, "--to", "1.1.1.1", + }) + }, + }, + { + name: "IPv6", + nodeConfig: &config.NodeConfig{ + GatewayConfig: &config.GatewayConfig{ + Name: "antrea-gw0", + }, + }, + markToSNATIP: map[uint32]net.IP{ + 10: net.ParseIP("fe80::e643:4bff:fe44:1"), + 11: net.ParseIP("fe80::e643:4bff:fe44:2"), + }, + mark: 11, + expectedCalls: func(mockIPTables *iptablestest.MockInterfaceMockRecorder) { + mockIPTables.DeleteRule(iptables.ProtocolIPv6, iptables.NATTable, antreaPostRoutingChain, []string{ + "-m", "comment", "--comment", "Antrea: SNAT Pod to external packets", + "!", "-o", "antrea-gw0", + "-m", "mark", "--mark", fmt.Sprintf("%#08x/%#08x", 11, types.SNATIPMarkMask), + "-j", iptables.SNATTarget, "--to", "fe80::e643:4bff:fe44:2", + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIPTables := iptablestest.NewMockInterface(ctrl) + c := &Client{ + ipt: mockIPTables, + nodeConfig: tt.nodeConfig, + markToSNATIP: sync.Map{}, + } + for mark, snatIP := range tt.markToSNATIP { + c.markToSNATIP.Store(mark, snatIP) + } + tt.expectedCalls(mockIPTables.EXPECT()) + assert.NoError(t, c.DeleteSNATRule(tt.mark)) + }) + } +} + +func TestAddNodePort(t *testing.T) { + tests := []struct { + name string + nodePortAddresses []net.IP + port uint16 + protocol openflow.Protocol + expectedCalls func(ipset *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "ipv4 tcp", + nodePortAddresses: []net.IP{ + net.ParseIP("1.1.1.1"), + net.ParseIP("1.1.2.2"), + }, + port: 30000, + protocol: openflow.ProtocolTCP, + expectedCalls: func(ipset *ipsettest.MockInterfaceMockRecorder) { + ipset.AddEntry(antreaNodePortIPSet, "1.1.1.1,tcp:30000") + ipset.AddEntry(antreaNodePortIPSet, "1.1.2.2,tcp:30000") + }, + }, + { + name: "ipv6 udp", + nodePortAddresses: []net.IP{ + net.ParseIP("fd00:1234:5678:dead:beaf::1"), + net.ParseIP("fd00:1234:5678:dead:beaf::2"), + }, + port: 30001, + protocol: openflow.ProtocolUDPv6, + expectedCalls: func(ipset *ipsettest.MockInterfaceMockRecorder) { + ipset.AddEntry(antreaNodePortIP6Set, "fd00:1234:5678:dead:beaf::1,udp:30001") + ipset.AddEntry(antreaNodePortIP6Set, "fd00:1234:5678:dead:beaf::2,udp:30001") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + ipset := ipsettest.NewMockInterface(ctrl) + c := &Client{ipset: ipset} + tt.expectedCalls(ipset.EXPECT()) + assert.NoError(t, c.AddNodePort(tt.nodePortAddresses, tt.port, tt.protocol)) + }) + } +} + +func TestDeleteNodePort(t *testing.T) { + tests := []struct { + name string + nodePortAddresses []net.IP + port uint16 + protocol openflow.Protocol + expectedCalls func(ipset *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "ipv4 tcp", + nodePortAddresses: []net.IP{ + net.ParseIP("1.1.1.1"), + net.ParseIP("1.1.2.2"), + }, + port: 30000, + protocol: openflow.ProtocolTCP, + expectedCalls: func(ipset *ipsettest.MockInterfaceMockRecorder) { + ipset.DelEntry(antreaNodePortIPSet, "1.1.1.1,tcp:30000") + ipset.DelEntry(antreaNodePortIPSet, "1.1.2.2,tcp:30000") + }, + }, + { + name: "ipv6 udp", + nodePortAddresses: []net.IP{ + net.ParseIP("fd00:1234:5678:dead:beaf::1"), + net.ParseIP("fd00:1234:5678:dead:beaf::2"), + }, + port: 30001, + protocol: openflow.ProtocolUDPv6, + expectedCalls: func(ipset *ipsettest.MockInterfaceMockRecorder) { + ipset.DelEntry(antreaNodePortIP6Set, "fd00:1234:5678:dead:beaf::1,udp:30001") + ipset.DelEntry(antreaNodePortIP6Set, "fd00:1234:5678:dead:beaf::2,udp:30001") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + ipset := ipsettest.NewMockInterface(ctrl) + c := &Client{ipset: ipset} + tt.expectedCalls(ipset.EXPECT()) + assert.NoError(t, c.DeleteNodePort(tt.nodePortAddresses, tt.port, tt.protocol)) + }) + } +} + +func TestAddClusterIPRoute(t *testing.T) { + nodeConfig := &config.NodeConfig{GatewayConfig: &config.GatewayConfig{LinkIndex: 10}} + tests := []struct { + name string + clusterIPs []string + expectedCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + clusterIPs: []string{"10.96.0.1", "10.96.0.10"}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("10.96.0.1"), Mask: net.CIDRMask(32, 32)}, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteListFiltered(netlink.FAMILY_V4, &netlink.Route{LinkIndex: 10}, netlink.RT_FILTER_OIF).Return([]netlink.Route{ + {Dst: getIPNet("10.96.0.0/24"), Gw: config.VirtualServiceIPv4}, + }, nil) + mockNetlink.RouteListFiltered(netlink.FAMILY_V6, &netlink.Route{LinkIndex: 10}, netlink.RT_FILTER_OIF).Return([]netlink.Route{}, nil) + mockNetlink.RouteDel(&netlink.Route{ + Dst: getIPNet("10.96.0.0/24"), Gw: config.VirtualServiceIPv4, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("10.96.0.0").To4(), Mask: net.CIDRMask(28, 32)}, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("10.96.0.1"), Mask: net.CIDRMask(32, 32)}, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + { + name: "IPv6", + clusterIPs: []string{"fd00:1234:5678:dead:beaf::1", "fd00:1234:5678:dead:beaf::a"}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::1"), Mask: net.CIDRMask(128, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteListFiltered(netlink.FAMILY_V4, &netlink.Route{LinkIndex: 10}, netlink.RT_FILTER_OIF).Return([]netlink.Route{}, nil) + mockNetlink.RouteListFiltered(netlink.FAMILY_V6, &netlink.Route{LinkIndex: 10}, netlink.RT_FILTER_OIF).Return([]netlink.Route{ + {Dst: getIPNet("fd00:1234:5678:dead:beaf::/80"), Gw: config.VirtualServiceIPv6}, + }, nil) + mockNetlink.RouteDel(&netlink.Route{ + Dst: getIPNet("fd00:1234:5678:dead:beaf::/80"), Gw: config.VirtualServiceIPv6, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::"), Mask: net.CIDRMask(124, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::1"), Mask: net.CIDRMask(128, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + c := &Client{ + netlink: mockNetlink, + nodeConfig: nodeConfig, + } + tt.expectedCalls(mockNetlink.EXPECT()) + + for _, clusterIP := range tt.clusterIPs { + assert.NoError(t, c.AddClusterIPRoute(net.ParseIP(clusterIP))) + } + }) + } +} + +func TestAddLoadBalancer(t *testing.T) { + nodeConfig := &config.NodeConfig{GatewayConfig: &config.GatewayConfig{LinkIndex: 10}} + tests := []struct { + name string + externalIPs []string + expectedCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + externalIPs: []string{"1.1.1.1", "1.1.1.2"}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{ + IP: net.ParseIP("1.1.1.1"), + Mask: net.CIDRMask(32, 32), + }, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{ + IP: net.ParseIP("1.1.1.2"), + Mask: net.CIDRMask(32, 32), + }, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + { + name: "IPv6", + externalIPs: []string{"fd00:1234:5678:dead:beaf::1", "fd00:1234:5678:dead:beaf::a"}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::1"), Mask: net.CIDRMask(128, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteReplace(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::a"), Mask: net.CIDRMask(128, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + c := &Client{ + netlink: mockNetlink, + nodeConfig: nodeConfig, + } + tt.expectedCalls(mockNetlink.EXPECT()) + + assert.NoError(t, c.AddLoadBalancer(tt.externalIPs)) + }) + } +} + +func TestDeleteLoadBalancer(t *testing.T) { + nodeConfig := &config.NodeConfig{GatewayConfig: &config.GatewayConfig{LinkIndex: 10}} + tests := []struct { + name string + externalIPs []string + expectedCalls func(mockNetlink *netlinktest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + externalIPs: []string{"1.1.1.1", "1.1.1.2"}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{ + IP: net.ParseIP("1.1.1.1"), + Mask: net.CIDRMask(32, 32), + }, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{ + IP: net.ParseIP("1.1.1.2"), + Mask: net.CIDRMask(32, 32), + }, + Gw: config.VirtualServiceIPv4, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + { + name: "IPv6", + externalIPs: []string{"fd00:1234:5678:dead:beaf::1", "fd00:1234:5678:dead:beaf::a"}, + expectedCalls: func(mockNetlink *netlinktest.MockInterfaceMockRecorder) { + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::1"), Mask: net.CIDRMask(128, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + mockNetlink.RouteDel(&netlink.Route{ + Dst: &net.IPNet{IP: net.ParseIP("fd00:1234:5678:dead:beaf::a"), Mask: net.CIDRMask(128, 128)}, + Gw: config.VirtualServiceIPv6, + Scope: netlink.SCOPE_UNIVERSE, + LinkIndex: 10, + }) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockNetlink := netlinktest.NewMockInterface(ctrl) + c := &Client{ + netlink: mockNetlink, + nodeConfig: nodeConfig, + } + tt.expectedCalls(mockNetlink.EXPECT()) + + assert.NoError(t, c.DeleteLoadBalancer(tt.externalIPs)) + }) + } +} + +func TestAddLocalAntreaFlexibleIPAMPodRule(t *testing.T) { + tests := []struct { + name string + nodeConfig *config.NodeConfig + connectUplinkToBridge bool + podAddresses []net.IP + expectedCalls func(mockIPSet *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "connectUplinkToBridge=false", + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: getIPNet("1.1.1.0/24"), + PodIPv6CIDR: getIPNet("aabb::/64"), + }, + connectUplinkToBridge: false, + podAddresses: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("aabb::1")}, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) {}, + }, + { + name: "connectUplinkToBridge=true,nodeIPAMPod", + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: getIPNet("1.1.1.0/24"), + PodIPv6CIDR: getIPNet("aabb::/64"), + }, + connectUplinkToBridge: false, + podAddresses: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("aabb::1")}, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) {}, + }, + { + name: "connectUplinkToBridge=true,antreaIPAMPod", + nodeConfig: &config.NodeConfig{ + PodIPv4CIDR: getIPNet("1.1.1.0/24"), + PodIPv6CIDR: getIPNet("aabb::/64"), + }, + connectUplinkToBridge: true, + podAddresses: []net.IP{net.ParseIP("1.1.2.1"), net.ParseIP("aabc::1")}, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(localAntreaFlexibleIPAMPodIPSet, "1.1.2.1") + mockIPSet.AddEntry(localAntreaFlexibleIPAMPodIP6Set, "aabc::1") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIPSet := ipsettest.NewMockInterface(ctrl) + c := &Client{ + ipset: mockIPSet, + nodeConfig: tt.nodeConfig, + connectUplinkToBridge: tt.connectUplinkToBridge, + } + tt.expectedCalls(mockIPSet.EXPECT()) + + assert.NoError(t, c.AddLocalAntreaFlexibleIPAMPodRule(tt.podAddresses)) + }) + } +} + +func TestDeleteLocalAntreaFlexibleIPAMPodRule(t *testing.T) { + nodeConfig := &config.NodeConfig{GatewayConfig: &config.GatewayConfig{LinkIndex: 10}} + tests := []struct { + name string + connectUplinkToBridge bool + podAddresses []net.IP + expectedCalls func(mockIPSet *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "connectUplinkToBridge=false", + connectUplinkToBridge: false, + podAddresses: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("aabb::1")}, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) {}, + }, + { + name: "connectUplinkToBridge=true", + connectUplinkToBridge: true, + podAddresses: []net.IP{net.ParseIP("1.1.1.1"), net.ParseIP("aabb::1")}, + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.DelEntry(localAntreaFlexibleIPAMPodIPSet, "1.1.1.1") + mockIPSet.DelEntry(localAntreaFlexibleIPAMPodIP6Set, "aabb::1") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIPSet := ipsettest.NewMockInterface(ctrl) + c := &Client{ + ipset: mockIPSet, + nodeConfig: nodeConfig, + connectUplinkToBridge: tt.connectUplinkToBridge, + } + tt.expectedCalls(mockIPSet.EXPECT()) + + assert.NoError(t, c.DeleteLocalAntreaFlexibleIPAMPodRule(tt.podAddresses)) + }) + } +} + +func TestAddAndDeleteNodeIP(t *testing.T) { + tests := []struct { + name string + multicastEnabled bool + networkConfig *config.NetworkConfig + podCIDR *net.IPNet + nodeIP net.IP + expectedCalls func(mockIPSet *ipsettest.MockInterfaceMockRecorder) + }{ + { + name: "IPv4", + multicastEnabled: true, + networkConfig: &config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, + podCIDR: getIPNet("192.168.0.0/24"), + nodeIP: net.ParseIP("1.1.1.1"), + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(clusterNodeIPSet, "1.1.1.1") + mockIPSet.DelEntry(clusterNodeIPSet, "1.1.1.1") + }, + }, + { + name: "IPv6", + multicastEnabled: true, + networkConfig: &config.NetworkConfig{TrafficEncapMode: config.TrafficEncapModeEncap}, + podCIDR: getIPNet("1122:3344::/80"), + nodeIP: net.ParseIP("aabb:ccdd::1"), + expectedCalls: func(mockIPSet *ipsettest.MockInterfaceMockRecorder) { + mockIPSet.AddEntry(clusterNodeIP6Set, "aabb:ccdd::1") + mockIPSet.DelEntry(clusterNodeIP6Set, "aabb:ccdd::1") + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + mockIPSet := ipsettest.NewMockInterface(ctrl) + c := &Client{ + ipset: mockIPSet, + networkConfig: tt.networkConfig, + multicastEnabled: tt.multicastEnabled, + } + tt.expectedCalls(mockIPSet.EXPECT()) + + ipv6 := tt.nodeIP.To4() == nil + assert.NoError(t, c.addNodeIP(tt.podCIDR, tt.nodeIP)) + var exists bool + if ipv6 { + _, exists = c.clusterNodeIP6s.Load(tt.podCIDR.String()) + } else { + _, exists = c.clusterNodeIPs.Load(tt.podCIDR.String()) + } + assert.True(t, exists) + + assert.NoError(t, c.deleteNodeIP(tt.podCIDR)) + if ipv6 { + _, exists = c.clusterNodeIP6s.Load(tt.podCIDR.String()) + } else { + _, exists = c.clusterNodeIPs.Load(tt.podCIDR.String()) + } + assert.False(t, exists) + }) + } +} diff --git a/pkg/agent/util/ipset/ipset.go b/pkg/agent/util/ipset/ipset.go index 79b41a27108..7882bf85e6f 100644 --- a/pkg/agent/util/ipset/ipset.go +++ b/pkg/agent/util/ipset/ipset.go @@ -34,8 +34,26 @@ const ( // memberPattern is used to match the members part of ipset list result. var memberPattern = regexp.MustCompile("(?m)^(.*\n)*Members:\n") +type Interface interface { + CreateIPSet(name string, setType SetType, isIPv6 bool) error + + AddEntry(name string, entry string) error + + DelEntry(name string, entry string) error + + ListEntries(name string) ([]string, error) +} + +type Client struct{} + +var _ Interface = &Client{} + +func NewClient() *Client { + return &Client{} +} + // CreateIPSet creates a new set, it will ignore error when the set already exists. -func CreateIPSet(name string, setType SetType, isIPv6 bool) error { +func (c *Client) CreateIPSet(name string, setType SetType, isIPv6 bool) error { var cmd *exec.Cmd if isIPv6 { // #nosec G204 -- inputs are not controlled by users @@ -51,7 +69,7 @@ func CreateIPSet(name string, setType SetType, isIPv6 bool) error { } // AddEntry adds a new entry to the set, it will ignore error when the entry already exists. -func AddEntry(name string, entry string) error { +func (c *Client) AddEntry(name string, entry string) error { cmd := exec.Command("ipset", "add", name, entry, "-exist") if err := cmd.Run(); err != nil { return fmt.Errorf("error adding entry %s to ipset %s: %v", entry, name, err) @@ -60,7 +78,7 @@ func AddEntry(name string, entry string) error { } // DelEntry deletes the entry from the set, it will ignore error when the entry doesn't exist. -func DelEntry(name string, entry string) error { +func (c *Client) DelEntry(name string, entry string) error { cmd := exec.Command("ipset", "del", name, entry, "-exist") if err := cmd.Run(); err != nil { return fmt.Errorf("error deleting entry %s from ipset %s: %v", entry, name, err) @@ -69,7 +87,7 @@ func DelEntry(name string, entry string) error { } // ListEntries lists all the entries of the set. -func ListEntries(name string) ([]string, error) { +func (c *Client) ListEntries(name string) ([]string, error) { cmd := exec.Command("ipset", "list", name) output, err := cmd.CombinedOutput() if err != nil { diff --git a/pkg/agent/util/ipset/testing/mock_ipset.go b/pkg/agent/util/ipset/testing/mock_ipset.go new file mode 100644 index 00000000000..c4cc4c5b55c --- /dev/null +++ b/pkg/agent/util/ipset/testing/mock_ipset.go @@ -0,0 +1,106 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: antrea.io/antrea/pkg/agent/util/ipset (interfaces: Interface) + +// Package testing is a generated GoMock package. +package testing + +import ( + ipset "antrea.io/antrea/pkg/agent/util/ipset" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockInterface is a mock of Interface interface +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// AddEntry mocks base method +func (m *MockInterface) AddEntry(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddEntry", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddEntry indicates an expected call of AddEntry +func (mr *MockInterfaceMockRecorder) AddEntry(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddEntry", reflect.TypeOf((*MockInterface)(nil).AddEntry), arg0, arg1) +} + +// CreateIPSet mocks base method +func (m *MockInterface) CreateIPSet(arg0 string, arg1 ipset.SetType, arg2 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateIPSet", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateIPSet indicates an expected call of CreateIPSet +func (mr *MockInterfaceMockRecorder) CreateIPSet(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateIPSet", reflect.TypeOf((*MockInterface)(nil).CreateIPSet), arg0, arg1, arg2) +} + +// DelEntry mocks base method +func (m *MockInterface) DelEntry(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DelEntry", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// DelEntry indicates an expected call of DelEntry +func (mr *MockInterfaceMockRecorder) DelEntry(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DelEntry", reflect.TypeOf((*MockInterface)(nil).DelEntry), arg0, arg1) +} + +// ListEntries mocks base method +func (m *MockInterface) ListEntries(arg0 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListEntries", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListEntries indicates an expected call of ListEntries +func (mr *MockInterfaceMockRecorder) ListEntries(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListEntries", reflect.TypeOf((*MockInterface)(nil).ListEntries), arg0) +} diff --git a/pkg/agent/util/iptables/iptables.go b/pkg/agent/util/iptables/iptables.go index 780c8b5fa4c..9514c16008d 100644 --- a/pkg/agent/util/iptables/iptables.go +++ b/pkg/agent/util/iptables/iptables.go @@ -75,6 +75,26 @@ const ( // iptables-restore: support acquiring the lock. var restoreWaitSupportedMinVersion = semver.Version{Major: 1, Minor: 6, Patch: 2} +type Interface interface { + EnsureChain(protocol Protocol, table string, chain string) error + + ChainExists(protocol Protocol, table string, chain string) (bool, error) + + AppendRule(protocol Protocol, table string, chain string, ruleSpec []string) error + + InsertRule(protocol Protocol, table string, chain string, ruleSpec []string) error + + DeleteRule(protocol Protocol, table string, chain string, ruleSpec []string) error + + DeleteChain(protocol Protocol, table string, chain string) error + + ListRules(table string, chain string) ([]string, error) + + Restore(data string, flush bool, useIPv6 bool) error + + Save() ([]byte, error) +} + type Client struct { ipts map[Protocol]*iptables.IPTables // restoreWaitSupported indicates whether iptables-restore (or ip6tables-restore) supports --wait flag. @@ -271,7 +291,7 @@ func (c *Client) ListRules(table string, chain string) ([]string, error) { // Restore calls iptable-restore to restore iptables with the provided content. // If flush is true, all previous contents of the respective tables will be flushed. // Otherwise only involved chains will be flushed. Restore supports "ip6tables-restore" for IPv6. -func (c *Client) Restore(data []byte, flush bool, useIPv6 bool) error { +func (c *Client) Restore(data string, flush bool, useIPv6 bool) error { var args []string if !flush { args = append(args, "--noflush") @@ -281,7 +301,7 @@ func (c *Client) Restore(data []byte, flush bool, useIPv6 bool) error { iptablesCmd = "ip6tables-restore" } cmd := exec.Command(iptablesCmd, args...) - cmd.Stdin = bytes.NewBuffer(data) + cmd.Stdin = bytes.NewBuffer([]byte(data)) stderr := &bytes.Buffer{} cmd.Stderr = stderr // We acquire xtables lock for iptables-restore to prevent it from conflicting diff --git a/pkg/agent/util/iptables/testing/mock_iptables_linux.go b/pkg/agent/util/iptables/testing/mock_iptables_linux.go new file mode 100644 index 00000000000..732666c23ec --- /dev/null +++ b/pkg/agent/util/iptables/testing/mock_iptables_linux.go @@ -0,0 +1,178 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: antrea.io/antrea/pkg/agent/util/iptables (interfaces: Interface) + +// Package testing is a generated GoMock package. +package testing + +import ( + iptables "antrea.io/antrea/pkg/agent/util/iptables" + gomock "github.com/golang/mock/gomock" + reflect "reflect" +) + +// MockInterface is a mock of Interface interface +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// AppendRule mocks base method +func (m *MockInterface) AppendRule(arg0 iptables.Protocol, arg1, arg2 string, arg3 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AppendRule", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// AppendRule indicates an expected call of AppendRule +func (mr *MockInterfaceMockRecorder) AppendRule(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AppendRule", reflect.TypeOf((*MockInterface)(nil).AppendRule), arg0, arg1, arg2, arg3) +} + +// ChainExists mocks base method +func (m *MockInterface) ChainExists(arg0 iptables.Protocol, arg1, arg2 string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainExists", arg0, arg1, arg2) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainExists indicates an expected call of ChainExists +func (mr *MockInterfaceMockRecorder) ChainExists(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainExists", reflect.TypeOf((*MockInterface)(nil).ChainExists), arg0, arg1, arg2) +} + +// DeleteChain mocks base method +func (m *MockInterface) DeleteChain(arg0 iptables.Protocol, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteChain", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteChain indicates an expected call of DeleteChain +func (mr *MockInterfaceMockRecorder) DeleteChain(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteChain", reflect.TypeOf((*MockInterface)(nil).DeleteChain), arg0, arg1, arg2) +} + +// DeleteRule mocks base method +func (m *MockInterface) DeleteRule(arg0 iptables.Protocol, arg1, arg2 string, arg3 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteRule", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteRule indicates an expected call of DeleteRule +func (mr *MockInterfaceMockRecorder) DeleteRule(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteRule", reflect.TypeOf((*MockInterface)(nil).DeleteRule), arg0, arg1, arg2, arg3) +} + +// EnsureChain mocks base method +func (m *MockInterface) EnsureChain(arg0 iptables.Protocol, arg1, arg2 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnsureChain", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnsureChain indicates an expected call of EnsureChain +func (mr *MockInterfaceMockRecorder) EnsureChain(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnsureChain", reflect.TypeOf((*MockInterface)(nil).EnsureChain), arg0, arg1, arg2) +} + +// InsertRule mocks base method +func (m *MockInterface) InsertRule(arg0 iptables.Protocol, arg1, arg2 string, arg3 []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "InsertRule", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(error) + return ret0 +} + +// InsertRule indicates an expected call of InsertRule +func (mr *MockInterfaceMockRecorder) InsertRule(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertRule", reflect.TypeOf((*MockInterface)(nil).InsertRule), arg0, arg1, arg2, arg3) +} + +// ListRules mocks base method +func (m *MockInterface) ListRules(arg0, arg1 string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListRules", arg0, arg1) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListRules indicates an expected call of ListRules +func (mr *MockInterfaceMockRecorder) ListRules(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListRules", reflect.TypeOf((*MockInterface)(nil).ListRules), arg0, arg1) +} + +// Restore mocks base method +func (m *MockInterface) Restore(arg0 string, arg1, arg2 bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Restore", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Restore indicates an expected call of Restore +func (mr *MockInterfaceMockRecorder) Restore(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Restore", reflect.TypeOf((*MockInterface)(nil).Restore), arg0, arg1, arg2) +} + +// Save mocks base method +func (m *MockInterface) Save() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Save") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Save indicates an expected call of Save +func (mr *MockInterfaceMockRecorder) Save() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Save", reflect.TypeOf((*MockInterface)(nil).Save)) +} diff --git a/pkg/agent/util/net_linux.go b/pkg/agent/util/net_linux.go index 94dc5ebe988..3a9a14111a8 100644 --- a/pkg/agent/util/net_linux.go +++ b/pkg/agent/util/net_linux.go @@ -33,16 +33,6 @@ import ( "k8s.io/klog/v2" ) -// GetNetLink returns dev link from name. -func GetNetLink(dev string) netlink.Link { - link, err := netlink.LinkByName(dev) - if err != nil { - klog.Errorf("Failed to find dev %s: %v", dev, err) - return nil - } - return link -} - // GetNSPeerDevBridge returns peer device and its attached bridge (if applicable) // for device dev in network space indicated by nsPath func GetNSPeerDevBridge(nsPath, dev string) (*net.Interface, string, error) { diff --git a/pkg/agent/util/netlink/netlink_linux.go b/pkg/agent/util/netlink/netlink_linux.go new file mode 100644 index 00000000000..7440c407f35 --- /dev/null +++ b/pkg/agent/util/netlink/netlink_linux.go @@ -0,0 +1,95 @@ +//go:build !windows +// +build !windows + +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package netlink + +import "github.com/vishvananda/netlink" + +// Interface is created to allow testing. +type Interface interface { + RouteReplace(route *netlink.Route) error + + RouteList(link netlink.Link, family int) ([]netlink.Route, error) + + RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) + + RouteDel(route *netlink.Route) error + + AddrList(link netlink.Link, family int) ([]netlink.Addr, error) + + AddrReplace(link netlink.Link, addr *netlink.Addr) error + + AddrDel(link netlink.Link, addr *netlink.Addr) error + + NeighList(linkIndex, family int) ([]netlink.Neigh, error) + + NeighSet(neigh *netlink.Neigh) error + + NeighDel(neigh *netlink.Neigh) error + + LinkByName(name string) (netlink.Link, error) +} + +type Client struct{} + +func NewClient() *Client { + return &Client{} +} + +func (c *Client) RouteReplace(route *netlink.Route) error { + return netlink.RouteReplace(route) +} + +func (c *Client) RouteList(link netlink.Link, family int) ([]netlink.Route, error) { + return netlink.RouteList(link, family) +} + +func (c *Client) RouteListFiltered(family int, filter *netlink.Route, filterMask uint64) ([]netlink.Route, error) { + return netlink.RouteListFiltered(family, filter, filterMask) +} + +func (c *Client) RouteDel(route *netlink.Route) error { + return netlink.RouteDel(route) +} + +func (c *Client) AddrList(link netlink.Link, family int) ([]netlink.Addr, error) { + return netlink.AddrList(link, family) +} + +func (c *Client) AddrReplace(link netlink.Link, addr *netlink.Addr) error { + return netlink.AddrReplace(link, addr) +} + +func (c *Client) AddrDel(link netlink.Link, addr *netlink.Addr) error { + return netlink.AddrDel(link, addr) +} + +func (c *Client) NeighList(linkIndex, family int) ([]netlink.Neigh, error) { + return netlink.NeighList(linkIndex, family) +} + +func (c *Client) NeighSet(neigh *netlink.Neigh) error { + return netlink.NeighSet(neigh) +} + +func (c *Client) NeighDel(neigh *netlink.Neigh) error { + return netlink.NeighDel(neigh) +} + +func (c *Client) LinkByName(name string) (netlink.Link, error) { + return netlink.LinkByName(name) +} diff --git a/pkg/agent/util/netlink/testing/mock_netlink_linux.go b/pkg/agent/util/netlink/testing/mock_netlink_linux.go new file mode 100644 index 00000000000..51ffbed8ac8 --- /dev/null +++ b/pkg/agent/util/netlink/testing/mock_netlink_linux.go @@ -0,0 +1,208 @@ +// Copyright 2022 Antrea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Code generated by MockGen. DO NOT EDIT. +// Source: antrea.io/antrea/pkg/agent/util/netlink (interfaces: Interface) + +// Package testing is a generated GoMock package. +package testing + +import ( + gomock "github.com/golang/mock/gomock" + netlink "github.com/vishvananda/netlink" + reflect "reflect" +) + +// MockInterface is a mock of Interface interface +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// AddrDel mocks base method +func (m *MockInterface) AddrDel(arg0 netlink.Link, arg1 *netlink.Addr) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddrDel", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddrDel indicates an expected call of AddrDel +func (mr *MockInterfaceMockRecorder) AddrDel(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrDel", reflect.TypeOf((*MockInterface)(nil).AddrDel), arg0, arg1) +} + +// AddrList mocks base method +func (m *MockInterface) AddrList(arg0 netlink.Link, arg1 int) ([]netlink.Addr, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddrList", arg0, arg1) + ret0, _ := ret[0].([]netlink.Addr) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// AddrList indicates an expected call of AddrList +func (mr *MockInterfaceMockRecorder) AddrList(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrList", reflect.TypeOf((*MockInterface)(nil).AddrList), arg0, arg1) +} + +// AddrReplace mocks base method +func (m *MockInterface) AddrReplace(arg0 netlink.Link, arg1 *netlink.Addr) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddrReplace", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddrReplace indicates an expected call of AddrReplace +func (mr *MockInterfaceMockRecorder) AddrReplace(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddrReplace", reflect.TypeOf((*MockInterface)(nil).AddrReplace), arg0, arg1) +} + +// LinkByName mocks base method +func (m *MockInterface) LinkByName(arg0 string) (netlink.Link, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LinkByName", arg0) + ret0, _ := ret[0].(netlink.Link) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LinkByName indicates an expected call of LinkByName +func (mr *MockInterfaceMockRecorder) LinkByName(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LinkByName", reflect.TypeOf((*MockInterface)(nil).LinkByName), arg0) +} + +// NeighDel mocks base method +func (m *MockInterface) NeighDel(arg0 *netlink.Neigh) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NeighDel", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// NeighDel indicates an expected call of NeighDel +func (mr *MockInterfaceMockRecorder) NeighDel(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NeighDel", reflect.TypeOf((*MockInterface)(nil).NeighDel), arg0) +} + +// NeighList mocks base method +func (m *MockInterface) NeighList(arg0, arg1 int) ([]netlink.Neigh, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NeighList", arg0, arg1) + ret0, _ := ret[0].([]netlink.Neigh) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NeighList indicates an expected call of NeighList +func (mr *MockInterfaceMockRecorder) NeighList(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NeighList", reflect.TypeOf((*MockInterface)(nil).NeighList), arg0, arg1) +} + +// NeighSet mocks base method +func (m *MockInterface) NeighSet(arg0 *netlink.Neigh) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NeighSet", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// NeighSet indicates an expected call of NeighSet +func (mr *MockInterfaceMockRecorder) NeighSet(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NeighSet", reflect.TypeOf((*MockInterface)(nil).NeighSet), arg0) +} + +// RouteDel mocks base method +func (m *MockInterface) RouteDel(arg0 *netlink.Route) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RouteDel", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RouteDel indicates an expected call of RouteDel +func (mr *MockInterfaceMockRecorder) RouteDel(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteDel", reflect.TypeOf((*MockInterface)(nil).RouteDel), arg0) +} + +// RouteList mocks base method +func (m *MockInterface) RouteList(arg0 netlink.Link, arg1 int) ([]netlink.Route, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RouteList", arg0, arg1) + ret0, _ := ret[0].([]netlink.Route) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RouteList indicates an expected call of RouteList +func (mr *MockInterfaceMockRecorder) RouteList(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteList", reflect.TypeOf((*MockInterface)(nil).RouteList), arg0, arg1) +} + +// RouteListFiltered mocks base method +func (m *MockInterface) RouteListFiltered(arg0 int, arg1 *netlink.Route, arg2 uint64) ([]netlink.Route, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RouteListFiltered", arg0, arg1, arg2) + ret0, _ := ret[0].([]netlink.Route) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// RouteListFiltered indicates an expected call of RouteListFiltered +func (mr *MockInterfaceMockRecorder) RouteListFiltered(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteListFiltered", reflect.TypeOf((*MockInterface)(nil).RouteListFiltered), arg0, arg1, arg2) +} + +// RouteReplace mocks base method +func (m *MockInterface) RouteReplace(arg0 *netlink.Route) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RouteReplace", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RouteReplace indicates an expected call of RouteReplace +func (mr *MockInterfaceMockRecorder) RouteReplace(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RouteReplace", reflect.TypeOf((*MockInterface)(nil).RouteReplace), arg0) +} diff --git a/test/integration/agent/route_test.go b/test/integration/agent/route_test.go index d74f23d60aa..66d75911334 100644 --- a/test/integration/agent/route_test.go +++ b/test/integration/agent/route_test.go @@ -187,6 +187,7 @@ func TestInitialize(t *testing.T) { case <-inited2: } + ipset := ipset.NewClient() // verify ipset err = exec.Command("ipset", "list", "ANTREA-POD-IP").Run() assert.NoError(t, err, "ipset not exist") @@ -382,6 +383,8 @@ func TestAddAndDeleteRoutes(t *testing.T) { } assert.Equal(t, expRouteStr, ipRoute, "route mismatch") + ipset := ipset.NewClient() + entries, err := ipset.ListEntries("ANTREA-POD-IP") assert.NoError(t, err, "list ipset entries failed") assert.Contains(t, entries, tc.peerCIDR, "entry should be in ipset") @@ -586,6 +589,7 @@ func TestReconcile(t *testing.T) { assert.Equal(t, fmt.Sprint(expNum), output, "mismatch number of routes to %s", dst) } + ipset := ipset.NewClient() entries, err := ipset.ListEntries("ANTREA-POD-IP") assert.NoError(t, err, "list ipset entries failed") assert.ElementsMatch(t, entries, tc.desiredPeerCIDRs, "mismatch ipset entries")