#!/usr/bin/env python
# Copyright 2019-2020 Cumulus Networks, Inc
#
# cl-flow-trace --
#   WARNING: This is a BETA utility
#   validates if there is a valid path to a src and dst

import argparse

from nlmanager.kernel_nlhelpers import *
from nlmanager.nlpacket import Route, Neighbor

from cStringIO import StringIO
from ipaddr import IPAddress, IPNetwork, IPv4Network, IPv6Network, AddressValueError
from pprint import pformat, pprint
from subprocess import Popen, PIPE, check_output, CalledProcessError
import argparse
import json
import os
import re
from sys import *
import cumulus.platforms

def macFormat1ToFormat2(mac):
    '''
    Format1: 1122.3344.5566
    Format2: 11:22:33:44:55:66
    This api converst a mac string of Format1 to a mac string of Format2
    '''
    if not ':' in mac:
        mac = mac.replace('.', '')
        mac = ':'.join([mac[i:i+2] for i in range(0, len(mac), 2)])
    return mac

class ClFlowTraceLogger:
    def __init__(self, args):
        self.verbose = args.verbose
        self.very_verbose = args.very_verbose
        if self.very_verbose:
            self.verbose = self.very_verbose

    #only the success/failure logging.
    def log(self, component, line):
        string = "[ " + component + " ] " + line + "\n"
        stdout.write(string)

    #verbose logging. Only the milestones
    def logv(self, component, line):
        if self.verbose:
            string = "[ " + component + " ] " + line + "\n"
            stdout.write(string)

    #very-verbose logging. Everything
    def logvv(self, component, line):
        if self.very_verbose:
            string = "[ " + component + " ] " + line + "\n"
            stdout.write(string)

    #log error
    def loge(self, component, line):
        string = "[ " + component + " ] [ ERROR ] " + line + "\n"
        stdout.write(string)

    #log error and exit
    def loge_exit(self, component, line):
        string = "[ " + component + " ] [ ERROR ] " + line + "\n"
        stdout.write(string)
        exit(-1)

