kubernetes/pkg/proxy/conntrack/filter_test.go

//go:build linux
// +build linux

/*
Copyright 2024 The Kubernetes 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 conntrack

import (
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/vishvananda/netlink"
	"golang.org/x/sys/unix"

	netutils "k8s.io/utils/net"
)

func applyFilter(flowList []netlink.ConntrackFlow, ipv4Filter *conntrackFilter, ipv6Filter *conntrackFilter) (ipv4Match, ipv6Match int) {
	for _, flow := range flowList {
		if ipv4Filter.MatchConntrackFlow(&flow) == true {
			ipv4Match++
		}
		if ipv6Filter.MatchConntrackFlow(&flow) == true {
			ipv6Match++
		}
	}
	return ipv4Match, ipv6Match
}

func TestConntrackFilter(t *testing.T) {
	var flowList []netlink.ConntrackFlow
	flow1 := netlink.ConntrackFlow{}
	flow1.FamilyType = unix.AF_INET
	flow1.Forward.SrcIP = netutils.ParseIPSloppy("10.0.0.1")
	flow1.Forward.DstIP = netutils.ParseIPSloppy("20.0.0.1")
	flow1.Forward.SrcPort = 1000
	flow1.Forward.DstPort = 2000
	flow1.Forward.Protocol = 17
	flow1.Reverse.SrcIP = netutils.ParseIPSloppy("20.0.0.1")
	flow1.Reverse.DstIP = netutils.ParseIPSloppy("192.168.1.1")
	flow1.Reverse.SrcPort = 2000
	flow1.Reverse.DstPort = 1000
	flow1.Reverse.Protocol = 17

	flow2 := netlink.ConntrackFlow{}
	flow2.FamilyType = unix.AF_INET
	flow2.Forward.SrcIP = netutils.ParseIPSloppy("10.0.0.2")
	flow2.Forward.DstIP = netutils.ParseIPSloppy("20.0.0.2")
	flow2.Forward.SrcPort = 5000
	flow2.Forward.DstPort = 6000
	flow2.Forward.Protocol = 6
	flow2.Reverse.SrcIP = netutils.ParseIPSloppy("20.0.0.2")
	flow2.Reverse.DstIP = netutils.ParseIPSloppy("192.168.1.1")
	flow2.Reverse.SrcPort = 6000
	flow2.Reverse.DstPort = 5000
	flow2.Reverse.Protocol = 6

	flow3 := netlink.ConntrackFlow{}
	flow3.FamilyType = unix.AF_INET6
	flow3.Forward.SrcIP = netutils.ParseIPSloppy("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")
	flow3.Forward.DstIP = netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")
	flow3.Forward.SrcPort = 1000
	flow3.Forward.DstPort = 2000
	flow3.Forward.Protocol = 132
	flow3.Reverse.SrcIP = netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")
	flow3.Reverse.DstIP = netutils.ParseIPSloppy("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")
	flow3.Reverse.SrcPort = 2000
	flow3.Reverse.DstPort = 1000
	flow3.Reverse.Protocol = 132
	flowList = append(flowList, flow1, flow2, flow3)

	testCases := []struct {
		name              string
		filterV4          *conntrackFilter
		filterV6          *conntrackFilter
		expectedV4Matches int
		expectedV6Matches int
	}{
		{
			name:              "Empty filter",
			filterV4:          &conntrackFilter{},
			filterV6:          &conntrackFilter{},
			expectedV4Matches: 0,
			expectedV6Matches: 0,
		},
		{
			name:              "Protocol filter",
			filterV4:          &conntrackFilter{protocol: 6},
			filterV6:          &conntrackFilter{protocol: 17},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Original Source IP filter",
			filterV4:          &conntrackFilter{original: &connectionTuple{srcIP: netutils.ParseIPSloppy("10.0.0.1")}},
			filterV6:          &conntrackFilter{original: &connectionTuple{srcIP: netutils.ParseIPSloppy("eeee:eeee:eeee:eeee:eeee:eeee:eeee:eeee")}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Original Destination IP filter",
			filterV4:          &conntrackFilter{original: &connectionTuple{dstIP: netutils.ParseIPSloppy("20.0.0.1")}},
			filterV6:          &conntrackFilter{original: &connectionTuple{dstIP: netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Original Source Port Filter",
			filterV4:          &conntrackFilter{protocol: 6, original: &connectionTuple{srcPort: 5000}},
			filterV6:          &conntrackFilter{protocol: 132, original: &connectionTuple{srcPort: 1000}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Original Destination Port Filter",
			filterV4:          &conntrackFilter{protocol: 6, original: &connectionTuple{dstPort: 6000}},
			filterV6:          &conntrackFilter{protocol: 132, original: &connectionTuple{dstPort: 2000}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Reply Source IP filter",
			filterV4:          &conntrackFilter{reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("20.0.0.1")}},
			filterV6:          &conntrackFilter{reply: &connectionTuple{srcIP: netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Reply Destination IP filter",
			filterV4:          &conntrackFilter{reply: &connectionTuple{dstIP: netutils.ParseIPSloppy("192.168.1.1")}},
			filterV6:          &conntrackFilter{reply: &connectionTuple{dstIP: netutils.ParseIPSloppy("dddd:dddd:dddd:dddd:dddd:dddd:dddd:dddd")}},
			expectedV4Matches: 2,
			expectedV6Matches: 0,
		},
		{
			name:              "Reply Source Port filter",
			filterV4:          &conntrackFilter{protocol: 17, reply: &connectionTuple{srcPort: 2000}},
			filterV6:          &conntrackFilter{protocol: 132, reply: &connectionTuple{srcPort: 2000}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
		{
			name:              "Reply Destination Port filter",
			filterV4:          &conntrackFilter{protocol: 6, reply: &connectionTuple{dstPort: 5000}},
			filterV6:          &conntrackFilter{protocol: 132, reply: &connectionTuple{dstPort: 1000}},
			expectedV4Matches: 1,
			expectedV6Matches: 1,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			v4Matches, v6Matches := applyFilter(flowList, tc.filterV4, tc.filterV6)
			require.Equal(t, tc.expectedV4Matches, v4Matches)
			require.Equal(t, tc.expectedV6Matches, v6Matches)
		})
	}
}