func (v *Validator) validateGigEDefaultRteNextHop(rteID string, vrfID uint32, vrfMap VrfMap, rtMap RouteMap,
	node *telemetrymodel.Node, outIfc *telemetrymodel.NodeInterface) int {
	if defaultRte, ok := vrfMap[0]["0.0.0.0/0"]; ok {
		defaultRteID := defaultRte.Value.NextHopAddr + "/32"
		if defaultRte, ok := vrfMap[0][defaultRteID]; ok {
			numErrs := v.validateRoute(defaultRteID, 0, vrfMap, rtMap, node.Name, outIfc.Value.Name,
				defaultRte.Value.NextHopAddr, 0, vpp_l3.Route_DROP)
			return numErrs
		}
	}
	return 0
}
validateGigEDefaultRteNextHop validates the next hop in the VRF0 default
route (if it's set). Two rules must be satisfied: the route to the next
hop must exist, and there must be an entry for the next hop in the ARP
table.
package l3
import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"text/tabwriter"
	"go.ligato.io/cn-infra/v2/logging"
	vpp_l3 "go.ligato.io/vpp-agent/v3/proto/ligato/vpp/l3"
	"github.com/contiv/vpp/plugins/crd/api"
	"github.com/contiv/vpp/plugins/crd/cache/telemetrymodel"
	"github.com/contiv/vpp/plugins/crd/validator/utils"
	"github.com/contiv/vpp/plugins/ipnet"
)
const (
	routeNotValidated = iota
	routeInvalid      = iota
	routeValid        = iota
	gigENameMatch = `GigabitEthernet[[:alnum:]]*/[[:alnum:]]*/[[:alnum:]]*`
	tap2HostName  = "tap-vpp2"
	hostToVppIfName = "vpp1"
	vppToHostIfName = "tap-vpp2"
)
var (
	v4CidrRe = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}/(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).*`)
)
type Validator struct {
	Log logging.Logger
	VppCache api.VppCache
	K8sCache api.K8sCache
	Report   api.Report
}
type Vrf map[string]telemetrymodel.NodeIPRoute
type VrfMap map[uint32]Vrf
type RouteMap map[uint32]map[string]int
type LinuxIfMap map[string]string
func (v *Validator) Validate() {
	nodeList := v.VppCache.RetrieveAllNodes()
	numErrs := 0
	v.Report.SetPrefix("L3-FIB")
	for _, node := range nodeList {
		if node.NodeIPam == nil {
			v.Log.Infof("No IPAM data for node %s", node.Name)
			v.Report.AppendToNodeReport(node.Name, "validation skipped - no IPAM data available")
			continue
		}
		if node.NodeInterfaces == nil {
			v.Log.Infof("validation aborted - no interfaces data available")
			v.Report.AppendToNodeReport(node.Name, "validation skipped - no interface data available")
			continue
		}
		if node.NodeStaticRoutes == nil {
			v.Log.Infof("validation aborted - no route data available")
			v.Report.AppendToNodeReport(node.Name, "validation skipped - no route data available")
			continue
		}
		vrfMap, err := v.createVrfMap(node)
		if err != nil {
			v.Report.AppendToNodeReport(node.Name, err.Error())
		}
		routeMap := v.createValidationMap(vrfMap)
		numErrs += v.validateVrf1PodRoutes(node, vrfMap, routeMap)
		numErrs += v.validateRouteToLocalVxlanBVI(node, vrfMap, routeMap)
		numErrs += v.validateVrf0GigERoutes(node, vrfMap, routeMap)
		numErrs += v.validateVrf0LocalHostRoute(node, vrfMap, routeMap)
		numErrs += v.validateDefaultRoutes(node, vrfMap, routeMap)
		numErrs += v.validateRemoteNodeRoutes(node, vrfMap, routeMap)
		numErrs += v.validatePodSubnetCidrRoutes(node, vrfMap, routeMap)
		numErrs += v.validateVppHostNetworkRoutes(node, vrfMap, routeMap)
		numErrs += v.validateRoutesToLinuxInterfaces(node, vrfMap, routeMap)
		numErrs += v.checkUnvalidatedRoutes(routeMap, node.Name)
	}
	if numErrs == 0 {
		v.Report.AppendToNodeReport(api.GlobalMsg, "validation OK")
	} else {
		errString := fmt.Sprintf("%d error%s found", numErrs, printS(numErrs))
		v.Report.AppendToNodeReport(api.GlobalMsg, errString)
	}
}
func (v *Validator) createVrfMap(node *telemetrymodel.Node) (VrfMap, error) {
	vrfMap := make(VrfMap, 0)
	for _, route := range node.NodeStaticRoutes {
		vrf, ok := vrfMap[route.Value.VrfId]
		if !ok {
			vrfMap[route.Value.VrfId] = make(Vrf, 0)
			vrf = vrfMap[route.Value.VrfId]
		}
		vrf[route.Value.DstNetwork] = route
	}
	return vrfMap, nil
}
func (v *Validator) createValidationMap(vm map[uint32]Vrf) RouteMap {
	valMap := make(RouteMap, 0)
	for vIdx, vrf := range vm {
		vrfRoutes := make(map[string]int, 0)
		for _, rte := range vrf {
			vrfRoutes[rte.Value.DstNetwork] = routeNotValidated
		}
		valMap[vIdx] = vrfRoutes
	}
	return valMap
}
func (v *Validator) validateVrf1PodRoutes(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	for _, pod := range node.PodMap {
		if pod.IPAddress == node.ManIPAddr {
			continue
		}
		if pod.IPAddress != "" {
			numErrs += v.validateRoute(pod.IPAddress+"/32", 1, vrfMap, routeMap, node.Name,
				pod.VppIfName, pod.IPAddress, 0, vpp_l3.Route_INTRA_VRF)
		} else {
			numErrs++
			errString := fmt.Sprintf("pod '%s': IP Address empty, pod route validation skipped", pod.Name)
			v.Report.AppendToNodeReport(node.Name, errString)
		}
	}
	return numErrs
}
func (v *Validator) validateVrf0GigERoutes(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	ifc, err := findInterface(gigENameMatch, node.NodeInterfaces)
	if err != nil {
		numErrs++
		errString := fmt.Sprintf("local GigE interface not found, error %s", err)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	numErrs += v.validateRoute(node.IPAddr, 0, vrfMap, routeMap, node.Name, ifc.Value.Name,
		"0.0.0.0", 0, vpp_l3.Route_INTRA_VRF)
	for _, ipAddr := range ifc.Value.IpAddresses {
		if ipAddr == node.IPAddr {
			numErrs += v.validatePhyNextHopRoutes(ipAddr, 0, vrfMap, routeMap, node.Name,
				ifc, 0, vpp_l3.Route_DROP)
			break
		}
	}
	nodeList := v.VppCache.RetrieveAllNodes()
	for _, remoteNode := range nodeList {
		if remoteNode == node {
			continue
		}
		dstIP := strings.Split(remoteNode.IPAddr, "/")
		numErrs += v.validateRoute(dstIP[0]+"/32", 0, vrfMap, routeMap, node.Name, ifc.Value.Name,
			dstIP[0], 0, vpp_l3.Route_INTRA_VRF)
	}
	return numErrs
}
func (v *Validator) validateRemoteNodeRoutes(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	_, err := findInterface(ipnet.DefaultVxlanBVIInterfaceName, node.NodeInterfaces)
	if err != nil {
		numErrs++
		errString := fmt.Sprintf("local vxlanBVI lookup failed, error %s; "+
			"unable to validate routes to remote nodes", err)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	nodeList := v.VppCache.RetrieveAllNodes()
	for _, othNode := range nodeList {
		if othNode.NodeIPam == nil {
			numErrs++
			errString := fmt.Sprintf("missing IPAM for node %s - routes to the node not validated",
				othNode.Name)
			v.Report.AppendToNodeReport(node.Name, errString)
			continue
		}
		if othNode.Name == node.Name {
			numErrs += v.validateRoute(othNode.NodeIPam.VppHostNetwork, 1, vrfMap, routeMap, node.Name,
				"", "0.0.0.0", 0, vpp_l3.Route_INTER_VRF)
			continue
		}
		ifc, err := findInterface(ipnet.DefaultVxlanBVIInterfaceName, othNode.NodeInterfaces)
		if err != nil {
			numErrs++
			errString := fmt.Sprintf("failed to validate route %s VRF%d - "+
				"failed lookup for vxlanBVI for node %s, error %s", othNode.ManIPAddr+"/32", 0, othNode.Name, err)
			v.Report.AppendToNodeReport(node.Name, errString)
			continue
		}
		bviAddr, err := checkIfRouteNextHopPointsToInterface(othNode.NodeIPam.VppHostNetwork, 1, vrfMap,
			ifc, othNode.Name)
		if err == nil {
			numErrs += v.validateRoute(othNode.NodeIPam.VppHostNetwork, 1, vrfMap, routeMap, node.Name,
				ipnet.DefaultVxlanBVIInterfaceName, bviAddr, 0, vpp_l3.Route_INTRA_VRF)
			numErrs += v.validateRoute(othNode.ManIPAddr+"/32", 0, vrfMap, routeMap, node.Name,
				"", "0.0.0.0", 1, vpp_l3.Route_INTER_VRF)
			numErrs += v.validateRoute(othNode.ManIPAddr+"/32", 1, vrfMap, routeMap, node.Name,
				ipnet.DefaultVxlanBVIInterfaceName, bviAddr, 0, vpp_l3.Route_INTRA_VRF)
		} else {
			numErrs++
			v.Report.AppendToNodeReport(node.Name, err.Error())
		}
		bviAddr, err = checkIfRouteNextHopPointsToInterface(othNode.NodeIPam.PodSubnetThisNode, 1, vrfMap,
			ifc, othNode.Name)
		if err == nil {
			numErrs += v.validateRoute(othNode.NodeIPam.PodSubnetThisNode, 1, vrfMap, routeMap, node.Name,
				ipnet.DefaultVxlanBVIInterfaceName, bviAddr, 0, vpp_l3.Route_INTRA_VRF)
		} else {
			numErrs++
			v.Report.AppendToNodeReport(node.Name, err.Error())
		}
	}
	return numErrs
}
func (v *Validator) validateVrf0LocalHostRoute(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	localRoute, ok := vrfMap[0][node.ManIPAddr+"/32"]
	if !ok {
		numErrs++
		errString := fmt.Sprintf("missing route with dst IP %s in VRF0 for node %s",
			node.ManIPAddr+"/32", node.Name)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	tapIntf, ok := node.NodeInterfaces.GetByName(localRoute.Value.OutgoingInterface)
	if !ok {
		numErrs++
		errString := fmt.Sprintf("missing interface %s for route with dst IP %s in VRF0 for node %s",
			localRoute.Value.OutgoingInterface, localRoute.Value.DstNetwork, node.Name)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	var nextHop string
	for _, arpEntry := range node.NodeIPArp {
		if arpEntry.Value.Interface == tapIntf.Value.Name {
			nextHop = arpEntry.Value.IpAddress
			numErrs += v.validateRoute(nextHop+"/32", 0, vrfMap, routeMap, node.Name,
				tap2HostName, nextHop, 0, vpp_l3.Route_INTRA_VRF)
			break
		}
	}
	numErrs += v.validateRoute(node.ManIPAddr+"/32", 0, vrfMap, routeMap, node.Name,
		tap2HostName, nextHop, 0, vpp_l3.Route_INTRA_VRF)
	return numErrs
}
func (v *Validator) validateDefaultRoutes(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	ifc, err := findInterface(gigENameMatch, node.NodeInterfaces)
	if err != nil {
		numErrs++
		errString := fmt.Sprintf("failed to validate route %s VRF%d - "+
			"local GigE interface not found (error '%s')", "0.0.0.0/0", 0, err)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	numErrs += v.validateRoute("0.0.0.0/0", 0, vrfMap, routeMap, node.Name,
		ifc.Value.Name, "", 0, vpp_l3.Route_INTRA_VRF)
	numErrs += v.validateRoute("0.0.0.0/32", 0, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("224.0.0.0/4", 0, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("240.0.0.0/4", 0, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("255.255.255.255/32", 0, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("::/0", 0, vrfMap, routeMap, node.Name,
		"", "::", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("fe80::/10", 0, vrfMap, routeMap, node.Name,
		"", "::", 0, vpp_l3.Route_INTRA_VRF)
	numErrs += v.validateRoute("0.0.0.0/0", 1, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_INTER_VRF)
	numErrs += v.validateRoute("0.0.0.0/32", 1, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("224.0.0.0/4", 1, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("240.0.0.0/4", 1, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateRoute("255.255.255.255/32", 1, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 0, vpp_l3.Route_DROP)
	return numErrs
}
func (v *Validator) validateRouteToLocalVxlanBVI(node *telemetrymodel.Node, vrfMap map[uint32]Vrf,
	routeMap map[uint32]map[string]int) int {
	numErrs := 0
	loopIf, err := findInterface(ipnet.DefaultVxlanBVIInterfaceName, node.NodeInterfaces)
	if err != nil {
		numErrs++
		v.Report.AppendToNodeReport(node.Name, err.Error())
		return numErrs
	}
	for _, ip := range loopIf.Value.IpAddresses {
		numErrs += v.validateRoute(ip, 1, vrfMap, routeMap, node.Name,
			loopIf.Value.Name, "0.0.0.0", 0, vpp_l3.Route_INTRA_VRF)
		numErrs += v.validatePhyNextHopRoutes(ip, 1, vrfMap, routeMap,
			node.Name, loopIf, 0, vpp_l3.Route_DROP)
	}
	return numErrs
}
func (v *Validator) validatePodSubnetCidrRoutes(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	if node.NodeIPam == nil {
		numErrs++
		errString := fmt.Sprintf("missing IPAM for node %s - validatePodSubnetCidrRoutes not validated",
			node.Name)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	podSubnetCidrRte := node.NodeIPam.Config.PodSubnetCIDR
	numErrs += v.validateRoute(podSubnetCidrRte, 0, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 1, vpp_l3.Route_INTER_VRF)
	numErrs += v.validateRoute(podSubnetCidrRte, 1, vrfMap, routeMap, node.Name,
		"local0", "0.0.0.0", 0, vpp_l3.Route_DROP)
	return numErrs
}
func (v *Validator) validateVppHostNetworkRoutes(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	if node.NodeIPam == nil {
		numErrs++
		errString := fmt.Sprintf("missing IPAM for node %s - routes to VppHostSubnetCIDR not validated",
			node.Name)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	numErrs += v.validateRoute(node.NodeIPam.Config.VPPHostSubnetCIDR, 0, vrfMap, routeMap, node.Name,
		"", "0.0.0.0", 1, vpp_l3.Route_INTER_VRF)
	numErrs += v.validateRoute(node.NodeIPam.Config.VPPHostSubnetCIDR, 1, vrfMap, routeMap, node.Name,
		"local0", "0.0.0.0", 0, vpp_l3.Route_DROP)
	numErrs += v.validateLocalVppHostNetworkRoute(node, vrfMap, routeMap)
	return numErrs
}
func (v *Validator) validateLocalVppHostNetworkRoute(node *telemetrymodel.Node, vrfMap VrfMap, routeMap RouteMap) int {
	numErrs := 0
	ifc, err := findInterface(tap2HostName, node.NodeInterfaces)
	if err != nil {
		numErrs++
		errString := fmt.Sprintf("failed to validate route to tap-vpp2 - "+
			"failed lookup for tap-vpp2, err %s", err)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	if node.NodeIPam == nil {
		numErrs++
		errString := fmt.Sprintf("missing IPAM for node %s - route to VppHostNetwork not validated",
			node.Name)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	ipamHostNetAddr, ipamHostNetMask, err := utils.Ipv4CidrToAddressAndMask(node.NodeIPam.VppHostNetwork)
	if err != nil {
		numErrs++
		errString := fmt.Sprintf("ipam vppHostNetwork %s bad format; err %s",
			node.NodeIPam.VppHostNetwork, err)
		v.Report.AppendToNodeReport(node.Name, errString)
		return numErrs
	}
	ipamHostNetPrefix := ipamHostNetAddr &^ ipamHostNetMask
	for _, ipAddr := range ifc.Value.IpAddresses {
		numErrs += v.validateRoute(ipAddr, 0, vrfMap, routeMap, node.Name,
			ifc.Value.Name, "0.0.0.0", 0, vpp_l3.Route_INTRA_VRF)
		numErrs += v.validatePhyNextHopRoutes(ipAddr, 0, vrfMap, routeMap,
			node.Name, ifc, 0, vpp_l3.Route_DROP)
		ifHostNetAddr, ifHostNetMask, err := utils.Ipv4CidrToAddressAndMask(ipAddr)
		if err != nil {
			numErrs++
			errString := fmt.Sprintf("tap-vpp2 IP address %s bad format; err %s",
				ifc.Value.IpAddresses[0], err)
			v.Report.AppendToNodeReport(node.Name, errString)
			continue
		}
		ifHostNetPrefix := ifHostNetAddr &^ ifHostNetMask
		if (ifHostNetMask != ipamHostNetMask) || (ifHostNetPrefix != ipamHostNetPrefix) {
			numErrs++
			errString := fmt.Sprintf("inconsistent ipam vppHostNetwork %s vs tap-vpp2 IP address %s",
				node.NodeIPam.VppHostNetwork, ifc.Value.IpAddresses[0])
			v.Report.AppendToNodeReport(node.Name, errString)
			continue
		}
	}
	return numErrs
}
func (v *Validator) validateRoute(rteID string, vrfID uint32, vrfMap VrfMap, rtMap RouteMap, nodeName string,
	eOutIface string, eNextHopAddr string, eViaVrf uint32, eType vpp_l3.Route_RouteType) int {
	numErrs := 0
	route, ok := vrfMap[vrfID][rteID]
	if !ok {
		numErrs++
		errString := fmt.Sprintf("missing route %s in VRF%d", rteID, vrfID)
		v.Report.AppendToNodeReport(nodeName, errString)
		return numErrs
	}
	rtMap[vrfID][route.Value.DstNetwork] = routeValid
	if (route.Value.OutgoingInterface != "") && (eOutIface != route.Value.OutgoingInterface) {
		numErrs++
		rtMap[vrfID][route.Value.DstNetwork] = routeInvalid
		errString := fmt.Sprintf("invalid route %s in VRF%d; bad outgoing if - "+
			"have '%s', expecting '%s'", route.Value.DstNetwork, vrfID, route.Value.OutgoingInterface, eOutIface)
		v.Report.AppendToNodeReport(nodeName, errString)
	}
	if route.Value.ViaVrfId != eViaVrf {
		numErrs++
		rtMap[vrfID][route.Value.DstNetwork] = routeInvalid
		errString := fmt.Sprintf("invalid route %s in VRF%d; bad viaVrfID - "+
			"have '%d', expecting '%d'", route.Value.DstNetwork, vrfID, route.Value.ViaVrfId, eViaVrf)
		v.Report.AppendToNodeReport(nodeName, errString)
	}
	if route.Value.Type != eType {
		numErrs++
		rtMap[vrfID][route.Value.DstNetwork] = routeInvalid
		errString := fmt.Sprintf("invalid route %s in VRF%d; bad Type - "+
			"have '%d', expecting '%d'", route.Value.DstNetwork, vrfID, route.Value.Type, eType)
		v.Report.AppendToNodeReport(nodeName, errString)
	}
	if (eNextHopAddr != "") && (route.Value.NextHopAddr != eNextHopAddr) {
		numErrs++
		rtMap[vrfID][route.Value.DstNetwork] = routeInvalid
		errString := fmt.Sprintf("invalid route %s in VRF%d; bad nextHop -"+
			"have '%s', expecting '%s", route.Value.DstNetwork, vrfID, route.Value.NextHopAddr, eNextHopAddr)
		v.Report.AppendToNodeReport(nodeName, errString)
	}
	return numErrs
}
func (v *Validator) validatePhyNextHopRoutes(rteID string, vrfID uint32, vrfMap VrfMap, rtMap RouteMap, nodeName string,
	outIfc *telemetrymodel.NodeInterface, eViaVrf uint32, eType vpp_l3.Route_RouteType) int {
	numErrs := 0
	rteAddr, rteMask, _ := utils.Ipv4CidrToAddressAndMask(rteID)
	phyNextHopCidr := utils.AddressAndMaskToIPv4(rteAddr, ^uint32(0))
	phyNextHopAddr := fmt.Sprintf("%d.%d.%d.%d",
		rteAddr>>24, (rteAddr>>16)&0xFF, (rteAddr>>8)&0xff, rteAddr&0xFF)
	numErrs += v.validateRoute(phyNextHopCidr, vrfID, vrfMap, rtMap, nodeName,
		outIfc.Value.Name, phyNextHopAddr, 0, vpp_l3.Route_INTRA_VRF)
	drop1Addr := utils.AddressAndMaskToIPv4(rteAddr&^rteMask, ^uint32(0))
	numErrs += v.validateRoute(drop1Addr, vrfID, vrfMap, rtMap, nodeName,
		"", "0.0.0.0", 0, eType)
	drop2Addr := utils.AddressAndMaskToIPv4(rteAddr|rteMask, ^uint32(0))
	numErrs += v.validateRoute(drop2Addr, vrfID, vrfMap, rtMap, nodeName,
		"", "0.0.0.0", 0, eType)
	return numErrs
}