class ClFlowTraceKernel:

    def __init__(self, args, logger):
        self.kernel = Kernel()
        self.logger = logger
        self.args = args
        self.svi = None
        self.swp = None
        self.bridge = None
        self.vrf = None
        self.neigh = None
        self.neigh_mac = None
        self.via = None
        self.vid = None
    
    def collect_data(self):
        # dict ifname -> Kernel.Interface
        ifaces = self.kernel.ifaces

        if not ifaces:
            stderr.write("No switch ports to check\n")
            exit(0)

    def get_mac(self, mac, port, vid):
        mac_re = re.compile("([^ ]+) *([^ ]+)")
        try:
            for line in Popen(["/sbin/bridge", "fdb", "show", "vlan", str(vid)], shell=False, stdout=PIPE).stdout:

                out_mac, rest = mac_re.findall(line)[0]

                mac_masters = re.findall("master ([^ ]+) ", line)
                brports = re.findall("dev ([^ ]+) ", line)
                mac_extern_learn = re.search("offload", line)
                if port:
                    if mac == out_mac and brports[0] == port:
                        self.logger.logvv("os", "fdb has the mac entry - %s output - %s" % (out_mac, line))
                        return True, mac_masters, mac_extern_learn
                else:
                    if mac == out_mac:
                        self.logger.logvv("os", "fdb has the mac entry - %s output - %s" % (out_mac, line))
                        return True, mac_masters, mac_extern_learn
        except Exception as e:
            self.logger.loge("os", "Popen failed for bridge fdb show mac %s brport %s vlan %d - error %s" % (mac, port, vid, str(e)))
        self.logger.loge("os", "fdb has no mac entry - %s brport %s vlan %d" % (mac, port, vid))
        return False, [], None

    def get_neigh(self, host, svi):
        # neighbor verification
        # get neigh matching svi. The lib can return a list. Eg - same neigh on SVI and VRR
        self.logger.logvv("os", "Try finding if neigh entry exists for the host %s svi %s" % (host, svi))
        all_neighs = self.kernel.neighbors
        if not all_neighs:
            self.logger.loge_exit("os", "No neighs in Kernel - %s")
        try:
            #VRR interface can have the same neigh. We need to ignore those
            neighs = all_neighs[host]
            if not neighs:
                self.logger.loge_exit("os", "No neighs found for host  - %s" % host)
                return None
        except KeyError:
            self.logger.loge_exit("os", "No neighs found for host  - %s" % host)
            return None
        for neigh in neighs:
            #filter vrr interface
            if "-v" not in neigh.svi:
                if svi: 
                    if neigh.svi == svi:
                        self.logger.logvv("os", "neigh entry exists for the host %s neigh svi %s" % (host, neigh.svi))
                        return neigh
                else:
                    self.logger.logvv("os", "neigh entry exists for the host %s neigh svi %s" % (host, neigh.svi))
                    return neigh
        self.logger.loge("os", "No neighs found for host - %s svi %s" % (host, svi))
        return None

    def verify_route_neigh_mac(self, svi, swp, bridge, vrf, host, host_type):
        # Routes verification
        # use the kernel lib way of doing once lib has added support
        # As of now, use reg ex to get mathching route using "ip route get"
        #routes = self.kernel.routes
        #if routes is None:
        #    self.logger.loge("os", "No routes found")
        #route = self.kernel.routes[host]
        #print route
        self.logger.logvv("os", "Start route, neigh and mac verification for the host - %s" % host)
        self.logger.logvv("os", "Try finding if matching route exists for the host - %s" % host)
        self.vid = self.args.vid
        rt_ifaces = None
        rt_prefix = None
        rt_vias = None
        count = 0
        route_re = re.compile("(^default |^local |^broadcast |^anycast |^unicast |^) *([^ ]+)")
        try:
            for line in Popen(["/sbin/ip", "-o", "-d", "route", "get", "vrf", vrf.ifname, host],
                              shell=False, stdout=PIPE).stdout:

                count+=1
                begin, rt_prefix = route_re.findall(line)[0]

                if begin == "unicast ":
                    rt_vias = re.findall("via ([^ ]+) ", line)
                    rt_ifaces = re.findall("dev ([^ ]+)", line)
                    self.logger.logvv("os", "matching route found - %s" % line)
                else:
                    self.logger.loge("os", "Route not found for host - %s - %s" % (host, line))
                    break
        except Exception as e:
            self.logger.loge("os", "Popen failed for - ip route get vrf %s host %s - error %s" % (vrf.ifname, host, str(e)))
        if count == 0:
            self.logger.loge("os", "Route not found for host - %s" % host)
        if count > 1:
            self.logger.loge_exit("os", "More than 1 best matching route for host - %s" % host)
        if rt_ifaces and not rt_vias:
            self.logger.logvv("os", "No Vias. Host %s is locally connected" % host)
            # only for sip, match the svi
            if host_type == "sip":
                if rt_ifaces[0] == svi.ifname:
                    self.logger.logvv("os", "Route SVI matches the specified SVI %s" % svi.ifname)
                else:
                    self.logger.loge("os", "Route svi %s does not match the specified SVI %s" % (rt_ifaces[0],
                        svi.ifname))

            # get neigh matching host and svi
            neigh = None
            specified_svi = None
            if host_type == "sip":
                neigh = self.get_neigh(host, svi.ifname)
                specified_svi = svi.ifname
            else:
                neigh = self.get_neigh(host, rt_ifaces[0])
                specified_svi = rt_ifaces[0]
            if neigh:
                neigh_mac = macFormat1ToFormat2(neigh.mac).lower()
                self.neigh = neigh
                self.neigh_mac = neigh_mac
                if neigh.state == "NUD_NOARP":
                    self.logger.loge("os", "Locally connected neigh state is NOARP - %s" % host)
                if neigh.svi != specified_svi:
                    self.logger.loge("os", "Neigh SVI %s and specified svi %s don't match" % (neigh.svi, specified_svi))
                else:
                    self.logger.logvv("os", "Neigh SVI %s and specified svi %s match" % (neigh.svi, specified_svi))
                    if neigh.state == "NUD_REACHABLE" or neigh.state == "NUD_PERMANENT":
                        self.logger.logvv("os", "Locally connected neigh state is correct - %s" % neigh.state)
                    else:
                        self.logger.loge("os", "Neigh is not in correct state - %s" % neigh.state)
                    self.logger.logv("os", "neigh verification successful - %s" % neigh.ip)

                    #bridge fdb mac verification -

                    self.logger.logvv("os", "Try finding if bridge fdb entry exists for the host's mac - %s" % neigh_mac)
                    found = False
                    mac_masters = None
                    mac_extern_learn = None
                    if host_type == "sip":
                        found, mac_masters, mac_extern_learn = self.get_mac(neigh_mac, self.args.port, self.args.vid)
                    else:
                        l2vni_svi = self.kernel.ifaces[neigh.svi]
                        if not l2vni_svi:
                            self.logger.loge("os", "SVI %s doesn't exist" % neigh.svi)
                        else:
                            self.logger.logvv("os", "L2VNI SVI %s exists in the Kernel" % neigh.svi)
                            vid_l2vni = self.kernel.get_svi_vid(l2vni_svi)
                            self.logger.logvv("os", "L2VNI SVI vlan id - %d" % vid_l2vni)
                            found, mac_masters, mac_extern_learn = self.get_mac(neigh_mac, None, vid_l2vni)
                            self.vid = vid_l2vni
                    if found:
                        if mac_extern_learn:
                            self.logger.loge("os", "Problem - Locally connected neigh is extern_learn - %s" % line)
                        else:
                            self.logger.logvv("os", "Locally connected neigh state is not extern_learn - %s" % line)
                        if mac_masters[0] != bridge.ifname:
                            self.logger.loge("os", "mac is not on the same bridge - %s" % line)
                        else:
                            self.logger.logvv("os", "mac is on the same bridge - %s" % line)
                        self.logger.logv("os", "Neigh fdb mac verification successful - %s" % line)
                    else:
                        self.logger.loge("os", "Neigh fdb mac verification unsuccessful - %s" % line)

                    self.logger.log("os", "Host %s verification successful" % host)
            else:
                self.logger.loge("os", "Host %s verification failed" % host)

        # Non locally connected case. Neigh is connected to remote vtep
        elif rt_ifaces and rt_vias:
            self.logger.logvv("os", "Host %s verification for neigh and mac. Non locally connected" % host)
            # get neigh matching host and svi
            neigh = None
            if host_type == "sip":
                neigh = self.get_neigh(host, svi.ifname)
            else:
                neigh = self.get_neigh(host, None)
            if neigh:
                neigh_mac = macFormat1ToFormat2(neigh.mac).lower()
                self.neigh = neigh
                self.neigh_mac = neigh_mac
                if neigh.state != "NUD_NOARP":
                    self.logger.loge("os", "Remotely connected neigh state is not NOARP - %s" % host)
                self.logger.logvv("os", "Neigh SVI %s specified svi %s" % (neigh.svi, svi.ifname))
                if host_type == "sip":
                    if neigh.svi != svi.ifname:
                        self.logger.loge("os", "Neigh SVI %s and specified svi %s don't match" % (neigh.svi, svi.ifname))
                    else:
                        self.logger.logvv("os", "Neigh SVI %s and specified svi %s match" % (neigh.svi, svi.ifname))
                self.logger.logv("os", "neigh verification successful - %s" % neigh.ip)

                #bridge fdb mac verification -
                l2vni_svi = self.kernel.ifaces[neigh.svi]
                if not l2vni_svi:
                    self.logger.loge("os", "SVI %s doesn't exist" % neigh.svi)
                else:
                    self.logger.logvv("os", "L2VNI SVI %s exists in the Kernel" % neigh.svi)
                    vid_l2vni = self.kernel.get_svi_vid(l2vni_svi)
                    self.vid = vid_l2vni
                    self.logger.logvv("os", "L2VNI SVI vlan id - %d" % vid_l2vni)
                    vxlan_iface_l2vni = self.kernel.get_vni_vxlan(vid_l2vni)
                    self.logger.logvv("os", "Corresponding l2vni vxlan interface - %s" % vxlan_iface_l2vni.ifname)

                    self.logger.logvv("os", "Try finding if bridge fdb entry exists for the host's mac - %s" % neigh_mac)
                    found, mac_masters, mac_extern_learn = self.get_mac(neigh_mac, vxlan_iface_l2vni.ifname, vid_l2vni)
                    if not mac_extern_learn:
                        self.logger.loge("os", "Problem - Remotely connected neigh is not extern_learn - %s" % line)
                    else:
                        self.logger.logvv("os", "Remotely connected neigh state is extern_learn - %s" % line)
                    if mac_masters[0] != bridge.ifname:
                        self.logger.loge("os", "mac is not on the same bridge - %s" % line)
                    else:
                        self.logger.logvv("os", "mac is on the same bridge - %s" % line)
                    self.logger.logv("os", "Neigh fdb mac verification successful - %s" % line)

                    self.logger.log("os", "Host %s verification successful" % host)
            else:
                self.logger.loge("os", "Host %s verification failed" % host)

            self.logger.logvv("os", "Found via %s. Host %s connected to remote" % (rt_vias[0], host))
            # get neigh matching the "via" remote vtep and l3vni svi
            self.via = rt_vias[0]
            neigh = self.get_neigh(rt_vias[0], rt_ifaces[0])
            neigh_mac = macFormat1ToFormat2(neigh.mac).lower()
            if neigh.state != "NUD_NOARP":
                self.logger.loge("os", "Remote vtep neigh is not in state NOARP - %s" % rt_vias[0])
            self.logger.logvv("os", "Remote VTEP Neigh SVI - %s" % neigh.svi)
            remote_svi = self.kernel.ifaces[neigh.svi]
            if not remote_svi:
                self.logger.loge("os", "SVI %s doesn't exist" % neigh.svi)
            else:
                self.logger.logvv("os", "L3VNI SVI %s exists in the Kernel" % neigh.svi)
                self.logger.logv("os", "neigh verification successful - %s" % neigh.ip)

                #bridge fdb mac verification -
                vid_l3vni = self.kernel.get_svi_vid(remote_svi)
                self.logger.logvv("os", "L3VNI SVI vlan id - %d" % vid_l3vni)
                vxlan_iface = self.kernel.get_vni_vxlan(vid_l3vni)
                self.logger.logvv("os", "Corresponding vxlan interface - %s" % vxlan_iface.ifname)
                self.logger.logvv("os", "Check if there is a matching offload fdb entry for mac %s bridge port %s and vlan %d" % (neigh_mac, vxlan_iface.ifname, vid_l3vni))
                found, mac_masters, mac_extern_learn = self.get_mac(neigh_mac, vxlan_iface.ifname, vid_l3vni)
                if not found:
                    self.logger.loge("os", "matching mac not found - %s. Neigh verification failed" % neigh_mac)
                else:
                    self.logger.logvv("os", "Matching fdb entry for mac %s bridge port %s and vlan %d found" % (neigh_mac, vxlan_iface.ifname, vid_l3vni))
                    if mac_extern_learn:
                        self.logger.logvv("os", "Matching fdb entry for mac %s bridge port %s and vlan %d found and it is offload" % (neigh_mac, vxlan_iface.ifname, vid_l3vni))
                        if mac_masters[0] != bridge.ifname:
                            self.logger.loge_exit("os", "mac is not on the same bridge - %s" % mac_masters[0])
                        else:
                            self.logger.logvv("os", "mac is on the same bridge - %s" % mac_masters[0])
                        self.logger.logv("os", "Fdb verification successful %s" % neigh_mac)

                    self.logger.log("os", "Host %s verification successful" % host)

    def verify_common(self):
        if self.kernel is None:
            self.logger.loge_exit("os", "Kernel instance doesn't exist!")

        #SVI Verification
        self.logger.logvv("os", "Try finding SVI corresponding to the vlan %d" % self.args.vid)
        svi = self.kernel.get_l3_vlan_dev(self.args.vid)
        if svi is None:
            self.logger.loge_exit("os", "SVI not found for the vlan id %d" % self.args.vid)
        self.svi = svi
        self.logger.logvv("os", "SVI for the vlan %d found - %s" % (self.args.vid, svi.ifname))
        if not svi.is_up or not svi.is_admin_up:
            self.logger.loge("os", "SVI is not up - %s" % svi.ifname)
        else:
            self.logger.logvv("os", "SVI is up - %s" % svi.ifname)
        self.logger.logv("os", "SVI verification successful - %s" % svi.ifname)

        #Port Verification
        self.logger.logvv("os", "Try finding if Switch Port exists - %s" % self.args.port)
        swp = self.kernel.ifaces[self.args.port]
        self.swp = swp
        if swp is None:
            self.logger.loge_exit("os", "Port not found - %s" % self.args.port)
        self.logger.logvv("os", "Port found - %s" % self.args.port)
        if not swp.is_up or not swp.is_admin_up:
            self.logger.loge("os", "Port is not up - %s" % swp.ifname)
        else:
            self.logger.logvv("os", "Port is up - %s" % swp.ifname)
        if swp.vids is None:
            self.logger.loge_exit("os", "Port %s has no vlan ids configured"\
                    % port.ifname)
        if self.args.vid not in swp.vids:
            self.logger.loge_exit("os", "Vlan id %d is not configured on the Port %s"\
                    % (self.args.vid, swp.ifname))
        self.logger.logvv("os", "Vlan id %d is configured on the Port %s"\
                    % (self.args.vid, swp.ifname))
        self.logger.logv("os", "Port verification successful - %s" % swp.ifname)

        #Bridge Verification
        self.logger.logvv("os", "Try finding if Bridge for the port %s exists" % self.args.port)
        bridge = swp.bridge
        if bridge is None:
            self.logger.loge_exit("os", "Port %s is not part of any bridge" % self.args.port)
        self.bridge = bridge
        self.logger.logvv("os", "Port %s is part of bridge - %s" % (self.args.port, bridge.ifname))
        if not bridge.is_up or not bridge.is_admin_up:
            self.logger.loge("os", "Bridge is not up - %s" % bridge.ifname)
        else:
            self.logger.logvv("os", "Bridge is up - %s" % bridge.ifname)
        if bridge.vids is None:
            self.logger.loge_exit("os", "Bridge %s has no vlan ids configured"\
                    % bridge.ifname)
        if self.args.vid not in bridge.vids:
            self.logger.loge_exit("os", "Vlan id %d is not configured on the bridge %s"\
                    % (self.args.vid, bridge.ifname))
        self.logger.logvv("os", "Vlan id %d is configured on the bridge %s"\
                    % (self.args.vid, bridge.ifname))
        self.logger.logv("os", "Bridge verification successful - %s" % bridge.ifname)

        #VRF SVI Verification
        self.logger.logvv("os", "Try finding if vrf exists for the SVI - %s" % svi.ifname)
        vrf = svi.vrf
        self.vrf = vrf
        if vrf is None:
            self.logger.loge("os", "Vrf not found for SVI - %s" % svi.ifname)
        else:
            self.logger.logvv("os", "Vrf found for SVI %s - %s" % (svi.ifname, vrf.ifname))
            if not vrf.is_up or not vrf.is_admin_up:
                self.logger.loge("os", "Vrf is not up - %s" % vrf.ifname)
            else:
                self.logger.logvv("os", "Vrf is up - %s" % vrf.ifname)
                self.logger.logv("os", "Vrf verification successful - %s" % vrf.ifname)

