func (ac *ACLConfig) GetACLs(ifName string) *InterfaceACLs {
	acls, hasACL := ac.byIf[ifName]
	if !hasACL {
		return &InterfaceACLs{}
	}
	return acls
}
GetACLs returns ACLs assigned to the given interface.
package aclengine
import (
	"errors"
	"fmt"
	"net"
	"strings"
	"sync"
	"go.ligato.io/cn-infra/v2/logging"
	"github.com/contiv/vpp/mock/localclient"
	"github.com/contiv/vpp/plugins/ipnet"
	podmodel "github.com/contiv/vpp/plugins/ksr/model/pod"
	"github.com/contiv/vpp/plugins/policy/renderer"
	"go.ligato.io/cn-infra/v2/datasync/syncbase"
	vpp_acl "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/acl"
)
const maxPortNum = uint32(^uint16(0))
type ConnectionAction int
const (
	ConnActionDenySyn ConnectionAction = iota
	ConnActionDenySynAck
	ConnActionAllow
	ConnActionFailure
)
type ACLAction int
const (
	ACLActionDeny ACLAction = iota
	ACLActionPermit
	ACLActionReflect
	ACLActionFailure
)
const aclPrefix = "config/vpp/acls/v2/acl/"
type MockACLEngine struct {
	sync.Mutex
	Log        logging.Logger
	IPNet      ipnet.API      
	ContivConf ContivConfAPI  
	pods      map[podmodel.ID]*PodConfig
	aclConfig *ACLConfig
}
type PodConfig struct {
	podIP       net.IP
	anotherNode bool
}
type ACLConfig struct {
	byName  map[string]*vpp_acl.ACL
	byIf    map[string]*InterfaceACLs
	changes int
}
type InterfaceACLs struct {
	inbound  *vpp_acl.ACL
	outbound *vpp_acl.ACL
}
func NewMockACLEngine(log logging.Logger, ipNet ipnet.API, contivConf ContivConfAPI) *MockACLEngine {
	return &MockACLEngine{
		Log:        log,
		IPNet:      ipNet,
		ContivConf: contivConf,
		pods:       make(map[podmodel.ID]*PodConfig),
		aclConfig:  NewACLConfig(),
	}
}
func NewACLConfig() *ACLConfig {
	return &ACLConfig{
		byName: make(map[string]*vpp_acl.ACL),
		byIf:   make(map[string]*InterfaceACLs),
	}
}
type ContivConfAPI interface {
	GetMainInterfaceName() string
}
func (mae *MockACLEngine) ClearACLs() {
	changes := mae.aclConfig.changes
	mae.aclConfig = NewACLConfig()
	mae.aclConfig.changes = changes
}
func (mae *MockACLEngine) RegisterPod(pod podmodel.ID, podIP string, anotherNode bool) {
	mae.Lock()
	defer mae.Unlock()
	mae.pods[pod] = &PodConfig{podIP: net.ParseIP(podIP), anotherNode: anotherNode}
}
func (mae *MockACLEngine) ApplyTxn(txn *localclient.Txn, latestRevs *syncbase.PrevRevisions) error {
	mae.Lock()
	defer mae.Unlock()
	if txn == nil {
		return errors.New("txn is nil")
	}
	if txn.VPPDataChangeTxn != nil || txn.VPPDataResyncTxn != nil {
		return errors.New("vpp txn is not supported")
	}
	if txn.LinuxDataResyncTxn != nil {
		mae.ClearACLs()
		for key, value := range txn.LinuxDataResyncTxn.Values {
			if !strings.HasPrefix(key, aclPrefix) {
				return errors.New("non-ACL changed in txn")
			}
			acl, isACL := value.(*vpp_acl.ACL)
			if !isACL {
				return errors.New("failed to cast ACL value")
			}
			err := mae.aclConfig.PutACL(acl)
			if err != nil {
				return err
			}
		}
		return nil
	}
	if txn.LinuxDataChangeTxn == nil {
		return errors.New("linux data change txn is nil")
	}
	dataChange := txn.LinuxDataChangeTxn
	for key, value := range dataChange.Values {
		if !strings.HasPrefix(key, aclPrefix) {
			return errors.New("non-ACL changed in txn")
		}
		aclName := strings.TrimPrefix(key, aclPrefix)
		if value != nil {
			acl, isACL := value.(*vpp_acl.ACL)
			if !isACL {
				return errors.New("failed to cast ACL value")
			}
			err := mae.aclConfig.PutACL(acl)
			if err != nil {
				return err
			}
		} else {
			err := mae.aclConfig.DelACL(aclName)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
func (mae *MockACLEngine) DumpACLs() (acls []*vpp_acl.ACL) {
	for _, acl := range mae.aclConfig.byName {
		acls = append(acls, acl)
	}
	return acls
}
func (mae *MockACLEngine) GetNumOfACLs() int {
	return len(mae.aclConfig.byName)
}
func (mae *MockACLEngine) GetInboundACL(ifName string) *vpp_acl.ACL {
	acls := mae.aclConfig.GetACLs(ifName)
	return acls.inbound
}
func (mae *MockACLEngine) GetOutboundACL(ifName string) *vpp_acl.ACL {
	acls := mae.aclConfig.GetACLs(ifName)
	return acls.outbound
}
func (mae *MockACLEngine) GetACLByName(aclName string) *vpp_acl.ACL {
	acl, has := mae.aclConfig.byName[aclName]
	if !has {
		return nil
	}
	return acl
}
func (mae *MockACLEngine) GetNumOfACLChanges() int {
	return mae.aclConfig.changes
}
func (mae *MockACLEngine) ConnectionPodToPod(srcPod podmodel.ID, dstPod podmodel.ID,
	protocol renderer.ProtocolType, srcPort, dstPort uint16) ConnectionAction {
	var srcIfName, dstIfName string
	srcPodCfg, hasCfg := mae.pods[srcPod]
	if !hasCfg {
		mae.Log.WithField("pod", srcPod).Error("Missing configuration for source pod")
		return ConnActionFailure
	}
	dstPodCfg, hasCfg := mae.pods[dstPod]
	if !hasCfg {
		mae.Log.WithField("pod", dstPod).Error("Missing configuration for destination pod")
		return ConnActionFailure
	}
	if srcPodCfg.anotherNode {
		srcIfName = mae.IPNet.GetVxlanBVIIfName()
		if srcIfName == "" {
			srcIfName = mae.ContivConf.GetMainInterfaceName()
		}
		if srcIfName == "" {
			mae.Log.Error("Missing node output interface")
			return ConnActionFailure
		}
	} else {
		var exists bool
		srcIfName, _, _, exists = mae.IPNet.GetPodIfNames(srcPod.Namespace, srcPod.Name)
		if !exists {
			mae.Log.WithField("pod", srcPod).Error("Missing interface for source pod")
			return ConnActionFailure
		}
	}
	if dstPodCfg.anotherNode {
		dstIfName = mae.IPNet.GetVxlanBVIIfName()
		if dstIfName == "" {
			dstIfName = mae.ContivConf.GetMainInterfaceName()
		}
		if dstIfName == "" {
			mae.Log.Error("Missing node output interface")
			return ConnActionFailure
		}
	} else {
		var exists bool
		dstIfName, _, _, exists = mae.IPNet.GetPodIfNames(dstPod.Namespace, dstPod.Name)
		if !exists {
			mae.Log.WithField("pod", srcPod).Error("Missing interface for source pod")
			return ConnActionFailure
		}
	}
	return mae.testConnection(srcIfName, srcPodCfg.podIP, dstIfName, dstPodCfg.podIP,
		protocol, srcPort, dstPort)
}
func (mae *MockACLEngine) ConnectionPodToInternet(srcPod podmodel.ID, dstIP string,
	protocol renderer.ProtocolType, srcPort, dstPort uint16) ConnectionAction {
	srcPodCfg, hasCfg := mae.pods[srcPod]
	if !hasCfg {
		mae.Log.WithField("pod", srcPod).Error("Missing configuration for source pod")
		return ConnActionFailure
	}
	if srcPodCfg.anotherNode {
		mae.Log.Error("Invalid scenario to test (pod from another node -> Internet)")
		return ConnActionFailure
	}
	srcIfName, _, _, exists := mae.IPNet.GetPodIfNames(srcPod.Namespace, srcPod.Name)
	if !exists {
		mae.Log.WithField("pod", srcPod).Error("Missing interface for source pod")
		return ConnActionFailure
	}
	dstIfName := mae.IPNet.GetVxlanBVIIfName()
	if dstIfName == "" {
		dstIfName = mae.ContivConf.GetMainInterfaceName()
	}
	if dstIfName == "" {
		mae.Log.Error("Missing node output interface")
		return ConnActionFailure
	}
	dstIPAddr := net.ParseIP(dstIP)
	if dstIPAddr == nil {
		mae.Log.WithField("dstIP", dstIP).Error("Failed to parse IP address")
		return ConnActionFailure
	}
	return mae.testConnection(srcIfName, srcPodCfg.podIP, dstIfName, dstIPAddr,
		protocol, srcPort, dstPort)
}
func (mae *MockACLEngine) ConnectionInternetToPod(srcIP string, dstPod podmodel.ID,
	protocol renderer.ProtocolType, srcPort, dstPort uint16) ConnectionAction {
	dstPodCfg, hasCfg := mae.pods[dstPod]
	if !hasCfg {
		mae.Log.WithField("pod", dstPod).Error("Missing configuration for destination pod")
		return ConnActionFailure
	}
	if dstPodCfg.anotherNode {
		mae.Log.Error("Invalid scenario to test (Internet -> pod from another node)")
		return ConnActionFailure
	}
	srcIfName := mae.IPNet.GetVxlanBVIIfName()
	if srcIfName == "" {
		srcIfName = mae.ContivConf.GetMainInterfaceName()
	}
	if srcIfName == "" {
		mae.Log.Error("Missing node output interface")
		return ConnActionFailure
	}
	srcIPAddr := net.ParseIP(srcIP)
	if srcIPAddr == nil {
		mae.Log.WithField("srcIP", srcIP).Error("Failed to parse IP address")
		return ConnActionFailure
	}
	dstIfName, _, _, exists := mae.IPNet.GetPodIfNames(dstPod.Namespace, dstPod.Name)
	if !exists {
		mae.Log.WithField("pod", dstPod).Error("Missing interface for destination pod")
		return ConnActionFailure
	}
	return mae.testConnection(srcIfName, srcIPAddr, dstIfName, dstPodCfg.podIP,
		protocol, srcPort, dstPort)
}
func (mae *MockACLEngine) testConnection(srcIfName string, srcIP net.IP,
	dstIfName string, dstIP net.IP, protocol renderer.ProtocolType, srcPort, dstPort uint16) ConnectionAction {
	mae.Lock()
	defer mae.Unlock()
	mae.Log.WithFields(logging.Fields{
		"srcIfName": srcIfName,
		"srcIP":     srcIP,
		"dstIfName": dstIfName,
		"dstIP":     dstIP,
		"protocol":  protocol,
		"srcPort":   srcPort,
		"dstPort":   dstPort,
	}).Debug("Testing connection")
	var srcIfReflected, dstIfReflected bool
	srcACLs := mae.aclConfig.GetACLs(srcIfName)
	dstACLs := mae.aclConfig.GetACLs(dstIfName)
	srcInAction := mae.evalACL(srcACLs.inbound, srcIP, dstIP, protocol, dstPort)
	if srcInAction == ACLActionFailure {
		return ConnActionFailure
	}
	if srcInAction == ACLActionDeny {
		return ConnActionDenySyn
	}
	if srcInAction == ACLActionReflect {
		srcIfReflected = true
		if srcIfName == dstIfName {
			dstIfReflected = true
		}
	}
	if !dstIfReflected {
		dstOutAction := mae.evalACL(dstACLs.outbound, srcIP, dstIP, protocol, dstPort)
		if dstOutAction == ACLActionFailure {
			return ConnActionFailure
		}
		if dstOutAction == ACLActionDeny {
			return ConnActionDenySyn
		}
		if dstOutAction == ACLActionReflect {
			dstIfReflected = true
			if srcIfName == dstIfName {
				srcIfReflected = true
			}
		}
	}
	if !dstIfReflected {
		dstInAction := mae.evalACL(dstACLs.inbound, dstIP, srcIP, protocol, srcPort)
		if dstInAction == ACLActionFailure {
			return ConnActionFailure
		}
		if dstInAction == ACLActionDeny {
			return ConnActionDenySynAck
		}
	}
	if !srcIfReflected {
		srcOutAction := mae.evalACL(srcACLs.outbound, dstIP, srcIP, protocol, srcPort)
		if srcOutAction == ACLActionFailure {
			return ConnActionFailure
		}
		if srcOutAction == ACLActionDeny {
			return ConnActionDenySynAck
		}
	}
	return ConnActionAllow
}
func (mae *MockACLEngine) evalACL(acl *vpp_acl.ACL, srcIP, dstIP net.IP,
	protocol renderer.ProtocolType, dstPort uint16) ACLAction {
	if acl == nil {
		return ACLActionPermit
	}
	for _, rule := range acl.Rules {
		if rule.MacipRule != nil {
			mae.Log.WithField("acl", *acl).Error("MAC-IP rules are not supported")
			return ACLActionFailure
		}
		if rule.IpRule == nil {
			mae.Log.WithField("acl", *acl).Error("Missing IP Rule")
			return ACLActionFailure
		}
		ipRule := rule.IpRule
		if ipRule.Icmp != nil || ipRule.Ip == nil {
			mae.Log.WithField("acl", *acl).Error("Missing IP or found unsupported ICMP section")
			return ACLActionFailure
		}
		if ipRule.Udp != nil && ipRule.Tcp != nil {
			mae.Log.WithField("acl", *acl).Error("Both TCP and UDP sections are defined")
			return ACLActionFailure
		}
		if ipRule.Ip.SourceNetwork != "" {
			_, srcNetwork, err := net.ParseCIDR(ipRule.Ip.SourceNetwork)
			if err != nil {
				mae.Log.WithField("acl", *acl).Error("Invalid source network")
				return ACLActionFailure
			}
			if !srcNetwork.Contains(srcIP) {
				continue
			}
		}
		if ipRule.Ip.DestinationNetwork != "" {
			_, dstNetwork, err := net.ParseCIDR(ipRule.Ip.DestinationNetwork)
			if err != nil {
				mae.Log.WithField("acl", *acl).Error("Invalid destination network")
				return ACLActionFailure
			}
			if !dstNetwork.Contains(dstIP) {
				continue
			}
		}
		switch protocol {
		case renderer.TCP:
			if ipRule.Udp != nil {
				continue
			}
			if ipRule.Tcp != nil {
				srcPortRange := ipRule.Tcp.SourcePortRange
				if srcPortRange == nil {
					mae.Log.WithField("acl", *acl).Error("Missing source port range")
					return ACLActionFailure
				}
				if srcPortRange.LowerPort != 0 || srcPortRange.UpperPort != maxPortNum {
					mae.Log.WithField("acl", *acl).Error("Source port range does not cover all ports")
					return ACLActionFailure
				}
				dstPortRange := ipRule.Tcp.DestinationPortRange
				if dstPortRange == nil {
					mae.Log.WithField("acl", *acl).Error("Missing destination port range")
					return ACLActionFailure
				}
				if dstPort < uint16(dstPortRange.LowerPort) || dstPort > uint16(dstPortRange.UpperPort) {
					continue
				}
			}
		case renderer.UDP:
			if ipRule.Tcp != nil {
				continue
			}
			if ipRule.Udp != nil {
				srcPortRange := ipRule.Udp.SourcePortRange
				if srcPortRange == nil {
					mae.Log.WithField("acl", *acl).Error("Missing source port range")
					return ACLActionFailure
				}
				if srcPortRange.LowerPort != 0 || srcPortRange.UpperPort != maxPortNum {
					mae.Log.WithField("acl", *acl).Error("Source port range does not cover all ports")
					return ACLActionFailure
				}
				dstPortRange := ipRule.Udp.DestinationPortRange
				if dstPortRange == nil {
					mae.Log.WithField("acl", *acl).Error("Missing destination port range")
					return ACLActionFailure
				}
				if dstPort < uint16(dstPortRange.LowerPort) || dstPort > uint16(dstPortRange.UpperPort) {
					continue
				}
			}
		case renderer.OTHER:
			if ipRule.Tcp != nil || ipRule.Udp != nil {
				continue
			}
		}
		mae.Log.WithFields(logging.Fields{
			"rule": *rule,
			"acl":  acl.Name,
		}).Debug("Connection matched by ACL rule")
		switch rule.Action {
		case vpp_acl.ACL_Rule_DENY:
			return ACLActionDeny
		case vpp_acl.ACL_Rule_PERMIT:
			return ACLActionPermit
		case vpp_acl.ACL_Rule_REFLECT:
			return ACLActionReflect
		default:
			return ACLActionFailure
		}
	}
	return ACLActionDeny  
}