diff --git a/pkg/networkutils/network.go b/pkg/networkutils/network.go index c7a7f0a110..c6a2ee7629 100644 --- a/pkg/networkutils/network.go +++ b/pkg/networkutils/network.go @@ -341,6 +341,8 @@ func (n *linuxNetwork) SetupHostNetwork(vpcv4CIDRs []string, primaryMAC string, // In strict mode, packets egressing pod veth interfaces must route via the trunk ENI in order for security group // rules to be applied. Therefore, the rule to lookup the local routing table is moved to a lower priority than VLAN rules. + // A high priority rule is added to handle to/from link-local traffic by the local routing table + // A high priority rule is added for ICMPv6 packets from the gateway to lookup in the local routing table if enablePodENI && n.podSGEnforcingMode == sgpp.EnforcingModeStrict { localRule := n.netLink.NewRule() localRule.Table = localRouteTable @@ -358,8 +360,18 @@ func (n *linuxNetwork) SetupHostNetwork(vpcv4CIDRs []string, primaryMAC string, return errors.Wrap(err, "ChangeLocalRulePriority: failed to delete priority 0 local rule") } - // In IPv6 strict mode, ICMPv6 packets from the gateway must lookup in the local routing table so that branch interfaces can resolve their gateway. + // Add a high priority rule, to handle all link-local traffic by local table. + if err := n.createLinkLocalIPv4Rule(); err != nil { + return errors.Wrap(err, "failed to install rule to ignore link-local packets in V4 for SGPP Strict Mode.") + } + if v6Enabled { + // Add a high priority rule, to handle all link-local traffic by local table for V6 packets. + if err := n.createLinkLocalIPv6Rule(); err != nil { + return errors.Wrap(err, "failed to install rule to ignore link-local packets in V6 for SGPP Strict Mode.") + } + + // In IPv6 strict mode, ICMPv6 packets from the gateway must lookup in the local routing table so that branch interfaces can resolve their gateway. if err := n.createIPv6GatewayRule(); err != nil { return errors.Wrapf(err, "failed to install IPv6 gateway rule") } @@ -1183,6 +1195,52 @@ func (n *linuxNetwork) createIPv6GatewayRule() error { return nil } +// With SGPP Strict Mode, all IPv4 link-local traffic (169.254.0.0/16) is handled by the local routing table with the highest priority +func (n *linuxNetwork) createLinkLocalIPv4Rule() error { + linkLocalV4Rule := n.netLink.NewRule() + linkLocalV4Rule.Src = &net.IPNet{IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)} + linkLocalV4Rule.Dst = &net.IPNet{IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)} + linkLocalV4Rule.Table = localRouteTable + linkLocalV4Rule.Priority = 0 + linkLocalV4Rule.Family = unix.AF_INET + if n.podSGEnforcingMode == sgpp.EnforcingModeStrict { + err := n.netLink.RuleAdd(linkLocalV4Rule) + if err != nil && !isRuleExistsError(err) { + return errors.Wrap(err, "createLinkLocalIPv4Rule: unable to create rule to ignore link-local packets") + } + } else { + // Rule must be deleted when not in strict mode to support transitions. + err := n.netLink.RuleDel(linkLocalV4Rule) + if !netlinkwrapper.IsNotExistsError(err) { + return errors.Wrap(err, "createLinkLocalIPv4Rule: unable to delete rule to ignore link-local packets") + } + } + return nil +} + +// With SGPP Strict Mode, all IPv6 link-local traffic (fd00::10/8) is handled by the local routing table with the highest priority +func (n *linuxNetwork) createLinkLocalIPv6Rule() error { + linkLocalV6Rule := n.netLink.NewRule() + linkLocalV6Rule.Src = &net.IPNet{IP: net.ParseIP("fd00::10"), Mask: net.CIDRMask(8, 128)} + linkLocalV6Rule.Dst = &net.IPNet{IP: net.ParseIP("fd00::10"), Mask: net.CIDRMask(8, 128)} + linkLocalV6Rule.Table = localRouteTable + linkLocalV6Rule.Priority = 0 + linkLocalV6Rule.Family = unix.AF_INET6 + if n.podSGEnforcingMode == sgpp.EnforcingModeStrict { + err := n.netLink.RuleAdd(linkLocalV6Rule) + if err != nil && !isRuleExistsError(err) { + return errors.Wrap(err, "createLinkLocalIPv6Rule: unable to create rule to ignore link-local packets") + } + } else { + // Rule must be deleted when not in strict mode to support transitions. + err := n.netLink.RuleDel(linkLocalV6Rule) + if !netlinkwrapper.IsNotExistsError(err) { + return errors.Wrap(err, "createLinkLocalIPv6Rule: unable to delete rule to ignore link-local packets") + } + } + return nil +} + // Increment the given net.IP by one. Incrementing the last IP in an IP space (IPv4, IPV6) is undefined. func incrementIPAddr(ip net.IP) { for i := len(ip) - 1; i >= 0; i-- { diff --git a/pkg/networkutils/network_test.go b/pkg/networkutils/network_test.go index fd5950ab84..6d34212497 100644 --- a/pkg/networkutils/network_test.go +++ b/pkg/networkutils/network_test.go @@ -230,6 +230,72 @@ func TestUpdateIPv6GatewayRule(t *testing.T) { assert.NoError(t, err) } +func TestCreateLinkLocalIPv4Rule(t *testing.T) { + ctrl, mockNetLink, _, _, _ := setup(t) + defer ctrl.Finish() + + ln := &linuxNetwork{ + netLink: mockNetLink, + podSGEnforcingMode: sgpp.EnforcingModeStrict, + } + + linkLocalV4Rule := netlink.Rule{ + Src: &net.IPNet{IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, + Dst: &net.IPNet{IP: net.IPv4(169, 254, 0, 0), Mask: net.CIDRMask(16, 32)}, + Table: localRouteTable, + Priority: 0, + Family: unix.AF_INET, + } + + // Validate rule add in strict mode + mockNetLink.EXPECT().NewRule().Return(&linkLocalV4Rule) + mockNetLink.EXPECT().RuleAdd(&linkLocalV4Rule) + + err := ln.createLinkLocalIPv4Rule() + assert.NoError(t, err) + + // Validate rule del in non-strict mode + ln.podSGEnforcingMode = sgpp.EnforcingModeStandard + mockNetLink.EXPECT().NewRule().Return(&linkLocalV4Rule) + mockNetLink.EXPECT().RuleDel(&linkLocalV4Rule) + + err = ln.createLinkLocalIPv6Rule() + assert.NoError(t, err) +} + +func TestCreateLinkLocalIPv6Rule(t *testing.T) { + ctrl, mockNetLink, _, _, _ := setup(t) + defer ctrl.Finish() + + ln := &linuxNetwork{ + netLink: mockNetLink, + podSGEnforcingMode: sgpp.EnforcingModeStrict, + } + + linkLocalV6Rule := netlink.Rule{ + Src: &net.IPNet{IP: net.ParseIP("fd00::10"), Mask: net.CIDRMask(8, 128)}, + Dst: &net.IPNet{IP: net.ParseIP("fd00::10"), Mask: net.CIDRMask(8, 128)}, + Table: localRouteTable, + Priority: 0, + Family: unix.AF_INET6, + } + + // Validate rule add in strict mode + mockNetLink.EXPECT().NewRule().Return(&linkLocalV6Rule) + mockNetLink.EXPECT().RuleAdd(&linkLocalV6Rule) + + err := ln.createLinkLocalIPv6Rule() + assert.NoError(t, err) + + // Validate rule del in non-strict mode + ln.podSGEnforcingMode = sgpp.EnforcingModeStandard + mockNetLink.EXPECT().NewRule().Return(&linkLocalV6Rule) + mockNetLink.EXPECT().RuleDel(&linkLocalV6Rule) + + err = ln.createLinkLocalIPv6Rule() + assert.NoError(t, err) +} + func TestSetupHostNetworkNodePortDisabledAndSNATDisabled(t *testing.T) { ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) defer ctrl.Finish() @@ -1074,6 +1140,7 @@ func TestSetupHostNetworkUpdateLocalRule(t *testing.T) { } setupNetLinkMocks(ctrl, mockNetLink) setupVethNetLinkMocks(mockNetLink) + setupLinkLocalMocks(ctrl, mockNetLink) mockNetLink.EXPECT() @@ -1082,6 +1149,174 @@ func TestSetupHostNetworkUpdateLocalRule(t *testing.T) { assert.NoError(t, err) } +func TestSetupHostNetworkUpdateLinkLocalRulesIPv4(t *testing.T) { + ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) + defer ctrl.Finish() + + ln := &linuxNetwork{ + useExternalSNAT: true, + nodePortSupportEnabled: true, + mainENIMark: defaultConnmark, + mtu: testMTU, + podSGEnforcingMode: sgpp.EnforcingModeStrict, + vethPrefix: eniPrefix, + + netLink: mockNetLink, + ns: mockNS, + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { + return mockIptables, nil + }, + } + + // Expected IPV4 Link-Local Rule + expectedRule := &netlink.Rule{ + Src: &net.IPNet{ + IP: net.IPv4(169, 254, 0, 0), + Mask: net.CIDRMask(16, 32), + }, + Dst: &net.IPNet{ + IP: net.IPv4(169, 254, 0, 0), + Mask: net.CIDRMask(16, 32), + }, + Table: localRouteTable, + Priority: 0, + Family: unix.AF_INET, + } + + // Matcher to capture the rule being added + var capturedRule *netlink.Rule + + setupNetLinkMocks(ctrl, mockNetLink) + setupVethNetLinkMocks(mockNetLink) + + mockNetLink.EXPECT().NewRule().Return(expectedRule) + mockNetLink.EXPECT().RuleAdd(expectedRule).DoAndReturn(func(rule *netlink.Rule) error { + capturedRule = rule + return nil + }) + + var vpcCIDRs []string + err := ln.SetupHostNetwork(vpcCIDRs, loopback, &testEniIPNet, true, true, false) + assert.NoError(t, err) + assert.NotNil(t, capturedRule) + assert.Equal(t, net.IPv4(169, 254, 0, 0).String(), capturedRule.Src.IP.String()) + assert.Equal(t, net.IPv4(169, 254, 0, 0).String(), capturedRule.Dst.IP.String()) + assert.Equal(t, localRouteTable, capturedRule.Table) + assert.Equal(t, 0, capturedRule.Priority) + assert.Equal(t, unix.AF_INET, capturedRule.Family) +} + +func TestSetupHostNetworkUpdateLinkLocalRulesIPv6(t *testing.T) { + ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) + defer ctrl.Finish() + + ln := &linuxNetwork{ + useExternalSNAT: true, + nodePortSupportEnabled: true, + mainENIMark: defaultConnmark, + mtu: testMTU, + podSGEnforcingMode: sgpp.EnforcingModeStrict, + vethPrefix: eniPrefix, + + netLink: mockNetLink, + ns: mockNS, + newIptables: func(iptables.Protocol) (iptableswrapper.IPTablesIface, error) { + return mockIptables, nil + }, + } + + // Expected IPV4 rule + expectedRule := &netlink.Rule{ + Src: &net.IPNet{ + IP: net.IPv4(169, 254, 0, 0), + Mask: net.CIDRMask(16, 32), + }, + Dst: &net.IPNet{ + IP: net.IPv4(169, 254, 0, 0), + Mask: net.CIDRMask(16, 32), + }, + Table: localRouteTable, + Priority: 0, + Family: unix.AF_INET, + } + + var capturedRule *netlink.Rule + + setupNetLinkMocks(ctrl, mockNetLink) + setupVethNetLinkMocks(mockNetLink) + + mockNetLink.EXPECT().NewRule().Return(expectedRule) + mockNetLink.EXPECT().RuleAdd(expectedRule).DoAndReturn(func(rule *netlink.Rule) error { + capturedRule = rule + return nil + }) + + var capturedRuleV6 *netlink.Rule + // Expected IPV6 rule + expectedRuleV6 := &netlink.Rule{ + Src: &net.IPNet{IP: net.ParseIP("fd00::10"), Mask: net.CIDRMask(8, 128)}, + Dst: &net.IPNet{IP: net.ParseIP("fd00::10"), Mask: net.CIDRMask(8, 128)}, + Table: localRouteTable, + Priority: 0, + Family: unix.AF_INET6, + } + + mockNetLink.EXPECT().NewRule().Return(&netlink.Rule{}) + mockNetLink.EXPECT().RuleAdd(expectedRuleV6).DoAndReturn(func(rule *netlink.Rule) error { + capturedRuleV6 = rule + return nil + }) + + // Expected IPV6 Gateway Rule + expectedGatewayRule := &netlink.Rule{ + Src: &net.IPNet{ + IP: GetIPv6Gateway(), + Mask: net.CIDRMask(128, 128), + }, + IPProto: unix.IPPROTO_ICMPV6, + Table: localRouteTable, + Priority: 0, + Family: unix.AF_INET6, + } + + var captureRuleIPV6GateWay *netlink.Rule + + mockNetLink.EXPECT().NewRule().Return(&netlink.Rule{}) + mockNetLink.EXPECT().RuleAdd(expectedGatewayRule).DoAndReturn(func(rule *netlink.Rule) error { + captureRuleIPV6GateWay = rule + return nil + }) + + var vpcCIDRs []string + err := ln.SetupHostNetwork(vpcCIDRs, loopback, &testEniIPNet, true, false, true) + assert.NoError(t, err) + assert.NotNil(t, capturedRule) + assert.Equal(t, net.IPv4(169, 254, 0, 0).String(), capturedRule.Src.IP.String()) + assert.Equal(t, net.IPv4(169, 254, 0, 0).String(), capturedRule.Dst.IP.String()) + assert.Equal(t, localRouteTable, capturedRule.Table) + assert.Equal(t, 0, capturedRule.Priority) + assert.Equal(t, unix.AF_INET, capturedRule.Family) + + // Expected Rules for IPV6 Link Local + expectedIP := net.ParseIP("fd00::10") + assert.Equal(t, expectedIP.String(), capturedRuleV6.Src.IP.String()) + assert.Equal(t, expectedIP.String(), capturedRuleV6.Dst.IP.String()) + assert.Equal(t, net.CIDRMask(8, 128).String(), capturedRuleV6.Src.Mask.String()) + assert.Equal(t, net.CIDRMask(8, 128).String(), capturedRuleV6.Dst.Mask.String()) + assert.Equal(t, localRouteTable, capturedRuleV6.Table) + assert.Equal(t, 0, capturedRuleV6.Priority) + assert.Equal(t, unix.AF_INET6, capturedRuleV6.Family) + + // Expected Rules for IPV6 Gateway + expectedIP = GetIPv6Gateway() + assert.Equal(t, expectedIP.String(), captureRuleIPV6GateWay.Src.IP.String()) + assert.Equal(t, net.CIDRMask(128, 128).String(), captureRuleIPV6GateWay.Src.Mask.String()) + assert.Equal(t, unix.IPPROTO_ICMPV6, captureRuleIPV6GateWay.IPProto) + assert.Equal(t, localRouteTable, captureRuleIPV6GateWay.Table) + assert.Equal(t, 0, captureRuleIPV6GateWay.Priority) + assert.Equal(t, unix.AF_INET6, captureRuleIPV6GateWay.Family) +} + func TestSetupHostNetworkDeleteOldConnmarkRuleForNonVpcOutboundTraffic(t *testing.T) { ctrl, mockNetLink, _, mockNS, mockIptables := setup(t) defer ctrl.Finish() @@ -1263,6 +1498,12 @@ func setupNetLinkMocks(ctrl *gomock.Controller, mockNetLink *mock_netlinkwrapper mockNetLink.EXPECT().RuleAdd(&mainENIRule) } +func setupLinkLocalMocks(ctrl *gomock.Controller, mockNetLink *mock_netlinkwrapper.MockNetLink) { + var mainENIRule netlink.Rule + mockNetLink.EXPECT().NewRule().Return(&mainENIRule) + mockNetLink.EXPECT().RuleAdd(&mainENIRule) +} + func setupVethNetLinkMocks(mockNetLink *mock_netlinkwrapper.MockNetLink) { var localRule netlink.Rule mockNetLink.EXPECT().NewRule().Return(&localRule)