class ClFlowTraceRouting:

    def __init__(self, args, logger):
        self.logger = logger
        self.args = args

    def verify_detail(self, cftKernel, host):

        self.logger.logv("routing", "verifying the arp-cache for %s vni %d" % (host, cftKernel.vid))
        arp_cache = {}
        cmd = ["/usr/bin/vtysh", "-c", "show evpn arp-cache vni " + str(cftKernel.vid) + " ip " + host + " json"]
        try:
            output = check_output(cmd).strip()
        except CalledProcessError:
            output = None
        if output is not None:
            try:
                arp_cache = json.loads(output)
            except ValueError:
                self.logger.loge("routing", "The json from '%s' is invalid\n%s" % (' '.join(cmd), output))
                return
            if arp_cache["type"] == "remote":
                if arp_cache["remoteVtep"] != cftKernel.via:
                    self.logger.loge("routing", "routing via %s and kernel remote vtep %s don't match" % (arp_cache["remoteVtep"], cftKernel.via))
                else:
                    self.logger.logvv("routing", "routing via %s and kernel remote vtep %s match" % (arp_cache["remoteVtep"], cftKernel.via))
            elif arp_cache["type"] == "local":
                self.logger.logvv("routing", "local arp-cache entry found for host %s" % host)
            if arp_cache["mac"] != cftKernel.neigh_mac:
                self.logger.loge("routing", "routing mac %s and kernel mac %s don't match" % (arp_cache["mac"], cftKernel.neigh_mac))
            else:
                self.logger.logvv("routing", "routing mac %s and kernel mac %s match" % (arp_cache["mac"], cftKernel.neigh_mac))
            if arp_cache["type"] == "remote" and cftKernel.neigh.state == "NUD_NOARP":
                self.logger.logvv("routing", "routing state %s and kernel state %s match" % (arp_cache["type"], cftKernel.neigh.state))
            elif arp_cache["type"] == "remote" and cftKernel.neigh.state != "NUD_NOARP":
                self.logger.loge("routing", "routing type %s and kernel state %s don't match" % (arp_cache["type"], cftKernel.neigh.state))
            elif arp_cache["type"] == "local" and cftKernel.neigh.state == "NUD_NOARP":
                self.logger.loge("routing", "routing type %s and kernel state %s don't match" % (arp_cache["type"], cftKernel.neigh.state))
            self.logger.logv("routing", "host %s verification in routing arp-cache successful" % host)
        else:
            self.logger.loge("routing", "frr arp cache verification for the host %s unsuccessful" % host)

        self.logger.logv("routing", "verifying the frr mac for %s vni %d" % (host, cftKernel.vid))
        mac_cache = {}
        cmd = ["/usr/bin/vtysh", "-c", "show evpn mac vni " + str(cftKernel.vid) + " mac " + cftKernel.neigh_mac + " json"]
        try:
            output = check_output(cmd).strip()
        except CalledProcessError:
            output = None
        if output is not None:
            try:
                mac_cache = json.loads(output)
            except ValueError:
                self.logger.loge("routing", "The json from '%s' is invalid\n%s" % (' '.join(cmd), output))
                return
            if mac_cache[cftKernel.neigh_mac]["type"] == "remote":
                if mac_cache[cftKernel.neigh_mac]["remoteVtep"] != cftKernel.via:
                    self.logger.loge("routing", "routing via %s and kernel remote vtep %s don't match" % (mac_cache[cftKernel.neigh_mac]["remoteVtep"], cftKernel.via))
                else:
                    self.logger.logvv("routing", "routing via %s and kernel remote vtep %s match" % (mac_cache[cftKernel.neigh_mac]["remoteVtep"], cftKernel.via))
            elif mac_cache[cftKernel.neigh_mac]["type"] == "local":
                self.logger.logv("routing", "host %s has local mac" % host)
            if mac_cache[cftKernel.neigh_mac]["type"] == "remote" and cftKernel.neigh.state == "NUD_NOARP":
                self.logger.logvv("routing", "routing state %s and kernel state %s match" % (mac_cache[cftKernel.neigh_mac]["type"], cftKernel.neigh.state))
            elif mac_cache[cftKernel.neigh_mac]["type"] == "remote" and cftKernel.neigh.state != "NUD_NOARP":
                self.logger.loge("routing", "routing type %s and kernel state %s don't match" % (mac_cache[cftKernel.neigh_mac]["type"], cftKernel.neigh.state))
            elif mac_cache[cftKernel.neigh_mac]["type"] == "local" and cftKernel.neigh.state == "NUD_NOARP":
                self.logger.loge("routing", "routing type %s and kernel state %s don't match" % (mac_cache[cftKernel.neigh_mac]["type"], cftKernel.neigh.state))
            self.logger.logv("routing", "host %s verification in routing mac cache successful" % host)
        else:
            self.logger.loge("routing", "frr mac cache verification for the host %s unsuccessful" % host)

        if arp_cache["type"] == "remote":
            self.logger.logv("routing", "verifying the frr matching route for %s" % host)
            frr_route = {}
            cmd = ["/usr/bin/vtysh", "-c", "show ip bgp vrf " + cftKernel.vrf.ifname + " " + host + " json"]
            try:
                output = check_output(cmd).strip()
            except CalledProcessError:
                output = None
            if output is not None:
                try:
                    frr_route = json.loads(output)
                except ValueError:
                    self.logger.loge("routing", "The json from '%s' is invalid\n%s" % (' '.join(cmd), output))
                    return
                if not frr_route:
                    self.logger.loge("routing", "no matching route found in frr for %s" % host)
                else:
                    self.logger.logvv("routing", "matching route found in frr for %s" % host)
                    if frr_route["paths"][0]["nexthops"][0]["ip"] != cftKernel.via:
                        self.logger.loge("routing", "frr route via %s and kernel remote vtep %s don't match" % (frr_route["paths"][0]["nexthops"][0]["ip"], cftKernel.via))
                    else:
                        self.logger.logvv("routing", "frr route via %s and kernel remote vtep %s match" % (frr_route["paths"][0]["nexthops"][0]["ip"], cftKernel.via))
                    self.logger.logv("routing", "frr route verification for the host %s successful" % host)
            else:
                self.logger.loge("routing", "frr route verification for the host %s unsuccessful" % host)
        else:
            self.logger.logv("routing", "frr route matching skipped for host %s as host neigh is local" % host)

    def verify(self, cftKernel):
        self.logger.logv("routing", "")
        self.logger.logv("routing", "------------ verify dip %s -------------" % self.args.dip)
        self.verify_detail(cftKernel, self.args.dip)
        self.logger.logv("routing", "")
        self.logger.logv("routing", "------------ verify sip %s -------------" % self.args.sip)
        self.verify_detail(cftKernel, self.args.sip)

class ClFlowTrace:

    def __init__(self, args, logger):
        self.args = args
        self.logger = logger
        self.cftKernel = ClFlowTraceKernel(args, logger)
        self.cftRouting = ClFlowTraceRouting(args, logger)

    def run(self):
        self.cftKernel.collect_data()
        self.cftKernel.verify_common()
        #Route, neigh and mac verification
        self.logger.logv("main", "")
        self.logger.logv("main", "------------ verify dip %s in kernel -------------" % self.args.dip)
        self.cftKernel.verify_route_neigh_mac(self.cftKernel.svi, self.cftKernel.swp, self.cftKernel.bridge,
                self.cftKernel.vrf, self.args.dip, "dip")
        #verify routing against kernel
        self.logger.logv("main", "")
        self.logger.logv("main", "------------ verify dip %s in routing -------------" % self.args.dip)
        self.cftRouting.verify_detail(self.cftKernel, self.args.dip)
        self.logger.logv("main", "")
        self.logger.logv("main", "------------ verify sip %s in kernel -------------" % self.args.sip)
        self.cftKernel.verify_route_neigh_mac(self.cftKernel.svi, self.cftKernel.swp, self.cftKernel.bridge,
                self.cftKernel.vrf, self.args.sip, "sip")
        self.logger.logv("main", "")
        self.logger.logv("main", "------------ verify sip %s in routing -------------" % self.args.sip)
        self.cftRouting.verify_detail(self.cftKernel, self.args.sip)



################################################################################
#                              MAIN                                            #
################################################################################
def parse_n_validate_cmd_line_arguments():
    parser = argparse.ArgumentParser(
        description="cl-flow-trace (BETA): verify in kernel, routing and hardware "
                    "if dst and src are reachable",
    )

    # is the name "table" semantically correct?
    parser.add_argument('table', help='Where? Evpn or Multicast?')
    option = parser.add_mutually_exclusive_group(required=False)
    option.add_argument(
        "-v", "--verbose", action='store_true',
        help="prints what the tool is doing - in summary"
    )
    option.add_argument(
        "-vv", "--very-verbose", action='store_true',
        help="prints what the tool is doing - in great detail"
    )

    parser.add_argument('--version', action='version', version='%(prog)s 1.0')
    parser.add_argument('--port', help='Via which incoming switch port?')
    #is vid optional? Range?
    parser.add_argument('--vid', type=int, help='Vlan Id (0..4096) for the incoming packet')
    parser.add_argument('--sip', help='Src IP to be traced via specified vlan and specified switch port')
    parser.add_argument('--dip', help='Dst IP to be traced')
    args = parser.parse_args()

    logger = ClFlowTraceLogger(args)
    if args.table != "evpn":
        logger.loge_exit("CONFIG", "Only evpn is supported")
        
    if args.vid < 0 or args.vid > 4096:
        logger.loge_exit("CONFIG", "Vlan Id should be in range 0..4096")

    if args.sip and args.vid is None or args.port is None:
        logger.loge_exit("CONFIG", "Please provide vlan id and port for sip verification")
    try:
        sip = IPAddress(args.sip)
    except ValueError:
        logger.loge_exit("CONFIG", "sip addr format not correct")
    try:
        sip = IPAddress(args.dip)
    except ValueError:
        logger.loge_exit("CONFIG", "dip addr format not correct")

    return args


def main():
    if os.geteuid():
        stderr.write("%(prog)s must run with root privileges, try running with sudo\n")
        exit(1)

    args = parse_n_validate_cmd_line_arguments()
    print args

    logger = ClFlowTraceLogger(args)
    cft = ClFlowTrace(args, logger)
    cft.run()
    return 0

if __name__ == "__main__":
    main()
