#!/usr/bin/python3

#-------------------------------------------------------------------------------
#
# Copyright 2014, 2020 Cumulus Networks, inc  all rights reserved
#
#-------------------------------------------------------------------------------

#
#   Import the necessary modules
#
try:
    import gc
    import resource 
    import platform
    import ctypes
    import argparse
    import clag.clagnetlink
    import clag.clagthread
    import clag.cmdsrv
    import clag.fdbsync
    import clag.healthcheck
    import clag.lacpsync
    import clag.mdbsync
    import clag.neighsync
    import clag.reloadconfig as reloadconfig
    import clag.threadmonitor
    import clag.util as util
    import clag.vlansync
    import clag.vxlansync
    import clag.mroutesync
    import clag.clagdPeerXchange_pb2 as peerXchange
    import clag.clagdStream_lib as stream_lib
    import copy
    import clag.clagSdnotify as clagSdnotify
    import daemon
    import fcntl
    import glob
    import ipaddress
    import json
    import lockfile
    import logging
    import logging.handlers
    import nlmanager.nlmanager
    import nlmanager.nlpacket
    import os
    import queue
    import re
    import select
    import signal
    import socket
    import struct
    import subprocess
    import sys
    import syslog
    import threading
    import time
    import traceback
    import xml.etree.ElementTree as ET
    import clag.clagdcsu as clagdcsu
    import datetime

except ImportError as e:
    raise ImportError (str(e) + "- required module not found")

#
#   Define constants
#
_InterfacePath          = "/sys/class/net/"

#
#   Global Variables
#
# Except for the initialization routine these are read-only.
Log                     = None
Parser                  = None
Daemon                  = None
Intf                    = None
Cmd                     = None

FdbSync                 = None
NeighSync               = None
LacpSync                = None
MdbSync                 = None
VxLanSync               = None
HealthCheck             = None
ClagNetLink             = None
MrouteSync              = None
VlanSync                = None
DataSyncs               = []
csu_client              = None
#
#   Events for waking up threads
#
stopEvent               = clag.clagthread.Event()
exitEvent               = None
peerlinkEvent           = clag.clagthread.Event()

#
#   Misc - These are miscellaneous global variables. It is possible that these
#   should be protected by a threading lock, but they are not.
#
serverSock              = None
serverSockPair          = None
inputSocks              = []
ourPeerIp               = None
clientSock              = None
clientSockPair          = None
clientSockInit          = False
keep_going              = True
peerSendQueue           = queue.Queue()
watchintv = 0
mstpClagState           = None
peerConfigMismatch      = False
vxlanAnycastMismatch    = False
peerIpMismatch          = False
sysMacMismatch          = False
remoteIpMismatchOnPeer  = False
bridgePriorityMismatch  = False
sendMsgSeq              = 0
system_mac              = None
global_nlm              = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_GLOBAL_ID)
sysTimeWatchDog         = None


#-------------------------------------------------------------------------------
#
#   Threads - The clagd daemon is implemented with six threads, each handling
#   a specfic task. Communication between the threads is handled using events
#   and the global data structures, above. The threads are:
#
#   CollectSysInfo - Periodically runs, getting current infromation on this  
#   system, like LACP partner MACs and dynamically learned MACs.  Signals the
#   PeerSend thread after reading data by setting the peerSendEvent.  When   
#   the LACP information changes the FindDualConnectedIfs thread is signaled 
#   by setting the newLacpDataEvent.                                         
#
#   PeerSend - Waits for the CollectSysInfo thread to retrieve data, opens a    
#   socket to the peer, sends our information to the peer, and closes the       
#   socket.                                                                     
#
#   PeerRecv - Periodically attempts to create a socket with the peer to allow  
#   the peer to connect with us. Once successful, waits for the peer to open
#   a connection. Reads data from that connection, decodes the data, and closes
#   that connection. Then signals the PeerTimeout thread to let it know that
#   the peer is still alive, and if the data changed from the last time we
#   received it from the peer, signals the FindDualConnectedIfs thread or 
#   UpdateMacsFromPeer thread.
#
#   PeerTimeout - Waits for a peerRecvEvent from the PeerRecv thread or times
#   out waiting for that event. If the event is received, but we had previously
#   timed out (peer has just become alive) then change the sysMac of all bonds
#   to the common value. If we timed out, and had previously not timed out,
#   undo everything and act like a standalone switch.
#
#   FindDualConnectedIfs - Waits for a newLacpDataEvent from either the PeerRecv 
#   or CollectSysInfo threads. Compares the partner MAC addresses we collected
#   from our bonds to those received from our peer. Any new matches place the
#   bond in the dual-connected state. Any former matches that are no longer
#   matching, take the bond out of the dual connected state.
#
#   UpdateMacsFromPeer - Waits for a newMacDataEvent from the PeerRecv thread.
#   Compare the MACs we've learned to the MACs the peer has learned and add or
#   remove MACs from our bonds to keep in sync with the peer's dynamically
#   learned MAC addresses.
#
#-------------------------------------------------------------------------------

def SendInitialXmlSyncDone():
    if Intf.isPeerAlive() and not Parser.syncDoneToPeer:
        # Once initial data sync is done we send a sync_done notification to
        # the peer. This is used by the peer as a handshake-done indication.
        Parser.syncDoneToPeer = True
        clagDataAttr = {'version' : Parser._ClagDataVersion}
        root = ET.Element("clag_data", clagDataAttr)
        ET.SubElement(root, "sync_done")
        xmlStr = ET.tostring(root)
        peerSendQueue.put((None, xmlStr))
        util.queueSetMaxLen('peerSendQueue')
        Log.log("Initial XML data sync to peer done.")

def SendInitialSyncDone():
    if Intf.isPeerAlive() and not Parser.syncDoneToPeer:
        # Once initial data sync is done we send a sync_done notification to
        # the peer. This is used by the peer as a handshake-done indication.
        Parser.syncDoneToPeer = True
        msgHdr = peerXchange.Header()
        msgHdr.type = peerXchange.Header.MessageType.Value('CLAG_PERIODIC_SYNC')
        msgData = peerXchange.ClagPeerSync()
        msgData.version = Parser._ClagDataVersion
        msgData.syncDone = True
        Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "TXing initial sync data: %s" % str(msgData))
        msgHdr.data = msgData.SerializeToString()
        peerSendQueue.put((msgHdr, None))
        util.queueSetMaxLen('peerSendQueue')
        Log.log("Initial data sync to peer done.")

def CollectSysInfo():
    '''
    This function operates as an independent thread of the script. It retrieves
    information about the system which is used by CLAG and possibly sent to
    the peer switch. There are two types of information collected:
        1. The 802.3ad partner MAC addresses of each bond -> ourLacpDB
        2. The dynamically learned MAC addresses of dual connected bonds -> ourMacDB
    '''
    Log.log_verbose(Log._LOG_DEBUG_COLLECT_SYS, "Beginning execution of the thread CollectSysInfo")
    nlm = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_COLLECT_ID)
    while keep_going:
        # Collect all of the local switch's information
        for sync in DataSyncs:
            sync.CollectLocalInfo(nlm)

        # Send this information to our peer
        if Parser and Parser.peerProtoBuf:
            MakeProtoBufMessage()
            if Intf.isPeerAlive():
                for sync in DataSyncs:
                    sync.MakeProtoBufMessage()
        else:
            MakeXmlMessage()

        if Parser and Parser.peerProtoBuf:
            SendInitialSyncDone()
        else:
            SendInitialXmlSyncDone()

        # Indicate that we are alive
        Daemon.TouchKeepAlive()

        Log.log_verbose(Log._LOG_DEBUG_COLLECT_SYS, "Waiting %d seconds before continuing execution of the thread CollectSysInfo" % (Parser.getCurrLacpPoll(),))
        stopEvent.wait(Parser.getCurrLacpPoll())
        stopEvent.clear()
        Log.log_verbose(Log._LOG_DEBUG_COLLECT_SYS, "Executing the thread CollectSysInfo")

    Log.log_verbose(Log._LOG_DEBUG_COLLECT_SYS, "Finished execution of the thread CollectSysInfo")


def PeerSend():
    '''
    This function is an independent thread of the script. It opens a socket to
    the peer and sends the LACP data retrieved by the CollectSysInfo thread 
    to the peer, formatted in XML. The socket is then closed and the thread goes
    to sleep waiting for more data from the CollectSysInfo thread.
    '''
    global clientSock
    global clientSockInit
    global sendMsgSeq
    Log.log_verbose(Log._LOG_DEBUG_INFORM_PEER, "Beginning execution of the thread PeerSend")

    clientSock = None
    while keep_going and not Parser.sendGoodbye:

        # Get the next message to send to the peer
        Log.log_verbose(Log._LOG_DEBUG_INFORM_PEER, "Waiting for an event before continuing execution of the thread PeerSend")
        sendMsg = None
        sendStr = None
        queueEmpty = False
        try:
            (sendMsg, sendStr) = peerSendQueue.get(True, Parser.getPeriodicRun())
        except queue.Empty:
            queueEmpty = True
        Log.log_verbose(Log._LOG_DEBUG_INFORM_PEER, "Executing the thread PeerSend.")

        # Create a socket to the peer switch if necessary
        if not Parser.remotePeerIp:
            if Parser.args.peerIp == 'linklocal':
                remotePeerIp = None
                try:
                    subprocess.check_output(['/bin/ping6', '-L', '-c', '1', 'ff02::1', '-I', Parser.args.peerIf])
                    neighbors = subprocess.check_output(['/bin/ip', 'n', 's', 'dev', Parser.args.peerIf]).decode()
                    remotePeerIp = re.search('^(fe80:\S+)', neighbors, re.MULTILINE).group(1)
                except (subprocess.CalledProcessError, AttributeError) as e:
                    util.queueClear(peerSendQueue)
                    pass
                if remotePeerIp:
                    Log.log("Using %s as remote Peer IP" % remotePeerIp)
                    Parser.remotePeerIp = remotePeerIp
                else:
                    try:
                        subprocess.check_output(['/bin/ping6', '-L', '-c', '1', 'ff02::1', '-I', Parser.args.peerIf]).decode()
                    except:
                        util.queueClear(peerSendQueue)
                        pass
            else:
                Parser.remotePeerIp = Parser.args.peerIp
        if not queueEmpty and keep_going and Parser.remotePeerIp and clientSock is None:
            for (family, socktype, proto, canonname, sockaddr) in \
                socket.getaddrinfo(Parser.remotePeerIp, Parser.args.peerPort, socket.AF_UNSPEC,
                                   socket.SOCK_STREAM, 0, socket.AI_PASSIVE):

                try:
                    clientSock = socket.socket(family, socktype, proto)
                except socket.error:
                    clientSock = None
                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Unable to create socket for %s:%d" % (Parser.remotePeerIp, Parser.args.peerPort))
                    util.queueClear(peerSendQueue)
                    continue
                # Attempt to connect to the peer
                try:
                    clientSockInit = True
                    clientSock.setsockopt(socket.SOL_SOCKET, 25, bytes(Parser.args.peerIf, "utf-8"))
                    # We need to set TCP_NODELAY option as well as TCP_MAXSEG option to handle socket getting stuck
                    # and packets not able to process on receive side: RM-3061322 RM-3070808
                    # Refer to https://www.techrepublic.com/article/tcp-ip-options-for-high-performance-data-transmission/
                    # In my experiments, we found that the TCP socket fails to receive any data when we have communication
                    # between a hardnode and a softnode just after the hardnode sends a TCP data of size more than 9K.
                    # When we configure the TCP_MAXSEG to a static value (preventing auto negotiation of the MTU), then we do
                    # not see this issue. The value of 5000 was chosen just from experimentation where even setting it to 1000
                    # was avoiding the issue of socket getting stuck.
                    clientSock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
                    clientSock.setsockopt(socket.SOL_TCP, socket.TCP_MAXSEG, 5000)
                    clientSock.settimeout(Parser.args.peerConnect)
                    clientSock.connect(sockaddr)
                    clientSock.settimeout(Parser.args.sendTimeout)
                    clientSock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, Parser.args.sendBufSize)
                    clientSockInit = False
                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "The client socket for sending data to the peer is connected.")
                except Exception as e:
                    if Parser:
                        Parser.CloseClientSock()
                    clientSockInit = False
                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Unable to connect socket to %s:%d: %s " % (Parser.remotePeerIp, Parser.args.peerPort, str(e)))
                    util.queueClear(peerSendQueue)
                    continue

                Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Opened a socket to the peer switch")
                Intf.SetPeerDeathPending(False)
                Parser.NotifyPeerCommChange()
                break

        while not queueEmpty and clientSock is not None and (sendMsg or sendStr):
            if sendMsg:
                sendMsg.seq = sendMsgSeq
                sendMsg = sendMsg.SerializeToString()
                Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "Sending data to peer, length: %d, seq: %d" % (len(sendMsg), sendMsgSeq))
                sockInfo = stream_lib.StreamInfo(clientSock, None)
                # Send the data to the peer
                sockInfo.build_write_data(sendMsg)
                try:
                    numSent = clientSock.send(sockInfo.writeBuf)
                    sendMsgSeq += 1
                except Exception as e:
                    Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "There was an error sending data to the peer switch: %s" % str(e))
                    numSent = 0
                if numSent == 0:
                    if Parser:
                        Parser.CloseClientSock()
                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Error sending data to peer, connection broken.")
                    util.queueClear(peerSendQueue)
                if sendMsgSeq > 99999:
                    sendMsgSeq = 0

            else:
                # Format the data in XML
                Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "Sending data to peer (%d): %s" % (len(sendStr), sendStr[:100]))
                sendStr += bytes(chr(0), "utf-8")
                while sendStr:
                    try:
                        numSent = clientSock.send(sendStr)
                    except Exception as e:
                        Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "There was an error sending data to the peer switch: %s" % str(e))
                        numSent = 0
                    if numSent == 0:
                        numSent = len(sendStr)
                        if Parser:
                            Parser.CloseClientSock()
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Error sending data to peer, connection broken.")
                        util.queueClear(peerSendQueue)
                    sendStr = sendStr[numSent:]

            try:
                (sendMsg, sendStr) = peerSendQueue.get(True, 0.001)
            except queue.Empty:
                queueEmpty = True



    Log.log_verbose(Log._LOG_DEBUG_INFORM_PEER, "Finished execution of the thread PeerSend")


def PeerRecv():
    '''
    This function is an independent thread of the script. It creates a socket,
    serverSock, and accepts connections on that socket. It receives the information
    send through those connections and signals the PeerTimeout thread to let it
    know that the peer is alive. If the information we receive has changed, it 
    signals the LacpDualConnectedIfs thread.
    '''
    global serverSock
    global inputSocks
    global ourPeerIp
    global clientSockPair
    global serverSockPair

    Log.log_verbose(Log._LOG_DEBUG_RECV_PEER, "Beginning execution of the thread PeerRecv")

    clientSockPair, serverSockPair = socket.socketpair()

    # Initially, all bonds have unique system IDs
    nlm = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_RECV_ID)
    if not Parser.args.backupIp:
        Intf.SetAllClagBondsSysMac(nlm, "00:00:00:00:00:00", [Parser.args.peerIf])

    myPeerSockInfo = {}
    while keep_going:
        # If we don't have a socket accepting connections from the peer, create one.
        if serverSock is None:
            ourPeerIp = Intf.GetIpAddressInSameNet(Parser.args.peerIf, Parser.args.peerIp)
            Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "The IP address of the %s interface is %s." % (Parser.args.peerIf, ourPeerIp))

            if ourPeerIp:
                for (family, socktype, proto, canonname, sockaddr) in \
                    socket.getaddrinfo(ourPeerIp, Parser.args.peerPort, socket.AF_UNSPEC,
                                       socket.SOCK_STREAM, 0, socket.AI_PASSIVE):

                    try:
                        serverSock = socket.socket(family, socktype, proto)
                    except socket.error as msg:
                        serverSock = None
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Unable to create socket for %s:%d" % (ourPeerIp, Parser.args.peerPort))
                        continue

                    try:
                        serverSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                        serverSock.setsockopt(socket.SOL_SOCKET, 25, bytes(Parser.args.peerIf, "utf-8"))
                        serverSock.bind(sockaddr)
                        serverSock.listen(5)
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Server socket is open for accepting connections from the peer.")
                    except socket.error as msg:
                        serverSock.close()
                        serverSock = None
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Unable to bind socket to %s:%d (%s) %s" % (ourPeerIp, Parser.args.peerPort, sockaddr, msg))
                        continue
                    except AttributeError:
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Unable to find server socket")
                        serverSock = None
                        continue

                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Server socket to %s:%d was created" % (ourPeerIp, Parser.args.peerPort))
                    break

        if serverSock is not None:
            inputSocks  = [serverSock, serverSockPair]
            while inputSocks and keep_going and serverSock:

                # Wait until there is a socket to read or an error
                Log.log_verbose(Log._LOG_DEBUG_RECV_PEER, "Waiting for something to happen")

                # If the user supplied a timeout, use it
                currTimeout = Parser.getPeerTimeout()
                Parser.peerTimeoutStart = int(os.times()[4])

                try:
                    (readable, writable, exceptional) = select.select(inputSocks, [], inputSocks, currTimeout)
                except (select.error, socket.error) as e:
                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "An error occurred: %s" % (e,))
                    (readable, writable, exceptional) = ([],[],[])
                else:
                    if keep_going and (readable, writable, exceptional) != ([],[],[]):
                        if not Intf.isPeerLinkDown:
                            # Did the peer just become alive?
                            PeerAliveProc(nlm)
                    else:
                        # Has the peer just gone silent?
                        if Intf.isPeerAlive():
                            if keep_going:
                                Log.log("Peer communication timeout. No data has been received from the peer for %d seconds." % (currTimeout,))
                            PeerIsNotActiveGraceful(nlm)

                # An error occured on a socket, gracefully shut it down
                for errSock in exceptional:
                    Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Closing a socket because of an error")
                    inputSocks.remove(errSock)
                    errSock.close()
                    if errSock in readable:
                        readable.remove(errSock)
                    if errSock in myPeerSockInfo:
                        del myPeerSockInfo[errSock]
                        if Intf.isPeerAlive():
                            PeerIsNotActiveGraceful(nlm)
                    if errSock is serverSock:
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "   The socket was the server socket")
                        serverSock = None

                if keep_going:
                    for readSock in readable:
                        if readSock is serverSock:
                            # Accept a new socket connection
                            Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "Accepting a new peer connection to receive data from the peer.")
                            (newSock, clientAddr) = readSock.accept()
                            # Add the new socket to our list, and clear its receive data buffer
                            if not Parser.readyToAcceptConnFromPeer():
                                Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "Closing stale Connections")
                                srvConn = [serverSock, serverSockPair]
                                stale_conn = set(inputSocks)- set(srvConn)
                                for sock in stale_conn:
                                     if sock in inputSocks:
                                         inputSocks.remove(sock)
                                     if sock in myPeerSockInfo:
                                         del myPeerSockInfo[sock]
                                     if sock in readable:
                                         readable.remove(sock)
                                     sock.close()
                            sockInfo  = stream_lib.StreamInfo(newSock, sockaddr)
                            inputSocks.append(newSock)
                            myPeerSockInfo[newSock] = sockInfo
                        else:
                            # Read data from this socket
                            Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "Reading data from the peer")
                            sockInfo = myPeerSockInfo.get(readSock, None)
                            myPeerData = 0
                            myPeerDataBytes = 0
                            if Parser and Parser.peerProtoBuf:
                                try:
                                    readlen = sockInfo.get_read_data_len()
                                    myPeerData = readSock.recv(readlen)
                                except:
                                    myPeerData = 0
                            else:
                                try:
                                    myPeerDataBytes = readSock.recv(2048)
                                except:
                                    myPeerDataBytes = 0

                            if myPeerData:
                                myPeerMsg = sockInfo.proc_read_data(myPeerData)
                                if Intf.GetPeerDeathPending():
                                    # ignore the data - peer conn. is gone; we
                                    # don't want to accidentally the peer as alive
                                    Log.log_debug(Log._LOG_DEBUG_RECV_PEER,\
                                                  "Ignoring data from the peer")
                                elif myPeerMsg:
                                    Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "PeerData length: (%d)" % (len(myPeerMsg),))
                                    ParseProtoBufMessage(nlm, myPeerMsg)
                            elif myPeerDataBytes:
                                try:
                                    myPeerDataStr = myPeerDataBytes.decode("utf-8")
                                except:
                                    myPeerDataStr = 0
                                if Intf.GetPeerDeathPending() or not myPeerDataStr:
                                    Log.log_debug(Log._LOG_DEBUG_RECV_PEER,\
                                             "Ignoring data from the peer")
                                else:
                                    # Add the data read to this socket's receive buffer
                                    try:
                                        myPeerSockInfo[readSock] += myPeerDataStr
                                    except:
                                        myPeerSockInfo[readSock] = myPeerDataStr
                                    while chr(0) in myPeerSockInfo[readSock]:
                                        inEnd = myPeerSockInfo[readSock].find(chr(0))
                                        inCmd = myPeerSockInfo[readSock][:inEnd]
                                        myPeerSockInfo[readSock] = myPeerSockInfo[readSock][inEnd+1:]
                                        Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "PeerData: (%d) %s" % (len(inCmd), inCmd[:100],))
                                        ParseXmlMessage(nlm, inCmd)
                            else:
                                # If no data was available, the socket has been closed, end of message.
                                Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Closing a peer receive socket because there's nothing to read")
                                inputSocks.remove(readSock)
                                readSock.close()
                                myPeerSockInfo.pop(readSock, None)
                                if Intf.isPeerAlive():
                                    PeerIsNotActiveGraceful(nlm)

            if keep_going and not serverSock:
                for inputSock in inputSocks:
                    if inputSock is not serverSockPair:
                        myPeerSockInfo.pop(inputSock, None)
                        inputSock.close()
                        Log.log_debug(Log._LOG_DEBUG_SOCKET_INFO, "Closing a peer receive socket because the server socket is closed.")

        if keep_going:
            Log.log_verbose(Log._LOG_DEBUG_RECV_PEER, "Waiting %d seconds before continuing execution of the thread PeerRecv" % (Parser.args.peerConnect,))
            stopEvent.wait(Parser.args.peerConnect)
            stopEvent.clear()
            Log.log_verbose(Log._LOG_DEBUG_RECV_PEER, "Executing the thread PeerRecv.")

    clientSockPair.close()
    serverSockPair.close()
    Log.log_verbose(Log._LOG_DEBUG_RECV_PEER, "Finished execution of the thread PeerRecv")

def PeerAliveProc(nlm):
    if not Intf.isPeerAlive():
        Intf.peerDownLock.acquire()
        Log.log("The peer switch is active.")
        Parser.ClearRemoteIpMismatchOnPeer()
        Parser.ClearPeerConfigMismatch()
        Intf.SetPeerAlive(nlm, True)
        Parser.ListenersStateUpdate("up")
        Intf.SetAllClagBondsSysMac(nlm, Parser.args.sysMac, [Parser.args.peerIf])
        if not Intf.initHandShakeDone:
            if not Parser.isPeerStaticClagStateAware():
                # Peer versions less than 1.1.0 are not capable of 
                # sending initial sync done message so move to handshake
                # done right away.
                Intf.SetInitHandShakeDone(nlm)
        Intf.SetPeerDeathPending(False)
        Log.log("Using current peerlink role: %s" % Parser.clagRole)
        Parser.ListenersRoleUpdate()
        if MrouteSync:
            MrouteSync.UpdateLocalZebraStatus()
            MrouteSync.ZebraNotifyClagStatusUpdate()
        Intf.peerDownLock.release()

def PeerLinkChange():
    '''
    This function is an independent thread of the script. It checks the state
    of the interface(s) to the peer switch. If the interface(s) to the peer
    switch go down, then the peer switch is marked as no longer active.
    '''
    Log.log_verbose(Log._LOG_DEBUG_PEER_LINK, "Beginning execution of the thread PeerLinkChange")
    nlm = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_CHANGE_ID)
    prevPeerIfDown = True;
    prevAllClagBondDown = False

    while keep_going:

        # Determine if the interfaces to the peer are down
        peerIfDown = Intf.isPeerLinkDown()
        allClagBondDown = Intf.allClagBondDownEvent

        # Has the peer link just gone down?
        if not prevPeerIfDown and peerIfDown:
            PeerIsNotActiveGraceful(nlm)

        if not prevAllClagBondDown and allClagBondDown:
            Intf.PeerLinkChangeDelayedUpdates(nlm)

        # Has the peer link just come up?
        if prevPeerIfDown and not peerIfDown:
            ConfigurePeerLearning(nlm, True)
            peerLink = Parser.args.peerIf
            if Intf.isSubIfName(peerLink):
                peerLink = Intf.GetLogicalMastersOfSubIf(peerLink)[0]
            Log.log_debug(Log._LOG_DEBUG_FSM, "toListeners: add peer-link = %s" % peerLink)
            Cmd.CmdGetAllOutput("add peer-link = %s" % (peerLink,))
            if MrouteSync:
                MrouteSync.ZebraNotifyClagStatusUpdate()
        prevPeerIfDown = peerIfDown
        prevAllClagBondDown = allClagBondDown

        Log.log_verbose(Log._LOG_DEBUG_PEER_LINK, "Waiting %d seconds or peerlink event before continuing execution of the thread PeerLinkChange" % (Parser.getPeerLinkPoll(),))
        peerlinkEvent.wait(Parser.getPeerLinkPoll())
        peerlinkEvent.clear()
        Log.log_verbose(Log._LOG_DEBUG_PEER_LINK, "Executing the thread PeerLinkChange.")

    Log.log_verbose(Log._LOG_DEBUG_PEER_LINK, "Finished execution of the thread PeerLinkChange")

def SysTimeWatchDog():
    Log.log_verbose(Log._LOG_DEBUG_SYS_TIME_WD, "Beginning execution of the thread SysTimeWatchDog")
    prev = datetime.datetime.now()
    while keep_going:
        time.sleep(10)
        now = datetime.datetime.now()
        time_diff = now - prev
        # On system timestamp update, the python thread apis which use the system timestamp
        # may hang in indefinite wait state. Hence waking up all the threads on system time update.
        if (int(time_diff.total_seconds()) not in range(8, 13)):
            try:
                Log.log("Systemd Time Stamp Changed from %s to %s" % (prev, now))
                stopEvent.set()
                peerlinkEvent.set()
                # Setting the flag sysTimeUpd pending for clagnetlink and healthcheck module.
                # This is required as we just need to wake up the threads, and not do the regular
                # event processing. So the flag is used to skip the event processing.
                if ClagNetLink:
                    ClagNetLink.sysTimeUpdPending = True
                    ClagNetLink.NeighListenEvent.set()
                    ClagNetLink.DelayNeighNotifStartEvent.set()
                    ClagNetLink.kernelDumpReadyEvent.set()
                if FdbSync:
                    FdbSync.stopEvent.set()
                if HealthCheck:
                    HealthCheck.sysTimeUpdPending = True
                    HealthCheck.helloReloadEvent.set()
                    HealthCheck.ForceInitDelayExpiry("System Timestamp Update")
                    HealthCheck.initDelayTimerStartEvent.set()
                    HealthCheck.helloTxEvent.set()
                if LacpSync:
                    LacpSync.stopEvent.set()
                    LacpSync.newLacpDataEvent.set()
                if MdbSync:
                    MdbSync.stopEvent.set()
                if MrouteSync:
                    MrouteSync.stopEvent.set()
                if NeighSync:
                    NeighSync.stopEvent.set()
                    NeighSync.SyncDelayEvent.set()
                if VlanSync:
                    VlanSync.peerReplyEvent.set()
                if VxLanSync:
                    VxLanSync.stopEvent.set()
                    VxLanSync.newVxLanDataEvent.set()
            except:
                Log.log_error(" Failed to notify clag events on system timestamp change")
                pass
        prev = now



#-------------------------------------------------------------------------------
#
#   Thread Support Routines - These functions are called by the above threads.
#
#-------------------------------------------------------------------------------

#-------------------------------------------------------------------------------
#
#   clagdata XML format - The messages sent between peers use XML to encode the
#   data. This provides a convienient and extensible way to transfer data
#   between peers. There are many libraries available which can encode and
#   parse XML-formatted data. The XML format used is shown by this example:
#
#       <clag_data version="0.1.0">
#         <clagid uniqueId="44:38:39:00:11:16" priority=32768 role=Primary />
#         <update_interval units="seconds">4</update_interval>
#         <mgmt_if ipaddr="10.0.1.5" />
#         <sync_done />
#         <i8023ad system_id="00:00:00:01:01:01">
#           <bond ad_partner_mac="00:02:00:00:00:09" name="bond4" portId="8.003">
#             <mac vlan="101">00:02:00:00:00:09</mac>
#             <multicast vlan="100" dev="br0">234.10.10.10</multicast>
#             <vlan id="101" name="bond4.101" />
#             <vlan id="102" name="bond4.102" />
#             <vlan id="103" name="bond4.103" />
#           </bond>
#           <bond ad_partner_mac="00:02:00:00:00:0b" name="bond5" portId="8.005" />
#           <bond ad_partner_mac="00:02:00:00:00:24" name="bond0" portId="8.009" />
#           <bond ad_partner_mac="00:02:00:00:00:03" name="bond1" portId="8.001">
#             <mac vlan="101">00:02:00:00:00:03</mac>
#             <vlan id="101" name="bond1.101"/>
#           </bond>
#           <bond ad_partner_mac="00:02:00:00:00:05" name="bond2" portId="8.004" />
#             <vlan id="101" name="bond2.101" />
#           <bond ad_partner_mac="00:02:00:00:00:07" name="bond3" portId="None" />
#           <neighbor ipaddr="10.1.0.4" dev="br100" mac="00:02:00:00:00:08" flag="reachable" bond="bond4" vlan="100" />
#           <neighbor ipaddr="10.1.0.12" dev="br10" mac="00:02:00:00:00:28" flag="stale" bond="bond3" vlan=None />
#         </i8023ad>
#       </clag_data>
#
#   The root element tag is "clag_data" and the required "version" attribute
#   defines the format of the child elements. Different major versions indicate 
#   entirely different formats. New minor versions indicate additions of data
#   while retaining all previous data. New sub-versions indicate cosmeic changes
#   which don't affect the data itself.
#
#   The sender of the XML data indicates how frequently it sends the data with
#   the update_interval element. Receivers can use this to know how long before
#   it should expect to wait before receiving the next data from the sender.
#
#   IEEE 802.3ad (LAG) information is contained as children of the i8023ad 
#   element. The system_id attribute contains the ad_actor_system that the
#   sender has assigned to all of its bonds.
#
#   The "bond" elements contain a "name" attribute which is the ifname of
#   the bond on the sender's system. It also contains the ad_partner_mac
#   which is the MAC address of the partner of the bond (used by the receiver 
#   to determine if the bond is dual connected).
#
#   Bonds which are dual connected may have "mac" sub elements, which are the
#   MAC addresses which have been dynamically learned on the interface. If the
#   bond has vlan sub-interfaces, then a vlan attribute will include the VLAN
#   ID associated with the mac addres.
#
#   Bonds can also have "vlan" sub elements, which are the VLANs configured
#   on that bond. 
#
#-------------------------------------------------------------------------------

def PrintElement(node, level):
    Log.log_debug(Log._LOG_DEBUG_COLLECT_SYS, "%s%s: %s: %s" % (" " * (level*3), node.tag, node.text, str(node.attrib)))
    for child in node.findall('*'):
        PrintElement(child, level+1)

def MakeXmlMessage():
    '''
    This function uses this switch's LACP information and clagd state and
    formulates an XML message which will be sent to the peer.
    '''
    clagDataAttr = {'version' : Parser._ClagDataVersion}
    if Parser.sendGoodbye:
        clagDataAttr['goodbye'] = 'yes'
    root = ET.Element("clag_data", clagDataAttr)
    ET.SubElement(root, "capabilities", {"redirectEnable" : str(Parser.args.redirectEnable), \
                                    "redirect2Enable" : str(Parser.args.redirect2Enable)})
    ET.SubElement(root, "clagid", {"priority" : str(Parser.clagId[0]), "uniqueId" : Parser.clagId[1], "role" : Parser.clagRole})
    ET.SubElement(root, "update_interval", {"units" : "seconds"}).text = str(Parser.getCfgLacpPoll())
    if serverSock is not None and ourPeerIp is not None:
        ET.SubElement(root, "mgmt_if", {"ipaddr" : ourPeerIp})
    if Parser.syncDoneToPeer:
        ET.SubElement(root, "sync_done")
    ET.SubElement(root, "i8023ad", {"system_id" : Parser.args.sysMac})
    ET.SubElement(root, "vxlan_config", {"anycast_ip" : str(Parser.args.vxlanAnycast)})
    if Intf.isPeerAlive():
        for sync in DataSyncs:
            sync.EncodeXmlInfo(root)
    #PrintElement(root, 0)
    xmlStr = ET.tostring(root)
    if root:
        del root
    peerSendQueue.put((None, xmlStr))
    util.queueSetMaxLen('peerSendQueue')

def MakeProtoBufMessage():
    msgHdr = peerXchange.Header()
    msgHdr.type = peerXchange.Header.MessageType.Value('CLAG_PERIODIC_SYNC')
    msgData = peerXchange.ClagPeerSync()
    msgData.version = Parser._ClagDataVersion
    msgData.redirectEnable = Parser.args.redirectEnable
    msgData.redirect2Enable = Parser.args.redirect2Enable
    msgData.priority = Parser.clagId[0]
    msgData.uniqueId = Parser.clagId[1]
    msgData.role = Parser.clagRole
    msgData.updateInterval = Parser.getCfgLacpPoll()
    msgData.updateIntervalUnit = "seconds"
    msgData.systemId = Parser.args.sysMac
    if serverSock and ourPeerIp:
        msgData.ourIpAddress = ourPeerIp
    if Parser.syncDoneToPeer:
        msgData.syncDone = True
    if Parser.sendGoodbye:
        msgData.goodbye = True
    if Parser.args.vxlanAnycast:
        msgData.anycastIp = str(Parser.args.vxlanAnycast)
    if Parser.bridgePriority:
        msgData.bridgePriority = Parser.bridgePriority
    Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "TXing periodic data: %s" % str(msgData))
    msgHdr.data = msgData.SerializeToString()
    peerSendQueue.put((msgHdr, None))
    util.queueSetMaxLen('peerSendQueue')

def ParseProtoBufPeriodicMessage(nlm, data):
    verStr = data.version
    # Is the version something we understand?
    (peerMajVer, peerMinVer, peerSubVer) = verStr.split(".")
    (myMajVer,   myMinVer,   mySubVer)   = Parser._ClagDataVersion.split(".")
    if int(peerMajVer) > int(myMajVer):
        Log.log_error("The peer is running a version I do not understand. My version: %s, Peer version: %s" % (Parser._ClagDataVersion, verStr))
        Daemon.SignalAllThreadsToStop()
        return
    if Parser.peerDataVersion != verStr:
        Parser.peerDataVersion = verStr

    # Is there a goodbye attribute?
    if data.goodbye:
        Intf.SetPeerDeathPending(True)
        Parser.ClearConfigMismatch(nlm)
        Intf.SetClagRole(nlm, Parser._ROLE_PRIMARY, "peer sent goodbye")
        if HealthCheck:
            HealthCheck.ForceInitDelayExpiry("goodbye received")
        return

    # Peer indicated that he has synced all the initial data
    if data.syncDone and not Parser.syncDoneFromPeer:
        Log.log("Initial data sync from peer done.")
        Parser.SetSyncDoneFromPeer(True)
        Intf.SetInitHandShakeDone(nlm)
        # Check if any of the dormant bonds can be moved to up without
        # involving the peer
        Intf.UpdatePendingDormantBonds(nlm)
        return

    peerRedirectEnable = data.redirectEnable
    peerRedirect2Enable = False if not Parser.isPeerRedirect2Capable() else data.redirect2Enable
    if peerRedirectEnable != Parser.args.redirectEnable:
        Log.log_warn("Mismatch in redirectEnable flag between Clag Peers")
    if peerRedirect2Enable != Parser.peerRedirect2Enable:
        Parser.peerRedirect2Enable = peerRedirect2Enable
        ConfigureBridgeBackupPort(nlm, Parser.isRedirectEnabled())

    Parser.peerId[0] = data.priority
    Parser.peerId[1] = data.uniqueId
    Parser.peerRole = data.role
    PeerAliveProc(nlm)
    if Parser.clagId[0] < Parser.peerId[0] or (Parser.clagId[0] == Parser.peerId[0] and Parser.clagId[1] < Parser.peerId[1]):
        Intf.SetClagRole(nlm, Parser._ROLE_PRIMARY, "elected")
    else:
        Intf.SetClagRole(nlm, Parser._ROLE_SECONDARY, "elected")

    if data.updateInterval:
        Parser.setPeerRate(data.updateInterval)

    peerIpAddr = data.ourIpAddress
    ourPeerIp = Intf.GetIpAddressInSameNet(Parser.args.peerIf, Parser.args.peerIp)
    if peerIpAddr:
        if Parser.remotePeerIp not in [None, peerIpAddr] or peerIpAddr == ourPeerIp:
            Parser.SetPeerIpMismatch(nlm, peerIpAddr, ourPeerIp)
    else:
        return

    if not data.systemId or data.systemId != Parser.args.sysMac:
        Parser.SetSysMacMismatch(nlm, data.systemId)
    else:
        Parser.ClearSysMacMismatch(nlm)

    if data.anycastIp:
        if data.anycastIp != Parser.args.vxlanAnycast:
            Parser.SetAnycastMismatch(nlm, data.anycastIp)
        else:
            Parser.ClearAnycastMismatch(nlm)

    if data.bridgePriority:
        if Parser.bridgePriority and data.bridgePriority != Parser.bridgePriority:
            Parser.SetBridgePriorityMismatch(nlm, data.bridgePriority)
        else:
            Parser.ClearBridgePriorityMismatch(nlm)

def ParseProtoBufMessage(nlm, data):
    # Ignore the the message if the peer link is down (enqueued prior to
    # link going down).
    if Intf.isPeerLinkDown():
        return

    msgHdr = peerXchange.Header()
    msgHdr.ParseFromString(data)
    seq = msgHdr.seq
    msgData = None
    if msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_PERIODIC_SYNC'):
        msgData = peerXchange.ClagPeerSync()
        msgData.ParseFromString(msgHdr.data)
        ParseProtoBufPeriodicMessage(nlm, msgData)
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_BOND_SYNC'):
        msgData = LacpSync.ParseProtoBufMessage(msgHdr)
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_VLAN_SYNC'):
        msgData = VlanSync.ParseProtoBufMessage(msgHdr)
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_VXLAN_SYNC'):
        msgData = VxLanSync.ParseProtoBufMessage(msgHdr)
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_FDB_SYNC'):
        msgData = FdbSync.ParseProtoBufMessage(msgHdr)
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_NEIGH_SYNC'):
        msgData = NeighSync.ParseProtoBufMessage(msgHdr) if NeighSync else None
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_MDB_SYNC'):
        msgData = MdbSync.ParseProtoBufMessage(msgHdr)
    elif msgHdr.type == peerXchange.Header.MessageType.Value('CLAG_MROUTE_SYNC'):
        msgData = MrouteSync.ParseProtoBufMessage(msgHdr)
    Log.log_debug(Log._LOG_DEBUG_RECV_PEER, "RXed Data: seq %d - %s." % (seq, str(msgData)))

def ParseXmlMessage(nlm, xmlStr):
    '''
    This function takes XML received from the peer and parses it.
    '''

    # If we're in the process of quitting, don't do any processing
    if Parser.sendGoodbye:
        return

    # Convert the XML string into an ElementTree element
    try:
        root = ET.fromstring(xmlStr)
    except ET.ParseError:
        Log.log_error("The data received from the peer is not in a valid format:\n%s" % (xmlStr,))
        return

    # Is the root element named properly?
    if root is None or root.tag != "clag_data":
        Log.log_error("The data received from the peer is not in a valid format:\n%s" % (xmlStr,))
        Daemon.SignalAllThreadsToStop()
        return

    # Is there a version attribute with the root element?
    verStr = root.get("version")
    if verStr is None:
        Log.log_error("The data received from the peer does not include a version:\n%s" % (xmlStr,))
        Daemon.SignalAllThreadsToStop()
        return

    # Is the version something we understand?
    (peerMajVer, peerMinVer, peerSubVer) = verStr.split(".")
    (myMajVer,   myMinVer,   mySubVer)   = Parser._ClagDataVersion.split(".")
    if int(peerMajVer) > int(myMajVer):
        Log.log_error("The peer is running a version I do not understand. My version: %s, Peer version: %s" % (Parser._ClagDataVersion, verStr))
        Daemon.SignalAllThreadsToStop()
        return

    if Parser.peerDataVersion != verStr:
        Parser.peerDataVersion = verStr

    # Is there a goodbye attribute with the root element?
    byeStr = root.get("goodbye")
    if byeStr == 'yes': 
        Intf.SetPeerDeathPending(True)
        Parser.ClearConfigMismatch(nlm)
        Intf.SetClagRole(nlm, Parser._ROLE_PRIMARY, "peer sent goodbye")
        if HealthCheck:
            HealthCheck.ForceInitDelayExpiry("goodbye received")
        return

    # Ignore the rest of the message if the peer link is down (enqueued prior to
    # link going down).
    if Intf.isPeerLinkDown():
        return
     
    capabilities = root.find("capabilities")
    if capabilities is not None or not Parser.isPeerRedirect2Capable():
        if capabilities is not None:
            peerRedirectEnableStr  = capabilities.get("redirectEnable")
            peerRedirectEnable = True if peerRedirectEnableStr == "True" else False
            if peerRedirectEnable != Parser.args.redirectEnable:
                Log.log_warn("Mismatch in redirectEnable flag between Clag Peers")
            peerRedirect2EnableStr = capabilities.get("redirect2Enable")
            peerRedirect2Enable = True if peerRedirect2EnableStr == "True" else False
        elif not Parser.isPeerRedirect2Capable():
            peerRedirect2Enable = False
        if peerRedirect2Enable != Parser.peerRedirect2Enable:
            Parser.peerRedirect2Enable = peerRedirect2Enable
            if Parser.isRedirectEnabled():
                ConfigureBridgeBackupPort(nlm, True)
            else:
                ConfigureBridgeBackupPort(nlm, False)
    
    clagIdNode = root.find('clagid')
    if clagIdNode is not None:
        Parser.peerId[0] = int(clagIdNode.get('priority'))
        Parser.peerId[1] = clagIdNode.get('uniqueId')
        Parser.peerRole = clagIdNode.get('role')
        PeerAliveProc(nlm)
        if Parser.clagId[0] < Parser.peerId[0] or (Parser.clagId[0] == Parser.peerId[0] and Parser.clagId[1] < Parser.peerId[1]):
            Intf.SetClagRole(nlm, Parser._ROLE_PRIMARY, "elected")
        else:
            Intf.SetClagRole(nlm, Parser._ROLE_SECONDARY, "elected")

    # Was a transmit interval supplied?
    interval = root.find('update_interval')
    if interval is not None:
        # If a units attribute was supplied it must be "seconds"
        units = interval.get('units')
        if units is not None and units != 'seconds':
            Log.log_warn("The units for the update_interval: %s, are not recognized. Skipping the interval" % (units,))
        else:
            # Get the peer's send rate. XXX: This checking can be tightened up.
            Parser.setPeerRate(int(interval.text))

    # Was a mgmt IP address supplied?
    mgmt_if = root.find('mgmt_if')
    if mgmt_if is not None:
        # Get the IP address
        peerIpAddr = mgmt_if.get('ipaddr')
        ourPeerIp = Intf.GetIpAddressInSameNet(Parser.args.peerIf, Parser.args.peerIp)
        if peerIpAddr is not None:
            if Parser.remotePeerIp not in [None, peerIpAddr] or peerIpAddr == ourPeerIp:
                Parser.SetPeerIpMismatch(nlm, peerIpAddr, ourPeerIp)
        else:
            return

    # Peer indicated that he has synced all the initial data
    sync_done = root.find('sync_done')
    if sync_done is not None:
        if not Parser.syncDoneFromPeer:
            Log.log("Initial data sync from peer done.")
            Parser.SetSyncDoneFromPeer(True)
            Intf.SetInitHandShakeDone(nlm)
            # Check if any of the dormant bonds can be moved to up without 
            # involving the peer
            Intf.UpdatePendingDormantBonds(nlm)

    # Go through all of the i8023ad elements
    i8023ad = root.find('i8023ad')
    if i8023ad is not None:
        # Is the peer's sysMac the same as ours?
        sysId = i8023ad.get('system_id')
        if sysId is None or sysId != Parser.args.sysMac:
            Parser.SetSysMacMismatch(nlm, sysId)
        else:
            Parser.ClearSysMacMismatch(nlm)

    # Go through all of the vxlan_config elements
    vxlan_config = root.find('vxlan_config')
    if vxlan_config is not None:
        peerAnycast = vxlan_config.get('anycast_ip')
        if peerAnycast == "None":
            peerAnycast = None
        # Is the peer's vxlan anycast IP the same as ours?
        if peerAnycast != Parser.args.vxlanAnycast:
            Parser.SetAnycastMismatch(nlm, peerAnycast)
        else:
            Parser.ClearAnycastMismatch(nlm)

    # Let each of the data syncs process their portion of the XML
    for sync in DataSyncs:
        sync.DecodeXmlInfo(root)

    if root:
        del root

    return

def ConfigureBridgeBackupPort(nlm, enable, ignoreLocks=False):
    if not ignoreLocks:
        Intf.bridgeAttrLock.acquire()
    clagBonds = Parser.GetClagBondDB()
    masterVlanMap = Intf.GetMasterIfVlanMap()
    clagBondSet = set(clagBonds)
    for clagBond in clagBonds:
        clagBondSet |= set(masterVlanMap.get(clagBond, {}).values())
    Intf.SetBridgeBackupPort(nlm, clagBondSet, enable)
    if not ignoreLocks:
        Intf.bridgeAttrLock.release()

def ConfigurePeerLearning(nlm, enable, ignoreLocks=False):
    if not ignoreLocks:
        Intf.bridgeAttrLock.acquire()
    masterVlanMap = Intf.GetMasterIfVlanMap()
    peerMasters = Intf.GetLogicalMastersOfSubIf(Parser.args.peerIf)
    if not peerMasters:
        peerMasters = [Parser.args.peerIf]
    allPeerIfs = list(peerMasters)
    for peerMaster in peerMasters:
        allPeerIfs += list(masterVlanMap.get(peerMaster, {}).values())
    if enable or \
        (not enable and Parser.args.peerlinkLearnEnable):
        Intf.SetBridgeLearning(nlm, allPeerIfs, not enable)
    if not ignoreLocks:
        Intf.bridgeAttrLock.release()


def PeerIsNotActive(nlm, peerSwitchDead):
    Intf.peerDownLock.acquire()
    Log.log("The peer switch is no longer active")
    Parser.setPeerRate(None)
    Parser.syncDoneToPeer = False
    Parser.SetSyncDoneFromPeer(False)
    Intf.initHandShakeDone = False
    Intf.SetPeerAlive(nlm, False)
    if Parser:
        Parser.CloseClientSock()

    global serverSock
    if serverSock:
        myServerSock = serverSock
        serverSock = None
        try:
            myServerSock.shutdown(socket.SHUT_RDWR)
            myServerSock.close()
        except:
            pass

    for sync in DataSyncs:
        sync.ClearPeerInfo()

    if HealthCheck and HealthCheck.IsBackupRoleAvailable():
        Log.log("Using current backup role: %s" % HealthCheck.GetBackupRole())

    if not Intf.waitAllBondDownEvent.is_set():
        Parser.ListenersStateUpdate("down")
    if MrouteSync:
        MrouteSync.ZebraNotifyClagStatusUpdate()

    # Now that the peer is dead bonds stuck in a dormant state can be moved to 
    # up
    Intf.UpdatePendingDormantBonds(nlm)
    Intf.peerDownLock.release()
    Parser.ClearConfigMismatch(nlm)

def PeerIsNotActiveGraceful(nlm):
    if not keep_going or not HealthCheck or not HealthCheck.GetBackupActive() or Intf.GetBackupRole() == Parser._ROLE_PRIMARY or not Intf.isPeerAlive():
        if HealthCheck and not HealthCheck.GetBackupActive() and  Parser.clagRole != Parser._ROLE_PRIMARY:
            Intf.SetAllClagBondsSysMac(nlm, "00:00:00:00:00:00", [Parser.args.peerIf])
        PeerIsNotActive(nlm, True)
        return

    # we need to check if peer switch is still accessible via the backup link
    if not Intf.GetPeerDeathPending():
        Intf.SetPeerDeathPending(True)
        HealthCheck.HelloRapidDetectionSetup()

def signal_handler(signum, frame):
    nlm = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_SIG_HNDL_INIT)
    def switchd_state_starting():
        Log.log("Switchd Process is starting")
        if Parser:
            Parser.switchdState = ClagParser._SWITCHD_STARTING

    def switchd_state_ready():
        Log.log("SwitchdState is ready")
        nlmsgs = bytes()
        if Parser: 
            Parser.switchdState = ClagParser._SWITCHD_READY
            if HealthCheck and not Parser.args.vm:
                HealthCheck.goodbyeSent = False
                if not HealthCheck.initDelayTimerStartEvent.is_set():
                    HealthCheck.initDelayTimerStartEvent.set()
                    for intf in list(Parser.GetClagIntfDB().values()):
                        if intf.intfType == Parser._CLAG_INTF_TYPE_BOND:
                            nlmsgs += intf.UpdateProtoDownFlags(nlm,  \
                            intf._PROTO_DOWNF_INIT_DELAY, intf._PROTO_DOWNF_NONE, batch=True)
                    if nlmsgs:
                        nlm.tx_nlpacket_raw(nlmsgs)

    def switchd_state_dead():
        Log.log("Switchd Process is dead")
        if Parser:
            Parser.switchdState = ClagParser._SWITCHD_DEAD
            if HealthCheck and not Parser.args.vm:
                HealthCheck.goodbyeSent = True
                Log.log("Sending goodBye over backup")
                HealthCheck.HelloSendGoodbye()

    sigrt_map = {
        signal.SIGRTMIN   : switchd_state_starting,
        signal.SIGRTMIN+1 : switchd_state_ready,
        signal.SIGRTMIN+2 : switchd_state_dead,
    }
    if signum == signal.SIGHUP:
        reopen()
    elif signum == signal.SIGTERM:
        if exitEvent is None:
            do_exit(None, 0, True)
        else:
            exitEvent.set()
    elif signum == signal.SIGRTMIN+3:
        Log.log("System Reboot/ Networking Service Restart Initiated")
        Parser.systemRebootTriggered = True
    elif signum in sigrt_map:
        switchd_sighndl = sigrt_map.get(signum)
        return switchd_sighndl()

def reopen():
    if Log:
        handlerStr = Log.getHandlerStr()
        if handlerStr not in ["syslog", "stdout"]:
            try:
                Log.setHandler(handlerStr)
                Log.log("Continuing logging after SIGHUP (possibly due to log rotation)")
            except IOError:
                pass

def do_exit(nlm, status=0, ignoreLocks=False):
    '''
    Clean up and exit.
    '''
    global Cmd
    global HealthCheck
    global ClagNetLink

    if Parser.systemRebootTriggered:
        Log.log("System Reboot/ Networking Service Restart In Progress")
        try:
            if Intf and Parser and nlm:
                if VxLanSync and Parser.args.vxlanAnycast:
                    Log.log("Deleting the anycast Ip")
                    Parser.args.vxlanAnycastDeleted = True
                    Intf.DelIpAddress(nlm, "lo", str(Parser.args.vxlanAnycast), 32)
            Log.log("Updating switchd fuse node")
            with open("/cumulus/switchd/ctrl/clag_shutdown_duallinks", "w") as f:
                f.write(str(1))
        except IOError:
            pass
    else:
        try:
            with open("/cumulus/switchd/config/vxlan/local_ip", "w") as f:
                f.write("0.0.0.0 0.0.0.0")
        except IOError:
            pass

    if VxLanSync:
        # Setting this early to avoid intf refresh due to role change
        VxLanSync.keep_going = False
    # Set protodown on clag bonds and vxlan devices before sending goodbye
    if Intf and Parser and nlm:
        Intf.SetProtoDownOnExit(nlm, ignoreLocks)

    # If the peer is alive, send a goodbye message
    if Intf and Intf.isPeerAlive():
        root = ET.Element("clag_data", {'version' : Parser._ClagDataVersion, 'goodbye' : 'yes'})
        xmlStr = ET.tostring(root)
        msgHdr = peerXchange.Header()
        msgHdr.type = peerXchange.Header.MessageType.Value('CLAG_PERIODIC_SYNC')
        msgData = peerXchange.ClagPeerSync()
        msgData.version = Parser._ClagDataVersion
        msgData.goodbye = True
        # Flush the send queue
        queueEmpty = False
        while not queueEmpty:
            try:
                peerSendQueue.get_nowait()
            except queue.Empty:
                queueEmpty = True
        Parser.sendGoodbye = True
        # Since the peer will now be primary, change our role to secondary
        if Parser and Parser.clagRole != Parser._ROLE_SECONDARY:
            Parser.clagRole = Parser._ROLE_SECONDARY
            if Log:
                Log.log("Role is now secondary; sent goodbye to peer")
            if Cmd:
                Log.log_debug(Log._LOG_DEBUG_FSM, "toListeners: add clag-role = %s" % (str(Parser.clagRole)))
                Cmd.CmdGetAllOutput("add clag-role = %s" % (str(Parser.clagRole),), ignoreLocks)
            if MrouteSync:
                MrouteSync.ZebraNotifyClagStatusUpdate()
        # Send the goodbye message
        if Parser and Parser.peerProtoBuf:
            Log.log_debug(Log._LOG_DEBUG_INFORM_PEER, "TXing goodbye data: %s" % str(msgData))
            msgHdr.data = msgData.SerializeToString()
            peerSendQueue.put((msgHdr, None))
        else:
            peerSendQueue.put((None, xmlStr))
        util.queueSetMaxLen('peerSendQueue')

    if FdbSync:
        FdbSync.ClearPeerInfo()

    if HealthCheck:
        HealthCheck.goodbyeSent = True
        HealthCheck.HelloSendGoodbye()

    # Wait for a short time so goodbye messages can go out
    time.sleep(0.5)

    if Intf and Parser and nlm:
        if Parser.isRedirectEnabled():
            ConfigureBridgeBackupPort(nlm, False, ignoreLocks)
        ConfigurePeerLearning(nlm, False, ignoreLocks)
        Intf.ClearDormantMode(nlm, ignoreLocks)

    if Daemon:
        Daemon.SignalAllThreadsToStop()
    for thread in Threads:
        if Threads[thread][1]:
            Threads[thread][1].join()

    if csu_client:
        csu_client.keep_going = False
        if csu_client.pubSubThread:
            csu_client.pubSubThread.join()
        if csu_client.monitorOperStatusThread:
            csu_client.monitorOperStatusThread.join()

    for sync in DataSyncs:
        sync.JoinThreads()
        del sync

    if Cmd:
        Cmd.JoinThreads()
    del Cmd

    if HealthCheck:
        HealthCheck.JoinThreads()
        del HealthCheck

    if ClagNetLink:
        ClagNetLink.JoinThreads()
        del ClagNetLink

    if Daemon:
        if Daemon.started and Daemon.pid_file:
            os.unlink(Daemon.pid_file)
            Daemon.lock.release()

    if Log:
        if status == 0:
            Log.log("clean exit")
        else:
            Log.log_error("exit with status %d" % status)
    sys.exit(status)



#-------------------------------------------------------------------------------
#
#   This dictionary contains the threads which we run. The key is the name of
#   the thread and the value is an array. The first elemenet is the address of
#   the function which implements the thread, and the second is the thread
#   object for this thread (filled in when the thread is created).
#
#-------------------------------------------------------------------------------

def CollectSysInfoT():
    try:
        CollectSysInfo()
    except:
        Daemon.DumpTraceback()

def PeerSendT():
    try:
        Log.log("[%d]Init PeerSendT" % (Parser.getTid()))
        PeerSend()
    except:
        Daemon.DumpTraceback()

def PeerRecvT():
    try:
        Log.log("[%d]Init PeerRecvT" % (Parser.getTid()))
        PeerRecv()
    except:
        Daemon.DumpTraceback()

def PeerLinkChangeT():
    try:
        Log.log("[%d]Init PeerlinkChangeT" % (Parser.getTid()))
        PeerLinkChange()
    except:
        Daemon.DumpTraceback()

def SysTimeWatchDogT():
    try:
        Log.log("[%d]Init SysTimeWatchDogT" % (Parser.getTid()))
        SysTimeWatchDog()
    except:
        Daemon.DumpTraceback()

Threads = {
    "CollectSysInfo"       : [CollectSysInfoT,      None],
    "PeerSendThread"       : [PeerSendT,            None],
    "PeerRecvThread"       : [PeerRecvT,            None],
    "PeerLinkChange"       : [PeerLinkChangeT,      None],
}


#-------------------------------------------------------------------------------
#
#   Error handling
#
#-------------------------------------------------------------------------------

class ClagdError(RuntimeError):
    '''
    Standard exception for all our runtime errors.
    The only argument is a string.
    '''
    pass

class ArgumentParsingError(Exception):
    '''
    Exception for argparse errors.
    '''
    pass


#-------------------------------------------------------------------------------
#
#   Daemon handling code
#
#-------------------------------------------------------------------------------

class ClagDaemon:
    '''
    Process management for a daemon.
    An instance should be created as early as possible.
    '''

    def __init__(self, name = None):
        '''
        name is the program name (defaulting to basename(argv[0]))
        '''
        if name is None:
            name = os.path.basename(sys.argv[0])
        self.name = name
        self.tick = 0
        self.pid_file = os.path.abspath("/var/run/%s.pid" % self.name)
        self.lock = lockfile.FileLock(self.pid_file)
        self.started = False

    def start(self):
        '''
        Start running.
        Check for other instances of this program and create pid file.
        This should be called after early initialization (like
        argument parsing), before any serious work.
        '''
        global watchintv
        try:
            self.write_pidfile()
        except Exception as e:
            raise ClagdError("Unable to write to PID file - %s." % (str(e),))
            do_exit(None, 4)
        self.started = True

        # the clag period for calling the watchdog is 2 seconds
        # (default_lacp_poll), and is rarely changed, and never
        # to large values, so no need to verify that the watchdog
        # interval is sufficiently longer than the period.
        watchintv = int(os.getenv('WATCHDOG_USEC', '0')) / 1000000

    def isClagAlreadyRunning(self):
        '''
        Check if another instance of clagd is already running. Check the PID file
        and if the PID file contents point to a valid running clagd instance.
        '''
        running = False
        try:
            with open(self.pid_file) as f:
                clag_pid = f.readline().strip()
            with open("/proc/" + clag_pid + "/cmdline") as f:
                cmdline = f.readline().strip().split('\x00')[1]
                if cmdline == "/usr/sbin/clagd":
                    running = True
        except (IOError, ValueError, IndexError):
            pass

        return running

    def write_pidfile(self):
        pid = str(os.getpid())
        try:
            f = open(self.pid_file, 'w')
        except:
            self.lock.release()
            raise ClagdError("Cannot open pid file %s" % self.pid_file)
        f.write("{0}\n".format(pid))
        f.flush()
        self.pidfd = f

    def DumpTraceback(self):
        (exc_type, exc_value, exc_traceback) = sys.exc_info()
        err = "".join(traceback.format_exception(exc_type, exc_value,
                                                 exc_traceback))
        if Log:
            Log.log_error("unhandled exception:")
            for line in err.split('\n'):
                Log.log_error("    %s" % (line,))
        else:
            print("unhandled exception:")
            for line in err.split('\n'):
                print(("    %s" % (line,)))
        self.SignalAllThreadsToStop()

    def TouchKeepAlive(self):
        global watchintv
        if watchintv:
            pass
            clagSdnotify.sd_notify(0, bytes("WATCHDOG=1", "utf-8"))
        try:
            # The file alive code should be removed as soon as
            # we are sure that no test or other code requires it.
            aliveFile = "/var/run/%s.alive" % (self.name,)
            with open(aliveFile, 'a'):
                os.utime(aliveFile, None)
        except:
            pass

    def SignalAllThreadsToStop(self):
        '''
        This function wakes up all threads which are waiting on a signal so that
        they can realize that they should stop execution.
        '''
        global keep_going
        global serverSock
        keep_going = False

        if serverSock is not None:
            myServerSock = serverSock
            serverSock = None
            myServerSock.shutdown(socket.SHUT_RDWR)

        # send data to our server socket pair to force select() to return in PeerRcvT
        try:
            clientSockPair.sendall(b'0')
        except:
            pass

        for sync in DataSyncs:
            sync.ClearPeerInfo()
            sync.SignalThreads()
        if Cmd is not None:
            Cmd.SignalThreads()
        if HealthCheck:
            HealthCheck.SignalThreads()
        if ClagNetLink:
            ClagNetLink.SignalThreads()

        try:
            peerSendQueue.put_nowait((None, None))
            util.queueSetMaxLen('peerSendQueue')
        except queue.Full:
            pass
        Intf.waitAllBondDownEvent.set()
        peerlinkEvent.set()
        stopEvent.set()
        if exitEvent is not None:
            exitEvent.set()



#-------------------------------------------------------------------------------
#
#   Log handling code
#
#-------------------------------------------------------------------------------

class HostAddFilter(logging.Filter):
    hostname = socket.gethostname()

    def filter(self, record):
        record.hostname = HostAddFilter.hostname
        return True


class Logger:

    _LOG_DEBUG_HOST_EVENTS  = 0x00000001
    _LOG_DEBUG_DAEMON       = 0x00000002
    _LOG_DEBUG_COLLECT_SYS  = 0x00000004
    _LOG_DEBUG_INFORM_PEER  = 0x00000008
    _LOG_DEBUG_RECV_PEER    = 0x00000010
    _LOG_DEBUG_PEER_TIMEOUT = 0x00000020
    _LOG_DEBUG_LACP_SYNC    = 0x00000040
    _LOG_DEBUG_PARSER       = 0x00000080
    _LOG_DEBUG_NEIGH_SYNC   = 0x00000200
    _LOG_DEBUG_FDB_SYNC     = 0x00000400
    _LOG_DEBUG_CMD_SERVER   = 0x00000800
    _LOG_DEBUG_PEER_LINK    = 0x00001000
    _LOG_DEBUG_MDB_SYNC     = 0x00002000
    _LOG_DEBUG_HEALTH_CHECK = 0x00004000
    _LOG_DEBUG_FSM          = 0x00008000
    _LOG_DEBUG_NETLINK      = 0x00010000
    _LOG_DEBUG_VXLAN_SYNC   = 0x00020000
    _LOG_DEBUG_INTF         = 0x00040000
    _LOG_DEBUG_RELOAD       = 0x00080000
    _LOG_DEBUG_SOCKET_INFO  = 0x00100000
    _LOG_DEBUG_MROUTE_SYNC  = 0x00200000
    _LOG_DEBUG_THREAD_MONITOR  = 0x00400000
    _LOG_DEBUG_CSU           = 0x00800000
    _LOG_DEBUG_SYS_TIME_WD   = 0x04000000

    debug_flags_dict = {
                'lacp_sync':            0x00000040,
                'parser':               0x00000080,
                'neigh_sync':           0x00000200,
                'fdb_sync':             0x00000400,
                'cmd_server':           0x00000800,
                'collect_sys':          0x00000004,
                'peerlink_tx':          0x00000008,
                'peerlink_rx':          0x00000010,
                'mdb_sync':             0x00002000,
                'healthcheck':          0x00004000,
                'fsm':                  0x00008000,
                'netlink':              0x00010000,
                'vxlan_sync':           0x00020000,
                'intf':                 0x00040000,
                'reload':               0x00080000,
                'socket':               0x00100000,
                'mroute_sync':          0x00200000,
                'thread_monitor':       0x00400000,
                'csu':                  0x00800000,
                'sys_time_wd':          0x04000000
    }

    #
    #   Default logging values
    #
    default_quiet           = False
    default_debug           = 0
    default_verbose         = False
    default_handlerStr      = "syslog"
    syslog_format           = "clagd[%(process)d]: %(message)s"
    file_format             = "%(asctime)s %(hostname)s clagd[%(process)d]: %(message)s"
    file_date               = "%Y-%m-%dT%H:%M:%S.000000%z "

    def __init__(self):
        self.logger  = logging.getLogger('clagd')
        self.logger.addFilter(HostAddFilter())
        self.handler = None
        self.setHandler(Logger.default_handlerStr)
        self.quiet   = Logger.default_quiet
        self.debug   = Logger.default_debug
        self.verbose = Logger.default_verbose
        self.setLogLevel()

    def setDebug(self, debug):
        self.debug = debug
        self.setLogLevel()

    def getDebugFlags(self):
        flagsList = []
        for k,v in list(self.debug_flags_dict.items()):
            debugBits = Parser.GetNonZeroBits(self.debug)
            for bit in debugBits:
                if v == bit:
                    flagsList.append(k)
        return flagsList

    def setDebugFlags(self, debug):
        self.debug += self.debug_flags_dict[debug]
        self.setLogLevel()

    def clearDebugFlags(self, debug = 0):
        if debug:
            self.debug -= self.debug_flags_dict[debug]
        else:
            self.debug = 0
        self.setLogLevel()

    def setQuiet(self, quiet):
        self.quiet = quiet
        self.setLogLevel()

    def setVerbose(self, verbose):
        self.verbose = verbose
        self.setLogLevel()

    def setHandler(self, handlerStr):
        if self.handler:
            self.logger.removeHandler(self.handler)
        self.handlerStr = handlerStr
        formatStr = Logger.syslog_format
        dateStr = None
        if handlerStr == "syslog":
            facility = logging.handlers.SysLogHandler.LOG_DAEMON
            self.handler = logging.handlers.SysLogHandler(address="/dev/log", facility=facility)
        elif handlerStr == "stdout":
            self.handler = logging.StreamHandler()
            formatStr = Logger.file_format
            dateStr = Logger.file_date
        else:
            self.handler = logging.FileHandler(handlerStr)
            formatStr = Logger.file_format
            dateStr = Logger.file_date
        self.formatter = logging.Formatter(formatStr, dateStr)
        self.handler.setFormatter(self.formatter)
        self.logger.addHandler(self.handler)

    def setLogLevel(self):
        if self.quiet:
            self.logger.setLevel(logging.CRITICAL)
        elif self.debug != 0:
            self.logger.setLevel(logging.DEBUG)
        else:
            self.logger.setLevel(logging.INFO)

    def getDebug(self):
        return self.debug

    def getQuiet(self):
        return self.quiet

    def getVerbose(self):
        return self.verbose

    def getHandlerStr(self):
        return self.handlerStr

    def log(self, *args, **kwargs):
        #self.logger.info(args, kwargs)
        self.logger.info(''.join(args))

    def log_debug(self, debugBit, *args, **kwargs):
        if self.debug & debugBit:
            #self.logger.debug(args, kwargs)
            self.logger.debug('[' + threading.current_thread().name + '] ' + ''.join(args))

    def log_verbose(self, debugBit, *args, **kwargs):
        if self.verbose and self.debug & debugBit:
            #self.logger.info(args, kwargs)
            self.logger.info('[' + threading.current_thread().name + '] ' + ''.join(args))

    def log_error(self, *args, **kwargs):
        #self.logger.error(args, kwargs)
        self.logger.error(''.join(args))

    def log_warn(self, *args, **kwargs):
        #self.logger.warning(args, kwargs)
        self.logger.warning(''.join(args))

    def log_crit(self, *args, **kwargs):
        #self.logger.critical(args, kwargs)
        self.logger.critical(''.join(args))


#-------------------------------------------------------------------------------
#       
#   CLAG interface manager
#           
#-------------------------------------------------------------------------------
class ClagIntf:
    '''
    utility class to represent a single CLAG intf/bond
    ''' 
    # when clagd comes up all clag bonds are place in a protoDown state. This
    # is cleared when the initial handshake complete or once the reload timer 
    # fires
    _PROTO_DOWNF_NONE = 0
    _PROTO_DOWNF_INIT  = (1 << 0)
    _PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE  = (1 << 1)
    # as part of clagd exit handling all clag bonds are placed in a protoDown 
    # state (this flag can only be set; never cleared)
    _PROTO_DOWNF_SHUTDOWN  = (1 << 2)
    _PROTO_DOWNF_SINGLE_VXLAN = (1 << 3)
    _PROTO_DOWNF_NO_ANYCAST   = (1 << 4)
    _PROTO_DOWNF_ANYCAST_MISMATCH   = (1 << 5)
    _PROTO_DOWNF_PEER_IP_MISMATCH   = (1 << 6)
    _PROTO_DOWNF_SYSMAC_MISMATCH    = (1 << 7)
    # delay bringup by a blanked X seconds
    _PROTO_DOWNF_INIT_DELAY = (1 << 8)
    _PROTO_DOWNF_PARTNER_MAC_MISMATCH = (1 << 9)
    _PROTO_DOWNF_DUPLICATE_PARTNER_MAC = (1 << 10)
    _PROTO_DOWNF_BRIDGE_PRIORITY_MISMATCH = (1 << 11)
    # During CSU keep the intf down
    _PROTO_DOWNF_CSU = (1 << 12)
    
    # some of the proto_down reason codes are ignored on the primary as
    # we want to keep the pimary bonds up and running most of the time
    _PROTO_DOWNF_USED_ON_PRIMARY = (_PROTO_DOWNF_INIT |\
                                    _PROTO_DOWNF_SHUTDOWN |\
                                    _PROTO_DOWNF_SINGLE_VXLAN |\
                                    _PROTO_DOWNF_NO_ANYCAST |\
                                    _PROTO_DOWNF_INIT_DELAY |\
                                    _PROTO_DOWNF_PARTNER_MAC_MISMATCH |\
                                    _PROTO_DOWNF_DUPLICATE_PARTNER_MAC | \
                                    _PROTO_DOWNF_CSU)

    def __init__(self, nlm, intfName, intfType, clagId = 0):
        self.intfName = intfName
        self.intfType = intfType
        self.clagId = clagId
        self.conflicts = Parser._CLAG_INTF_CONFLICT_NONE
        self.protoDownFlags = 0 if Intf.initHandShakeDone or \
            (HealthCheck and HealthCheck.reloadExpired()) else self._PROTO_DOWNF_INIT

        # Do not set vxlan_single protodown flag for vxlan devices
        # after reload timeout when peerlink/backup links are down
        if self.intfType == Parser._CLAG_INTF_TYPE_VXLAN:
            if Intf.isPeerAlive() or not HealthCheck or \
               HealthCheck.GetBackupActive() or not HealthCheck.reloadExpired():
                self.protoDownFlags |= self._PROTO_DOWNF_SINGLE_VXLAN

        if self.intfType == Parser._CLAG_INTF_TYPE_BOND and\
           HealthCheck.initDelayBringup():
            self.protoDownFlags |= self._PROTO_DOWNF_INIT_DELAY

        if csu_client and self.intfType == Parser._CLAG_INTF_TYPE_BOND and \
            csu_client.isSlaveProtoDown() == True:
            self.protoDownFlags |= self._PROTO_DOWNF_CSU

        self.kernProtoDown = None

        # add clag bond to an id dict
        if self.clagId:
            Parser.bondClagIdDB[self.clagId] = self.intfName

        if self.intfType == Parser._CLAG_INTF_TYPE_BOND:
            if (Parser.args.backupIp and Intf.GetIfIndex(self.intfName) is not None) \
                    or (Intf.isPeerAlive() and not Parser.args.backupIp):
                Intf.SetBondSysMac(nlm, system_mac, self.intfName)
            self.SetClagIntfModeDormant(nlm, dormant=True)
        self.UpdateIntfProtoDown(nlm)

        # initHandShakeDone is set once the peer has sent his
        # peer db over or after reload timer has fired. Once
        # this attr is set we can start conflict evaluation
        # on clag bond add itself
        if Intf.ReadyToRunClagConflictChecks() and LacpSync and self.intfType == Parser._CLAG_INTF_TYPE_BOND:
            setConflicts = LacpSync.EvaluateClagIntfConflicts(self.intfName, self.clagId)
            self.UpdateConflicts(setConflicts, 0)

    def CleanUp(self, nlm):
        if self.intfType == Parser._CLAG_INTF_TYPE_BOND:
            if self.clagId in Parser.bondClagIdDB:
                del Parser.bondClagIdDB[self.clagId]
            self.SetClagIntfModeDormant(nlm, dormant=False)

            Intf.SetBondSysMac(nlm, '00:00:00:00:00:00', self.intfName)
            self.SetIntfKernProtoDown(nlm, protoDown=0)

    def SetNewClagId(self, nlm, clagId):
        if self.clagId == clagId:
            return

        if self.clagId in Parser.bondClagIdDB:
            del Parser.bondClagIdDB[self.clagId]

        self.clagId = clagId

        # add clag bond to an id dict
        if self.clagId:
            Parser.bondClagIdDB[self.clagId] = self.intfName

        self.SetClagIntfModeDormant(nlm, dormant=True)
        self.UpdateIntfProtoDown(nlm)

        # Reset the conflicts
        if Intf.ReadyToRunClagConflictChecks() and LacpSync and self.intfType == Parser._CLAG_INTF_TYPE_BOND:
            setConflicts = LacpSync.EvaluateClagIntfConflicts(self.intfName, self.clagId)
            self.UpdateConflicts(setConflicts, Parser._CLAG_INTF_CONFLICT_LACP_ALL & ~setConflicts)

    def GetProtoDownRole(self): 
        if not Intf.isPeerAlive() and HealthCheck and\
                     HealthCheck.IsBackupRoleAvailable():
            role = HealthCheck.GetBackupRole()
        else:
            role = Parser.clagRole

        return role

    def GetProtoDown(self): 
        role = self.GetProtoDownRole()

        if role == Parser._ROLE_PRIMARY:
            stateFlags = (self.protoDownFlags & self._PROTO_DOWNF_USED_ON_PRIMARY)
        else:
            stateFlags = self.protoDownFlags
        return 1 if stateFlags else 0

    def GetProtoDownReasons(self):
        reasons = []
        role = self.GetProtoDownRole()
        if role == Parser._ROLE_PRIMARY:
            stateFlags = (self.protoDownFlags & self._PROTO_DOWNF_USED_ON_PRIMARY)
        else:
            stateFlags = self.protoDownFlags

        if stateFlags & self._PROTO_DOWNF_INIT:
            reasons.append('init')
        if stateFlags & self._PROTO_DOWNF_INIT_DELAY:
            reasons.append('startup-delay')
        if stateFlags & self._PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE:
            reasons.append('isl-down')
        if stateFlags & self._PROTO_DOWNF_SHUTDOWN:
            reasons.append('shut')
        if stateFlags & self._PROTO_DOWNF_SINGLE_VXLAN:
            reasons.append('vxlan-single')
        if stateFlags & self._PROTO_DOWNF_NO_ANYCAST:
            reasons.append('no-anycast-ip')
        if stateFlags & self._PROTO_DOWNF_ANYCAST_MISMATCH:
            reasons.append('anycast-ip-mismatch')
        if stateFlags & self._PROTO_DOWNF_PEER_IP_MISMATCH:
            reasons.append('peer-ip-mismatch')
        if stateFlags & self._PROTO_DOWNF_SYSMAC_MISMATCH:
            reasons.append('system-mac-mismatch')
        if stateFlags & self._PROTO_DOWNF_DUPLICATE_PARTNER_MAC:
            reasons.append('duplicate-partner-mac')
        if stateFlags & self._PROTO_DOWNF_PARTNER_MAC_MISMATCH:
            reasons.append('partner-mac-mismatch')
        if stateFlags & self._PROTO_DOWNF_BRIDGE_PRIORITY_MISMATCH:
            reasons.append('bridge-priority-mismatch')
        if stateFlags & self._PROTO_DOWNF_CSU:
            reasons.append('CSU-in-progress')            

        return reasons

    def UpdateProtoDownFlags(self, nlm, addProtoDownFlags, delProtoDownFlags, batch=False):
        retVal = bytes()
        self.protoDownFlags |= addProtoDownFlags
        self.protoDownFlags &= ~delProtoDownFlags
        newProtoDown = self.GetProtoDown()
        if self.kernProtoDown != newProtoDown:
            Log.log("UpdateProtoDownFlags Interface %s add flags 0x%d del flags 0x%x final flags 0x%x"
                % (self.intfName, addProtoDownFlags, delProtoDownFlags, self.protoDownFlags))
            retVal = self.SetIntfKernProtoDown(nlm, newProtoDown, batch=batch)
        return retVal

    def DoSetIntfKernProtoDown(self, nlm, intfName, newProtoDown, batch=False):
        retVal = bytes()
        self.kernProtoDown = newProtoDown
        oldProtoDown = Intf.GetLinkProtoDownFlags(intfName)
        if oldProtoDown != newProtoDown:
            Log.log_debug(Log._LOG_DEBUG_FSM, "Interface %s mbr %s proto_down change; from %d to %d" % (self.intfName, intfName, oldProtoDown, newProtoDown))
            retVal = Intf.SetLinkProtoDownFlags(nlm, intfName, newProtoDown, batch=batch)
        return retVal

    def SetIntfKernProtoDown(self, nlm, protoDown, batch=False):
        retVal = bytes()
        newProtoDown = 1 if protoDown else 0
        if self.intfType == Parser._CLAG_INTF_TYPE_BOND:
            for intfMbr in Intf.GetBaseInterfaces(self.intfName):
                retVal += self.DoSetIntfKernProtoDown(nlm, intfMbr, newProtoDown, batch=batch)
        else:
            retVal += self.DoSetIntfKernProtoDown(nlm, self.intfName, newProtoDown, batch=batch)
        return retVal

    def UpdateIntfProtoDown(self, nlm, batch=False):
        protoDown = self.GetProtoDown()
        #Log.log_debug(Log._LOG_DEBUG_FSM, "Interface %s protoDown %d" % (intf, protoDown))
        return self.SetIntfKernProtoDown(nlm, protoDown, batch=batch)

    def SetClagIntfModeDormant(self, nlm, dormant, retainProtoDown=False):
        # Check if dormant mode setting has been disabled
        if dormant:
            if Parser.args.dormantDisable:
                Log.log_debug(Log._LOG_DEBUG_FSM, "Interface %s dormant mode setting skipped" % (self.intfName))
                return
            if (self.protoDownFlags & self._PROTO_DOWNF_SHUTDOWN):
                # clagd is in the process of exiting; don't allow dormant
                # setting from the UpdateLacpConfig thread at this point
                Log.log_debug(Log._LOG_DEBUG_FSM,
                    "%s dormant mode skipped; clag exit in prog" % (self.intfName))
                return

        # check if the mode already matches
        oldMode = Intf.GetLinkMode(self.intfName)
        if oldMode == dormant:
            return

        # shut down the slaves first
        self.SetIntfKernProtoDown(nlm, protoDown=1)

        # set the mode
        modeStr = "dormant" if dormant else "default"
        oldModeStr = "dormant" if oldMode else "default"
        Log.log_debug(Log._LOG_DEBUG_FSM, "Interface %s mode changed from %s to %s" % (self.intfName, oldModeStr, modeStr))
        Intf.SetLinkMode(nlm, self.intfName, dormant)

        # change the mode to match local state
        if retainProtoDown:
            self.UpdateIntfProtoDown(nlm)

    def LogConflictSetFlags(self, flags):
        if (flags & Parser._CLAG_INTF_CONFLICT_NO_PEER):
            Log.log("Conflict (%s): matching clag-id (%d) not configured on peer" % (self.intfName, self.clagId))

        if (flags & Parser._CLAG_INTF_CONFLICT_LACP_PARTNER_MAC_MISMATCH):
            Log.log("Conflict (%s): LACP partner MAC mismatch" % (self.intfName))

        if (flags & Parser._CLAG_INTF_CONFLICT_LACP_MAC_EQ_SYS_MAC):
            Log.log("Conflict (%s): LACP partner MAC is same as our CLAG sysmac" % (self.intfName))

        if (flags & Parser._CLAG_INTF_CONFLICT_LACP_MAC_DUP):
            Log.log("Conflict (%s): duplicate LACP partner MAC" % (self.intfName))

    def LogConflictClearFlags(self, flags):
        if (flags & Parser._CLAG_INTF_CONFLICT_NO_PEER):
            Log.log("Conflict cleared (%s): matching clag-id (%d) detected on peer" % (self.intfName, self.clagId))

        if (flags & Parser._CLAG_INTF_CONFLICT_LACP_PARTNER_MAC_MISMATCH):
            Log.log("Conflict cleared (%s): LACP partner MAC is no longer mismatched" % (self.intfName))

        if (flags & Parser._CLAG_INTF_CONFLICT_LACP_MAC_EQ_SYS_MAC):
            Log.log("Conflict (%s): LACP partner MAC is no longer same as our CLAG sysmac" % (self.intfName))

        if (flags & Parser._CLAG_INTF_CONFLICT_LACP_MAC_DUP):
            Log.log("Conflict (%s): LACP partner MAC is no longer duplicate" % (self.intfName))

    def UpdateConflicts(self, set, clear, logConflict=True):
        set &= ~self.conflicts
        clear &= self.conflicts
        if clear:
            self.conflicts &= ~clear
            if logConflict:
                self.LogConflictClearFlags(clear)

        if set:
            self.conflicts |= set
            if logConflict:
                self.LogConflictSetFlags(set)


#-------------------------------------------------------------------------------
#
#   Command line parsing code
#
#-------------------------------------------------------------------------------

class ThrowingArgumentParser(argparse.ArgumentParser):
    def error(self, message):
        raise ArgumentParsingError(message)

class ClagParser:

    #
    #   CLAG Version information
    #
    _ClagVersion            = "1.4.0"
    _ClagDataVersion        = "1.4.0"
    _ClagCmdVersion         = "1.1.0"
    _ClagHelloVersion       = "1.1.1"

    _ROLE_SECONDARY         = "secondary"
    _ROLE_PRIMARY           = "primary"

    _CLAG_ID_MAX            = 65535
    #
    #   Default command line parameter values
    #
    default_lacp_poll       = 2
    default_peer_connect    = 1
    default_send_timeout    = 30
    default_send_bufsize    = 65536
    default_cmd_connect     = 1
    default_link_poll       = 1
    default_daemon          = False
    default_peer_port       = 5342
    default_backup_port     = 5342
    default_peer_timeout    = None
    default_role            = _ROLE_SECONDARY
    default_priority        = 32768
    default_switchd_timeout = 120
    default_reload_timer    = 300
    default_periodic_run    = 4
    default_init_delay      = 180
    default_force_dynamic   = False
    default_dormant_disable = False
    default_vxlan_anycast   = None
    default_redirect_enable = False
    default_redirect2_enable = True
    default_neigh_sync      = True
    default_permanent_mac_sync = True
    default_single_vxlan_device = False
    default_peerlinklearning_enable = False
    default_dup_partner_mac = True
    default_protobuf = True
    default_csu_support = True

    # fully resolved remote peer IP address 
    remotePeerIp            = None
    localVtepIp             = "0.0.0.0"

    # The following variable used to skip some steps on shutdown/reboot
    fastShutdown = True
    # The number of echo replies to ignore from the peer which is going down/rebooting
    backupFaultTolerance = 3

    # Keep a cached value of the system MAC (workaround for CM-21314)
    # CM-21321 will be the actual fix
    systemMac = None

    # bond to clagIntf association
    # {
    #     "bond1" : <clagIntf1>,
    #     "bond2" : <clagIntf2>
    # }
    # key=intfName : val=class ClagIntf
    clagIntfDB              = {}
    # lock for clagIntfDB and bondClagIdDb access
    clagIntfDBLock          = threading.Lock()

    # clag-id to bond association
    # {
    #     100 : "bond1",
    #     2 : "bond2"
    # }
    bondClagIdDB            = {}

    peerDataVersion             = "0.0.0"
    peerRedirect2Enable         =  False
    peerProtoBuf                = default_protobuf
    # set once all the initial data has been replayed to peer. cleared when peer
    # connectivity is lost
    syncDoneToPeer          = False
    # set once the initial replay of data from peer is done (peer will send a 
    # sync_done notification). cleared when peer connectivity is lost.
    syncDoneFromPeer        = False

    # We wait for all the config to be replayed from ifupdown2 before starting
    # communication with the peer
    configReloadDone        = False

    #Flag to check clagD is in the process of exiting 
    sendGoodbye             = False

    peerTimeoutStart        = 0
    bridgePriority          = 0
    #Flag to check if anycast Ip has been deleted by clag
    vxlanAnycastDeleted = False
    
    # Flag to enable/disable interaction with CSU Manager
    csuSupport              = default_csu_support

    # Consistency checks are run between the peer switches for all clag
    # interfaces/bonds. If there is config/state inconsistency it is reported
    # as a conflict on the on the interface via one of these reason codes.
    _CLAG_INTF_CONFLICT_NONE = 0
    _CLAG_INTF_CONFLICT_NO_PEER = (1 << 0)
    _CLAG_INTF_CONFLICT_LACP_PARTNER_MAC_MISMATCH = (1 << 1)
    _CLAG_INTF_CONFLICT_LACP_MAC_EQ_SYS_MAC = (1 << 2)
    _CLAG_INTF_CONFLICT_LACP_MAC_DUP = (1 << 3)
    _CLAG_INTF_CONFLICT_LACP_MAX = (1 << 4)

    _CLAG_INTF_CONFLICT_LACP_ALL = (_CLAG_INTF_CONFLICT_LACP_MAX -1)

    _clag_intf_conflict_map = \
            {_CLAG_INTF_CONFLICT_NO_PEER : 'matching clagid not configured on peer',\
             _CLAG_INTF_CONFLICT_LACP_PARTNER_MAC_MISMATCH : 'lacp partner mac mismatch',
             _CLAG_INTF_CONFLICT_LACP_MAC_EQ_SYS_MAC : 'lacp partner mac is same as our clag sysmac',
             _CLAG_INTF_CONFLICT_LACP_MAC_DUP : 'duplicate lacp partner mac'}


    _CLAG_INTF_TYPE_BOND  = 0
    _CLAG_INTF_TYPE_VXLAN = 1

    _SWITCHD_STARTING   = 1
    _SWITCHD_READY      = 2
    _SWITCHD_DEAD       = 3

    def __init__(self):
        '''
        Create the parser
        '''

        # Determine if we are running on a VM
        default_vm = False
        try:
            default_vm = (subprocess.check_output("/usr/sbin/virt-what").decode() != "")
        except (subprocess.CalledProcessError, OSError):
            pass

        self.parser = ThrowingArgumentParser(description="CLAG daemon, version %s" % (ClagParser._ClagVersion,))
        self.parser.add_argument("--lacpPoll", "-o", type=int, default=ClagParser.default_lacp_poll, 
                                 metavar="SECONDS",
                                 help="Seconds between obtaining local LACP information")
        self.parser.add_argument("--peerConnect", "-r", type=int, default=ClagParser.default_peer_connect,
                                 metavar="SECONDS",
                                 help="Seconds between trying to connect to peer.")
        self.parser.add_argument("--sendTimeout", type=int, default=ClagParser.default_send_timeout,
                                 metavar="SECONDS",
                                 help="Seconds until sending socket will timeout.")
        self.parser.add_argument("--sendBufSize", type=int, default=ClagParser.default_send_bufsize,
                                 metavar="BYTES",
                                 help="Bytes in the send socket buffer.")
        self.parser.add_argument("--cmdConnect", "-c", type=int, default=ClagParser.default_cmd_connect,
                                 metavar="SECONDS",
                                 help="Seconds between waiting for command connections.")
        self.parser.add_argument("--peerLinkPoll", type=int, default=ClagParser.default_link_poll,
                                 metavar="SECONDS",
                                 help="Seconds between checking if peer link is up.")
        self.parser.add_argument("--switchdReadyTimeout", type=int, default=ClagParser.default_switchd_timeout,
                                 metavar="SECONDS",
                                 help="Seconds clagd waits for switchd to be ready.")
        self.parser.add_argument("--reloadTimer", type=int, default=ClagParser.default_reload_timer,
                                 metavar="SECONDS",
                                 help="Seconds clagd waits for peer to become ready.")
        self.parser.add_argument("--priority", "-i", type=int, default=ClagParser.default_priority,
                                 help="The priority of this instance compared to the peer.")
        self.parser.add_argument("--periodicRun", type=int, default=ClagParser.default_periodic_run, 
                                 metavar="SECONDS",
                                 help="Seconds between running periodic processes.")
        self.parser.add_argument("--initDelay", type=int, default=ClagParser.default_init_delay,
                                 metavar="SECONDS",
                                 help="Seconds clagd delays the bringup of clag bond and anycastIp")
        self.parser.add_argument("--forceDynamic", "-f", action="store_true", 
                                 default=ClagParser.default_force_dynamic, 
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--dormantDisable", "-a", action="store_true", 
                                 default=ClagParser.default_dormant_disable, 
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--redirectEnable", action="store_true",
                                 default=ClagParser.default_redirect_enable,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--redirect2Enable", type=self.str2bool,
                                default=ClagParser.default_redirect2_enable,
                                help=argparse.SUPPRESS)
        self.parser.add_argument("--neighSync", type=self.str2bool,
                                 default=ClagParser.default_neigh_sync,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--permanentMacSync", type=self.str2bool,
                                 default=ClagParser.default_permanent_mac_sync,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--singleVxlanDevice", type=self.str2bool,
                                 default=ClagParser.default_single_vxlan_device,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--vxlanAnycast", default=ClagParser.default_vxlan_anycast, 
                                 metavar="IPADDR",
                                 help="Anycast local IP address for dual connected VxLANs")
        self.parser.add_argument("--daemon", "-d", action="store_true", default=ClagParser.default_daemon,
                                 help="Run as a daemon.")
        self.parser.add_argument("--quiet", "-q", action="store_true", default=Logger.default_quiet,
                                 help="Don't log anything.")
        self.parser.add_argument("--debug", type=self.intBase, default=Logger.default_debug,
                                 metavar="DEBUG_CODE",
                                 help="Debugging output mask.")
        self.parser.add_argument("--verbose", "-v", action="store_true", default=Logger.default_verbose,
                                 help="Log with verbosity.")
        self.parser.add_argument("--log", "-l", "--logfile", type=self.logDest, default=Logger.default_handlerStr,
                                 help="Log output to stdout, syslog, or a file")
        self.parser.add_argument("--vm", "-m", action="store_true", dest='vm',
                                 help="Enable debugging on a virtual machine")
        self.parser.add_argument("--novm", action="store_false", dest='vm',
                                 help="Disable debugging on a virtual machine")
        self.parser.set_defaults(vm=default_vm)
        self.parser.add_argument("--backupPort", "-b", type=int, default=ClagParser.default_backup_port,
                                 help="UDP port used by peer to respond to backup health check requests.")
        self.parser.add_argument("--backupIp", "-s", default="",
                                 help="Backup IP address of peer")
        self.parser.add_argument("--backupVrf", default="",
                                 help="VRF for the backupIp address")
        self.parser.add_argument("--peerPort", "-p", type=int, default=ClagParser.default_peer_port,
                                 help="TCP/IP port used by peer to communicate topology info.")
        self.parser.add_argument("--peerTimeout", "-t", type=int, default=ClagParser.default_peer_timeout,
                                 help="Seconds before peer information is discarded.")
        self.parser.add_argument("peerIp", type=self.isValidPeerIp,
                                 help='Unique IP v4/v6 address of peer. "linklocal" connects to peers IPv6 link address')
        self.parser.add_argument("peerIf", type=self.isIfName,
                                 help="Name of the interface connected to peer")
        self.parser.add_argument("sysMac", type=self.isMacAddr,
                                 help="The system ID shared with the peer (xx:xx:xx:xx:xx:xx)")
        self.parser.add_argument("--peerlinkLearnEnable", type=self.str2bool,
                                 default=ClagParser.default_peerlinklearning_enable,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--allowPartnerMacDup", type=self.str2bool,
                                 default=ClagParser.default_dup_partner_mac,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--protobuf", type=self.str2bool,
                                 default=ClagParser.default_protobuf,
                                 help=argparse.SUPPRESS)
        self.parser.add_argument("--csuSupport", type=self.str2bool,
                                 default=ClagParser.default_csu_support,
                                 help=argparse.SUPPRESS)
        #
        #   LACP Rate - The rate at which the peer will send information to us. Before
        #   we receive the first message from the peer, we don't know his rate and so
        #   this value will be None. After each message is received from the peer the
        #   included rate, in seconds, is saved here. If we lose communication to the
        #   peer, the value goes back to None.
        #
        self.peerLacpRateLock   = threading.Lock()
        self.peerLacpRate       = None

        self.currLacpPoll       = ClagParser.default_lacp_poll
        #                                 [0] Priority,             [1] UniqueId,     
        self.clagId             = [ ClagParser.default_priority, "00:00:00:00:00:00" ]
        self.peerId             = [ ClagParser.default_priority, "00:00:00:00:00:00" ]
        self.clagRole           = ClagParser.default_role
        self.peerRole           = None
        self.systemdStateMap    = {
            "inactive"      : ClagParser._SWITCHD_DEAD,
            "activating"    : ClagParser._SWITCHD_STARTING,
            "active"        : ClagParser._SWITCHD_READY,
            "deactivating"  : ClagParser._SWITCHD_DEAD,
            "failed"        : ClagParser._SWITCHD_DEAD,
        }
        try:
            stateStrT = subprocess.check_output(["/bin/systemctl", "is-active", "switchd.service"]).decode()
            stateStr = stateStrT.strip()
        except subprocess.CalledProcessError as e:
            stateStr = "failed"
        self.switchdState = self.systemdStateMap.get(stateStr, ClagParser._SWITCHD_DEAD)
        self.systemRebootTriggered = False

    def intBase(self, intStr):
        '''
        Allow an integer to be specified with standard base representations:
        37 = decimal, 0x25 = hex, 045 = octal, 0b100101 = binary
        '''
        try:
            intVal = int(intStr,0)
        except:
            msg = "%s is not a valid integer" % (intStr,)
            raise argparse.ArgumentTypeError(msg)
        return intVal

    def logDest(self, logDest):
        '''
        The log file can be the constant 'syslog', 'stdout', or the name of a file.
        '''
        if logDest not in ['syslog', 'stdout']:
            if os.path.isfile(logDest):
                if not os.access(logDest, os.W_OK):
                    msg = "%s is not a file that can be opened for writing" % (logDest,)
                    raise argparse.ArgumentTypeError(msg)
            else:
                if not os.access(os.path.dirname(os.path.realpath(logDest)), os.W_OK):
                    msg = "%s is not a file/directory that can be opened for writing" % (logDest,)
                    raise argparse.ArgumentTypeError(msg)

        return logDest

    def str2bool(self, v):
        if v.lower() in ('yes', 'true', 't', 'y', '1'):
            return True
        elif v.lower() in ('no', 'false', 'f', 'n', '0'):
            return False
        else:
            raise argparse.ArgumentTypeError('provide a boolean string; "true" or "false"')

    def getDataVer(self, verStr):
        (majVer, minVer, subVer) = verStr.split(".")
        return (int(majVer), int(minVer))

    def getPeerDataVer(self):
        return self.getDataVer(Parser.peerDataVersion)

    def getMyDataVer(self):
        return self.getDataVer(Parser._ClagDataVersion)

    def readyToAcceptConnFromPeer(self):
        srvConn = (serverSock, serverSockPair)
        lenAdj = 2 if set(srvConn).issubset(set(inputSocks)) else 0
        if (len(inputSocks) - lenAdj) == 0:
            return True
        else:
            return False

    def isPeerStaticClagStateAware(self):
        # dormant and protoDown states were introduced in in 1.1.0
        # This checks if the version is greater than or equal to 1.1.0
        (majVer, minVer) = self.getPeerDataVer()
        if (majVer > 1):
            return True
        if majVer == 1 and minVer > 0:
            return True
        return False

    def isPeerVlanSyncOptimized(self):
        # An optimized version of the vlan sync was introduced in in 1.3.0
        # This checks if the version is greater than or equal to 1.3.0
        (majVer, minVer) = self.getPeerDataVer()
        if (majVer > 1):
            return True
        if majVer == 1 and minVer > 2:
            return True
        return False

    def isPeerVxLanStateAware(self):
        # VxLAN Active-Active was introduced in in 1.2.0
        # This checks if the version is greater than or equal to 1.2.0
        (majVer, minVer) = self.getPeerDataVer()
        if (majVer > 1):
            return True
        if majVer == 1 and minVer > 1:
            return True
        return False

    def isPeerRedirect2Capable(self):
        (majVer, minVer) = self.getPeerDataVer()
        if (majVer > 1):
            return True
        if majVer == 1 and minVer > 3:
            return True
        return False

    def isValidPeerIp(self, peerIp):
        '''
        Make sure the peerIp specification is a valid IP v4/v6 address or "linklocal".
        '''
        if peerIp == 'linklocal':
            return peerIp
        try:
            remoteIp = ipaddress.ip_address(peerIp)
        except ValueError as msg:
            raise argparse.ArgumentTypeError(msg)
        return str(remoteIp)

    def isIfName(self, ifName):
        '''
        Determine if the agrument passed in is the name of an interface. Used 
        for argparse type checking.
        '''
        if not Intf.isIfName(ifName):
            msg = "%s is not the name of an interface" % (ifName,)
            raise argparse.ArgumentTypeError(msg)
        return ifName

    def isClagBond(self, bond):
        return self.GetBondClagId(bond)

    def isValidClagId(self, clagId):
        return 0 <= clagId <= Parser._CLAG_ID_MAX

    def GetClagIntf(self, bond):
        clagIntf = None
        self.clagIntfDBLock.acquire()
        if bond in self.clagIntfDB:
            clagIntf = self.clagIntfDB[bond]
        self.clagIntfDBLock.release()
        return clagIntf

    def GetBondClagId(self, bond):
        clagId = 0
        self.clagIntfDBLock.acquire()
        if bond in self.clagIntfDB:
            clagIntf = self.clagIntfDB[bond]
            clagId = clagIntf.clagId
        self.clagIntfDBLock.release()
        return clagId

    def GetBondFromClagId(self, clagId):
        bond = None
        self.clagIntfDBLock.acquire()
        if clagId in self.bondClagIdDB:
            bond = self.bondClagIdDB[clagId]
        self.clagIntfDBLock.release()
        return bond

    def GetVxLanInfo(self, vxlan):
        vxinfo = None
        if VxLanSync:
            vxinfo = VxLanSync.GetOurVxLanInfo(vxlan)
        return vxinfo

    def GetClagIntfDB(self, ignoreLocks=False):
        # Return a list of current CLAG interfaces
        if not ignoreLocks:
            self.clagIntfDBLock.acquire()
        clagIntfs = copy.copy(self.clagIntfDB)
        if not ignoreLocks:
            self.clagIntfDBLock.release()
        return clagIntfs

    def GetClagBondDB(self, ignoreLocks=False):
        # Return a list of current CLAG bond 
        clagBonds = { k:v for k,v in list(self.GetClagIntfDB(ignoreLocks).items()) if v.intfType == Parser._CLAG_INTF_TYPE_BOND }
        return clagBonds

    def SetAnycastMismatch(self, nlm, peerAnycast):
        global vxlanAnycastMismatch
        global peerConfigMismatch
        if Parser.clagRole == Parser._ROLE_PRIMARY:
            peerConfigMismatch = True
        if not vxlanAnycastMismatch:
            vxlanAnycastMismatch = True
            Log.log_error("The vxlanAnycast IP of the peer does not match our vxlanAnycast IP. Peer = %s, Our = %s" % (str(peerAnycast), Parser.args.vxlanAnycast))
            if Parser.clagRole != Parser._ROLE_PRIMARY:
                Intf.protoDownLock.acquire()
                nlmsgs = bytes()
                for intf in list(Parser.GetClagIntfDB().values()):
                    Log.log_error("Anycast IP mismatch, setting proto down for interface %s " % (intf.intfName,))
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, intf._PROTO_DOWNF_ANYCAST_MISMATCH, 0, batch=True)
                if nlmsgs:
                    nlm.tx_nlpacket_raw(nlmsgs)
                Intf.protoDownLock.release()
                if VxLanSync:
                    VxLanSync.newVxLanDataEvent.set()
                Parser.ListenersStateUpdate("down")
                if MrouteSync:
                    MrouteSync.ZebraNotifyClagStatusUpdate()

    def ClearAnycastMismatch(self, nlm):
        global vxlanAnycastMismatch
        if vxlanAnycastMismatch:
            vxlanAnycastMismatch = False
            Parser.ClearPeerConfigMismatch()
            nlmsgs = bytes()
            Intf.protoDownLock.acquire()
            for intf in list(Parser.GetClagIntfDB().values()):
                nlmsgs += intf.UpdateProtoDownFlags(nlm, 0, intf._PROTO_DOWNF_ANYCAST_MISMATCH, batch=True)
            if nlmsgs:
                nlm.tx_nlpacket_raw(nlmsgs)
            Intf.protoDownLock.release()
            if VxLanSync:
                VxLanSync.newVxLanDataEvent.set()
            Parser.ListenersStateUpdate("up")
            if MrouteSync:
                MrouteSync.ZebraNotifyClagStatusUpdate()

    def SetSysMacMismatch(self, nlm, sysId):
        global sysMacMismatch
        global peerConfigMismatch
        if Parser.clagRole == Parser._ROLE_PRIMARY:
            peerConfigMismatch = True
        if not sysMacMismatch:
            sysMacMismatch = True
            Log.log_error("The system ID of the peer does not match our system ID. Peer = %s, Our = %s" % ("None" if not sysId else sysId, Parser.args.sysMac))
            if Parser.clagRole != Parser._ROLE_PRIMARY:
                Intf.protoDownLock.acquire()
                nlmsgs = bytes()
                for intf in list(Parser.GetClagIntfDB().values()):
                    Log.log_error("System ID  mismatch, setting proto down for interface %s " % (intf.intfName,))
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, intf._PROTO_DOWNF_SYSMAC_MISMATCH, 0, batch=True)
                if nlmsgs:
                    nlm.tx_nlpacket_raw(nlmsgs)
                Intf.protoDownLock.release()
                Parser.ListenersStateUpdate("down")
                if MrouteSync:
                    MrouteSync.ZebraNotifyClagStatusUpdate()

    def ClearSysMacMismatch(self, nlm):
        global sysMacMismatch
        if sysMacMismatch:
            sysMacMismatch = False
            Parser.ClearPeerConfigMismatch()
            nlmsgs = bytes()
            Intf.protoDownLock.acquire()
            for intf in list(Parser.GetClagIntfDB().values()):
                nlmsgs += intf.UpdateProtoDownFlags(nlm, 0, intf._PROTO_DOWNF_SYSMAC_MISMATCH, batch=True)
            if nlmsgs:
                nlm.tx_nlpacket_raw(nlmsgs)
            Intf.protoDownLock.release()
            Parser.ListenersStateUpdate("up")
            if MrouteSync:
                MrouteSync.ZebraNotifyClagStatusUpdate()

    def SetBridgePriorityMismatch(self, nlm, peerBridgePriority):
        global bridgePriorityMismatch
        global peerConfigMismatch
        if Parser.clagRole == Parser._ROLE_PRIMARY:
            peerConfigMismatch = True
        if not bridgePriorityMismatch:
            bridgePriorityMismatch = True
            Log.log_error("The peer bridge priority %s does not match ours %s" % (str(peerBridgePriority), Parser.bridgePriority))
            if Parser.clagRole != Parser._ROLE_PRIMARY:
                Intf.protoDownLock.acquire()
                nlmsgs = bytes()
                for intf in Parser.GetClagIntfDB().values():
                    Log.log_error("Bridge Priority mismatch, setting proto down for interface %s " % (intf.intfName,))
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, intf._PROTO_DOWNF_BRIDGE_PRIORITY_MISMATCH, 0, batch=True)
                if nlmsgs:
                    nlm.tx_nlpacket_raw(nlmsgs)
                Intf.protoDownLock.release()
                Parser.ListenersStateUpdate("down")
                if MrouteSync:
                    MrouteSync.ZebraNotifyClagStatusUpdate()

    def ClearBridgePriorityMismatch(self, nlm):
        global bridgePriorityMismatch
        if bridgePriorityMismatch:
            bridgePriorityMismatch = False
            Parser.ClearPeerConfigMismatch()
            nlmsgs = bytes()
            Intf.protoDownLock.acquire()
            for intf in Parser.GetClagIntfDB().values():
                nlmsgs += intf.UpdateProtoDownFlags(nlm, 0, intf._PROTO_DOWNF_BRIDGE_PRIORITY_MISMATCH, batch=True)
            if nlmsgs:
                nlm.tx_nlpacket_raw(nlmsgs)
            Intf.protoDownLock.release()
            Parser.ListenersStateUpdate("up")
            if MrouteSync:
                MrouteSync.ZebraNotifyClagStatusUpdate()

    def SetPeerIpMismatch(self, nlm, peerIpAddr, ourPeerIp):
        global peerIpMismatch
        if not peerIpMismatch:
            Log.log_error("The peer's IP address supplied when starting clagd (%s) is not the IP address the peer is using (%s)" % (Parser.remotePeerIp, peerIpAddr))

            if peerIpAddr == ourPeerIp:
                Log.log_error("The peer's IP address supplied when starting clagd (%s) is the same as our IP" % Parser.remotePeerIp)

            if (Parser.args.peerIp == "linklocal") and not (peerIpAddr == ourPeerIp):
                try:
                    neighbors = subprocess.check_output(['/bin/ip', '-6', 'n', 's', 'dev', Parser.args.peerIf]).decode()
                    remotePeerIps = re.findall('^(fe80:\S+)', neighbors, re.MULTILINE)
                except:
                    remotePeerIps = []
                if peerIpAddr in remotePeerIps:
                    Parser.remotePeerIp = peerIpAddr
                    Log.log_error("Reconnecting with the new PeerIp %s" % (Parser.remotePeerIp))
                    Parser.CloseClientSock()
            else:
                if HealthCheck:
                    # notify peer-ip mismatch via backup until conflict is resolved
                    HealthCheck.HelloSendPeerIpMismatch()
                Log.log_error("Restart clagd with the correct peerIp address (%s)" % (peerIpAddr,))
                Intf.SetClagRole(nlm, Parser._ROLE_SECONDARY, "peer-ip mismatch")
                Intf.protoDownLock.acquire()
                nlmsgs = bytes()
                for intf in list(Parser.GetClagIntfDB().values()):
                    Log.log_error("Peer IP mismatch, setting proto down for interface %s " % (intf.intfName,))
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, intf._PROTO_DOWNF_PEER_IP_MISMATCH, 0, batch=True)
                if nlmsgs:
                    nlm.tx_nlpacket_raw(nlmsgs)
                Intf.protoDownLock.release()
                peerIpMismatch = True
                Parser.ListenersStateUpdate("down")
                if MrouteSync:
                    MrouteSync.ZebraNotifyClagStatusUpdate()

    def ClearPeerIpMismatch(self, nlm):
        global peerIpMismatch
        if peerIpMismatch:
            Intf.protoDownLock.acquire()
            nlmsgs = bytes()
            for intf in list(Parser.GetClagIntfDB().values()):
                nlmsgs += intf.UpdateProtoDownFlags(nlm, 0, intf._PROTO_DOWNF_PEER_IP_MISMATCH, batch=True)
            if nlmsgs:
                nlm.tx_nlpacket_raw(nlmsgs)
            Intf.protoDownLock.release()
            peerIpMismatch = False
            Parser.ListenersStateUpdate("up")
            if MrouteSync:
                MrouteSync.ZebraNotifyClagStatusUpdate()

    def SetRemoteIpMismatchOnPeer(self):
        global remoteIpMismatchOnPeer
        remoteIpMismatchOnPeer = True
        
    def ClearRemoteIpMismatchOnPeer(self):
        global remoteIpMismatchOnPeer
        remoteIpMismatchOnPeer = False

    def SetPeerConfigMismatch(self):
        global peerConfigMismatch
        peerConfigMismatch = True

    def ClearPeerConfigMismatch(self):
        global peerConfigMismatch
        if vxlanAnycastMismatch or sysMacMismatch or remoteIpMismatchOnPeer or bridgePriorityMismatch:
            return 
        peerConfigMismatch = False

    def GetMismatchState(self):
        return True if peerIpMismatch or vxlanAnycastMismatch or \
                       peerConfigMismatch or sysMacMismatch or bridgePriorityMismatch else False

    def ClearConfigMismatch(self, nlm):
        '''
        reset any related configuration mismatch when the peer sends a goodbye
        message or when the peer is not reachable via peerlink
        '''
        global peerConfigMismatch
        if vxlanAnycastMismatch:
            Parser.ClearAnycastMismatch(nlm)
        if peerIpMismatch:
            Parser.ClearPeerIpMismatch(nlm)
        if sysMacMismatch:
            Parser.ClearSysMacMismatch(nlm)
        if bridgePriorityMismatch:
            Parser.ClearBridgePriorityMismatch(nlm)
        peerConfigMismatch = False

    def ListenersStateUpdate(self, state):
        global mstpClagState
        if peerConfigMismatch or not Intf.isPeerAlive():
            state = "down"
        if Cmd and state != mstpClagState:
            Log.log_debug(Log._LOG_DEBUG_FSM, "toListeners: add clag-state = %s" % state)
            Cmd.CmdGetAllOutput("add clag-state = %s" % state)
            mstpClagState = state

    def ListenersRoleUpdate(self):
        role = self.GetCombinedRole()
        if Cmd:
            Log.log_debug(Log._LOG_DEBUG_FSM, "toListeners: add clag-role = %s" % (str(role)))
            Cmd.CmdGetAllOutput("add clag-role = %s" % (str(role),))

    def GetCombinedRole(self):
        if not Intf.isPeerAlive() and HealthCheck and\
                     HealthCheck.IsBackupRoleAvailable():
            role = HealthCheck.GetBackupRole()
        else:
            role = self.clagRole
        return role

    def delClagBond(self, nlm, bond):
        self.clagIntfDBLock.acquire()
        clagIntf = self.clagIntfDB.get(bond)
        if clagIntf:
            clagIntf.CleanUp(nlm)
            del self.clagIntfDB[bond]
        self.clagIntfDBLock.release()

    def addClagBond(self, nlm, bond, clagId):
        self.clagIntfDBLock.acquire()
        if bond in self.clagIntfDB:
            clagIntf = self.clagIntfDB[bond]
            clagIntf.SetNewClagId(nlm, clagId)
        else:
            self.clagIntfDB[bond] = ClagIntf(nlm, bond, intfType=Parser._CLAG_INTF_TYPE_BOND, clagId=clagId)
        intf = self.clagIntfDB[bond]
        self.clagIntfDBLock.release()
        if LacpSync:
            LacpSync.EvaluateProtoDownBondConflict(nlm, intf)

    def delClagVxLan(self, nlm, vxlan):
        self.clagIntfDBLock.acquire()
        clagIntf = self.clagIntfDB.get(vxlan)
        if clagIntf:
            clagIntf.CleanUp(nlm)
            del self.clagIntfDB[vxlan]
        self.clagIntfDBLock.release()

    def addClagVxLan(self, nlm, vxlan):
        self.clagIntfDBLock.acquire()
        if vxlan not in self.clagIntfDB:
            self.clagIntfDB[vxlan] = ClagIntf(nlm, vxlan, intfType=Parser._CLAG_INTF_TYPE_VXLAN)
        self.clagIntfDBLock.release()

    def UpdateClagConflicts(self, bond, set, clear):
        self.clagIntfDBLock.acquire()
        if bond in self.clagIntfDB:
            self.clagIntfDB[bond].UpdateConflicts(set, clear)
        self.clagIntfDBLock.release()

    def ResetClagConflicts(self):
        self.clagIntfDBLock.acquire()
        resetDone = False
        for intfName in self.clagIntfDB:
            if self.clagIntfDB[intfName].conflicts:
                resetDone = True
                self.clagIntfDB[intfName].UpdateConflicts(0, self._CLAG_INTF_CONFLICT_LACP_ALL, logConflict=False)
        if resetDone:
            Log.log("Conflict reset (all-interfaces): peer connectivity lost")
        self.clagIntfDBLock.release()
        
    def SetSyncDoneFromPeer(self, done):
        self.syncDoneFromPeer = done
        if self.syncDoneFromPeer:
            if LacpSync:
                LacpSync.EvaluateAndUpdateConflicts()
            if VxLanSync:
                VxLanSync.newVxLanDataEvent.set()
        else:
            self.ResetClagConflicts() 

    def GetClagIntfConflicts(self, intfName):
        conflicts = 0
        self.clagIntfDBLock.acquire()
        conflicts = self.clagIntfDB[intfName].conflicts if intfName in self.clagIntfDB else 0
        self.clagIntfDBLock.release()
        return conflicts
        
    # XXX - shoudl really sit in an util module
    def GetNonZeroBits(self, bits):
        while bits:
            # get the first non-zero bit
            nzBit = bits & (~bits + 1)    
            # generator for nz bit value
            yield nzBit
            # clear the bit that we just returned
            bits &= ~nzBit

    def GetClagIntfConflictStr(self, intfName):
        conflictList = []
        conflicts = self.GetClagIntfConflicts(intfName)
        bits = self.GetNonZeroBits(conflicts)
        for bit in bits:
            if bit in Parser._clag_intf_conflict_map:
                bitStr = Parser._clag_intf_conflict_map[bit]
            else:
                bitStr = "0x%x" % bit
            conflictList.append(bitStr)
        return conflictList

    def setReloadDone(self):
        Log.log("Initial config loaded")
        # XXX - need to use this as trigger to start communicating with the
        # peer
        self.configReloadDone = True
        return ""

    def setBondClagId(self, nlm, bond, clagId):
        Log.log_debug(Log._LOG_DEBUG_PARSER, "Set %s clag_id %d" % (bond, clagId))
        msg = ""

        clagBondSet = set(Intf.GetSubIfsOfMaster(bond))
        clagBondSet |= set([bond])

        if clagId == 0:
            self.delClagBond(nlm, bond)
            Intf.bridgeAttrLock.acquire()
            Intf.SetBridgeBackupPort(nlm, clagBondSet, False)
            Intf.bridgeAttrLock.release()
            return msg

        if Intf.GetMasterOfSubIf(self.args.peerIf) == bond:
            return "ERROR %s is a peer interface and cannot be assigned a clag-id (%s)" % (bond, clagId)

        if not self.isValidClagId(clagId):
            return "ERROR %s is not in the clag id range of <0-%d>." % (clagId, Parser._CLAG_ID_MAX)

        # duplicate config
        oldClagId = self.GetBondClagId(bond)
        if clagId == oldClagId:
            return msg

        # check if the clagId is being used by a different bond
        oldBond = self.GetBondFromClagId(clagId)
        if oldBond:
            return "ERROR %d is already in use for bond %s" % (clagId, oldBond)

        # set clag id
        self.addClagBond(nlm, bond, clagId)

        Intf.bridgeAttrLock.acquire()
        Intf.SetBridgeBackupPort(nlm, clagBondSet, True)
        Intf.bridgeAttrLock.release()
        return msg

    def setAnycastIp(self, nlm, address):
        msg = ""
        if self.args.vxlanAnycast != address:
            Log.log_debug(Log._LOG_DEBUG_PARSER, "Set VXLAN Anycast IP to %s" % (address))
            if VxLanSync:
                if self.args.vxlanAnycast:
                    vxlanDB = VxLanSync.GetOurVxLanDB()
                    Parser.args.vxlanAnycastDeleted = True 
                    Intf.DelIpAddress(nlm, "lo", str(self.args.vxlanAnycast), 32)
                    Intf.SetVxLanLocalIp(nlm, [(k,v[1]) for k,v in list(vxlanDB.items()) if v[1]])
                self.args.vxlanAnycast = address
                VxLanSync.anycastIpEvent = True
                VxLanSync.newVxLanDataEvent.set()
        return msg

    def setBackupIp(self, nlm, address, vrf=None):
        msg = ""
        self.args.backupIp = address
        self.args.backupVrf = vrf
        addrFamily = HealthCheck.GetAddressFamily(address)
        if not addrFamily:
            return "ERROR %s is not a valid IP address" % address

        # if address family has changed, close the TX/RX sockets
        if HealthCheck.addrFamily != addrFamily:
            HealthCheck.addrFamily = addrFamily
            HealthCheck.helloTxSock.close()
            HealthCheck.CloseRxSocket()
            HealthCheck.CreateTxSocket()
        else:
            if not vrf:
                HealthCheck.backupVrfUnbind = True
            HealthCheck.backupVrfBindSuccess = False
            HealthCheck.AttemptVrfBind()

        HealthCheck.HelloBackupReset(nlm)
        return msg

    def CloseClientSock(self):
        global clientSock
        global sendMsgSeq
        if Parser:
            Parser.peerProtoBuf = Parser.args.protobuf
        sendMsgSeq = 0
        myClientSock = clientSock
        if clientSock:
            clientSock = None
            myClientSock.close()
            self.NotifyPeerCommChange()

    def isClientSockOpen(self):
        return clientSock is not None

    def isVxlanAnycastIPMismatch(self):
        return vxlanAnycastMismatch

    def NotifyPeerCommChange(self):
        if VxLanSync:
            VxLanSync.newVxLanDataEvent.set()
        if LacpSync:
            LacpSync.newLacpDataEvent.set()

    def isMacAddr(self, macAddr):
        '''
        Determine if the agrument passed in is a valid mac address, of the form
        xx:xx:xx:xx:xx:xx, where x is a hexidecimal digit.
        '''
        macAddr = macAddr.lower()
        if re.match("[0-9a-f]{1,2}([-:])[0-9a-f]{1,2}(\\1[0-9a-f]{1,2}){4}$", macAddr):
            macAddr = macAddr.replace("-", ":") 
            macAddr = ':'.join(x.zfill(2) for x in macAddr.split(":"))
            Log.log("macAddr = %s" % macAddr)
        else:
            msg = "%s is not a valid mac address " % (macAddr,)
            raise argparse.ArgumentTypeError(msg)
        return macAddr

    def isRedirectEnabled(self):
        if Parser.args.redirectEnable:
            return True
        return Parser.args.redirect2Enable and Parser.peerRedirect2Enable

    def ParseCmdLine(self):
        '''
        Parse the command line parameters
        '''
        try:
            self.args, unknown = self.parser.parse_known_args()
        except ArgumentParsingError as e:
            Log.log_error("There was an error in the command line parameters.")
            Log.log_error(str(e))
            Log.log_error("exit with status -1")
            sys.exit(-1)

        for arg in unknown:
            Log.log_warn("The command line argument '%s' is unrecognized and will be ignored." % (arg,))

    def getCfgLacpPoll(self):
        return self.args.lacpPoll

    def getCurrLacpPoll(self):
        return self.currLacpPoll

    def setPeerRate(self, rate):
        self.peerLacpRateLock.acquire()
        self.peerLacpRate = rate
        try:
            self.currLacpPoll = max(self.peerLacpRate, self.args.lacpPoll)
        except:
            self.currLacpPoll = self.args.lacpPoll
        self.peerLacpRateLock.release()

    def getPeerTimeout(self):
        if self.args.peerTimeout:
            return self.args.peerTimeout
        return self.getCurrLacpPoll() * 10

    def setPeerTimeout(self, timeout):
        self.args.peerTimeout = timeout

    def getPeerLinkPoll(self):
        return self.args.peerLinkPoll

    def setPeerLinkPoll(self, timeout):
        self.args.peerLinkPoll = timeout

    def getPeriodicRun(self):
        return self.args.periodicRun

    def setPeriodicRun(self, timeout):
        self.args.periodicRun = timeout

    def getSocketInfo(self):
        sockInfo = {}
        sockInfo["socketToPeer"] = "Not Connected" if clientSock is None or clientSockInit else "Connected"
        lenAdj = 1 if serverSock in inputSocks else 0
        if serverSockPair in inputSocks:
            lenAdj += 1
        sockInfo["socketFromPeer"] = "Not Connected" if len(inputSocks) - lenAdj == 0 else "Connected"
        sockInfo["serverSocket"] = "Not Listening" if serverSock is None else "Listening"
        return sockInfo

    def getInterestingObjectsSize(self):
        objSize = {}
        objSize['bondClagIdDB'] = util.getSize(self.bondClagIdDB)
        self.clagIntfDBLock.acquire()
        objSize['clagIntfDB'] = util.getSize(self.clagIntfDB)
        self.clagIntfDBLock.release()
        Intf.ifInfoLock.acquire()
        objSize['ifNameToIndex'] = util.getSize(Intf.ifNameToIndex)
        objSize['ifInfoByIndex'] = util.getSize(Intf.ifInfoByIndex)
        Intf.ifInfoLock.release()
        for sync in DataSyncs:
            objSize.update(sync.GetObjectSize())
        return objSize
    
    def getTid(self):
        try:
            if platform.system().startswith('Linux'):
                syscalls = {
                'i386':   224,   # unistd_32.h: #define __NR_gettid 224
                'x86_64': 186,   # unistd_64.h: #define __NR_gettid 186
                }
                tid = ctypes.CDLL('libc.so.6').syscall(syscalls[platform.machine()])
        except:
            tid = -1
        return tid

    def clearConflictState(self, nlm, bonds):
        nlmsgs = bytes()
        if not bonds:
            bonds = Intf.GetAllClagBonds()
        for intf in list(Parser.GetClagBondDB().values()):
            protoDown = intf._PROTO_DOWNF_PARTNER_MAC_MISMATCH | intf._PROTO_DOWNF_DUPLICATE_PARTNER_MAC
            if intf.intfName in bonds and intf.protoDownFlags & protoDown:
                Intf.protoDownLock.acquire()
                nlmsgs += intf.UpdateProtoDownFlags(nlm, 0, protoDown, batch=True)
                Intf.protoDownLock.release()
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)
        return ""

    def DumpParameters(self):
        '''
        Print out all of the command line parameter values to the debug log.
        '''
        Log.log_debug(Log._LOG_DEBUG_PARSER, "The parameters are:")
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    lacpPoll = %d" % (self.args.lacpPoll,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerConnect = %d" % (self.args.peerConnect,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    sendTimeout = %d" % (self.args.sendTimeout,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    sendBufSize = %d" % (self.args.sendBufSize,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    cmdConnect = %d" % (self.args.cmdConnect,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerLinkPoll = %d" % (self.args.peerLinkPoll,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    switchdReadyTimeout = %d" % (self.args.switchdReadyTimeout,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    reloadTimer = %d" % (self.args.reloadTimer,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    periodicRun = %d" % (self.args.periodicRun,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    priority = %d" % (self.args.priority,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    quiet = %s" % (self.args.quiet,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    debug = 0x%X" % (self.args.debug,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    verbose = %s" % (self.args.verbose,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    log = %s" % (self.args.log,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    vm = %s" % (self.args.vm,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerPort = %d" % (self.args.peerPort,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerTimeout = %s" % (self.args.peerTimeout,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerIp = %s" % (self.args.peerIp,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerIf = %s" % (self.args.peerIf,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    backupIp = %s" % (self.args.backupIp,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    backupVrf = %s" % (self.args.backupVrf,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    backupPort = %d" % (self.args.backupPort,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    sysMac = %s" % (self.args.sysMac,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    forceDynamic = %s" % (self.args.forceDynamic,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    vxlanAnycast = %s" % (self.args.vxlanAnycast,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    dormantDisable = %s" % (self.args.dormantDisable,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    redirectEnable = %s" % (self.args.redirectEnable,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    redirect2Enable = %s" % (self.args.redirect2Enable,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    neighSync = %s" % (self.args.neighSync,))
        Log.log_debug(Log._LOG_DEBUG_PARSER, "    peerlinkLearnEnable = %s" % (self.args.peerlinkLearnEnable,))


#-------------------------------------------------------------------------------
#
#   Linux interface utility routines
#
#-------------------------------------------------------------------------------

class IntfSupport:

    # Device flags are stored with this attribute ID
    IFLA_FLAGS         = -1
    CLAG_ID_MATCHIF_IDX_RIF = 0
    CLAG_ID_MATCHIF_IDX_RIF_STATE = 1

    def __init__(self, nlm):
        #
        #   Dual Connected Interfaces - This is a dictionary which contains our  
        #   interface names of the current dual connected bonds as the keys and the
        #   peer switch's corresponding interface names as the values:
        #   {
        #       "bond1" : "bond4",
        #       "bond3" : "bond3"
        #   }
        #
        self.dualIfsLock = threading.Lock()
        self.dualIfs     = {}

        # key: l_if, data: (CLAG_ID_MATCHIF_IDX_XXX,....)
        self.clagIdMatchIfs = {}
        self.peerAlive   = False
        self.firstInitHandShakeDone = False
        self.initHandShakeDone = False
        # multiple threads can trigger peer connectivity shutdown
        self.peerDownLock = threading.Lock()
        self.peerDeathPending = False
        self.protoDownLock = threading.Lock()
        self.bridgeAttrLock = threading.Lock()
        self.ifInfoLock = threading.Lock()
        self.ifInfoByIndex = {}
        self.ifNameToIndex = {}
        self.vlan_id_to_ifname = {}
        self.waitAllBondDownEvent = clag.clagthread.Event()
        self.allClagBondDownEvent = True

        self.anycastIpAddDelayed = False

        # Override the nlmanager's mac_int_to_str function to print the MACs
        # like xx:xx:xx:xx:xx:xx instead of xxxx.xxxx.xxxx
        nlmanager.nlpacket.mac_int_to_str = self.mac_int_to_str

        # Dump the interface table and load up the ifInfoByIndex database
        debug = nlmanager.nlpacket.RTM_GETLINK in nlm.debug
        for msg in nlm.request_dump(nlmanager.nlpacket.RTM_GETLINK, socket.AF_UNSPEC, debug):
            self.AddInterface(msg)
        for msg in nlm.request_dump(nlmanager.nlpacket.RTM_GETLINK, socket.AF_BRIDGE, debug):
            self.AddInterface(msg)

    def mac_int_to_str(self, mac_int):
        """
        Return an integer in MAC string format: xx:xx:xx:xx:xx:xx
        """
        return ':'.join(("%012x" % mac_int)[i:i+2] for i in range(0, 12, 2))

    def CombineDicts(self, dest, source):
        """
        Take all of the items in the source dictionary whose values are not
        None, and add/replace them in the dest dictionary. Nested dictionaries
        are handled recursively.
        """
        for k,v in list(source.items()):
            if v is not None:
                if isinstance(v, dict):
                    self.CombineDicts(dest.setdefault(k,{}), v)
                else:
                    dest[k] = v

    def DeleteAdPartnerMacInDict(self, idx):
        self.ifInfoLock.acquire()
        ifDict = self.ifInfoByIndex.get(idx, {})
        link = nlmanager.nlmanager.Link.IFLA_LINKINFO
        data = nlmanager.nlmanager.Link.IFLA_INFO_DATA
        adInfo = nlmanager.nlpacket.Link.IFLA_BOND_AD_INFO
        adPartnerMac = nlmanager.nlpacket.Link.IFLA_BOND_AD_INFO_PARTNER_MAC
        if link in ifDict:
            if data in ifDict[link]:
                if adInfo in ifDict[link][data]:
                    if adPartnerMac in ifDict[link][data][adInfo]:
                        del ifDict[link][data][adInfo][adPartnerMac]
        self.ifInfoLock.release()

    def AddInterface(self, msg):
        """
        Called by the netlink code when an interface is added or updated. This
        code adds/updates the ifInfoByIndex and ifNameToIndex databases and
        calls any other code which needs to know about new interfaces.
        """
        idx = msg.ifindex
        flags = msg.flags
        nlm = global_nlm

        # Pair down the dictionary to include only the information which clagd needs.
        srcLinkInfo = msg.get_attribute_value(msg.IFLA_LINKINFO, {})
        kind = srcLinkInfo.get(msg.IFLA_INFO_KIND)
        srcLinkData = srcLinkInfo.get(msg.IFLA_INFO_DATA, {})
        srcSlaveData = None
        if srcLinkInfo.get(msg.IFLA_INFO_SLAVE_KIND) == "bond":
            srcSlaveData = srcLinkInfo.get(msg.IFLA_INFO_SLAVE_DATA)
            if srcSlaveData:
                slaveByPass = srcSlaveData.get(msg.IFLA_BOND_SLAVE_AD_RX_BYPASS)

        adPartnerMac = None
        if kind == 'bond':
            srcAdInfo = srcLinkData.get(msg.IFLA_BOND_AD_INFO, {})
            if srcAdInfo:
                adPartnerMac = srcAdInfo.get(msg.IFLA_BOND_AD_INFO_PARTNER_MAC)
        if kind == 'vxlan':
            ip = srcLinkData.get(msg.IFLA_VXLAN_LOCAL)
            if ip is not None:
                srcLinkData[msg.IFLA_VXLAN_LOCAL] = str(ip)
        dataKeys = {
            'vlan'   : (msg.IFLA_VLAN_ID,),
            'bond'   : (msg.IFLA_BOND_MODE, msg.IFLA_BOND_AD_ACTOR_SYSTEM),
            'bridge' : (msg.IFLA_BR_AGEING_TIME, msg.IFLA_BR_MCAST_MEMBERSHIP_INTVL,
                        msg.IFLA_BR_MCAST_QUERIER_INTVL, msg.IFLA_BR_VLAN_FILTERING,
                        msg.IFLA_BR_PRIORITY),
            'vxlan'  : (msg.IFLA_VXLAN_ID, msg.IFLA_VXLAN_LOCAL)
        }
        linkData = { k:srcLinkData.get(k) for k in dataKeys.get(kind, ()) }
        if kind == 'bond' and adPartnerMac:
            adInfo = {
                msg.IFLA_BOND_AD_INFO_PARTNER_MAC : adPartnerMac
            }
            linkData[msg.IFLA_BOND_AD_INFO] = adInfo

        slaveData = None
        if srcSlaveData:
            slaveData = {
                msg.IFLA_BOND_SLAVE_AD_RX_BYPASS : slaveByPass
            }

        linkInfo = {
            msg.IFLA_INFO_KIND : kind,
            msg.IFLA_INFO_DATA : linkData,
            msg.IFLA_INFO_SLAVE_DATA : slaveData
        }

        srcAfSpec = msg.get_attribute_value(msg.IFLA_AF_SPEC, {})
        afSpec = {
            msg.IFLA_BRIDGE_VLAN_INFO : srcAfSpec.get(msg.IFLA_BRIDGE_VLAN_INFO),
            msg.IFLA_BRIDGE_VLAN_TUNNEL_INFO : srcAfSpec.get(msg.IFLA_BRIDGE_VLAN_TUNNEL_INFO)
        } if msg.family == socket.AF_BRIDGE else {}

        srcProtInfo = msg.get_attribute_value(msg.IFLA_PROTINFO, {})
        protInfo = {
            msg.IFLA_BRPORT_STATE     : srcProtInfo.get(msg.IFLA_BRPORT_STATE),
            msg.IFLA_BRPORT_LEARNING  : srcProtInfo.get(msg.IFLA_BRPORT_LEARNING),
            msg.IFLA_BRPORT_VLAN_TUNNEL : srcProtInfo.get(msg.IFLA_BRPORT_VLAN_TUNNEL),
            msg.IFLA_BRPORT_PEER_LINK : srcProtInfo.get(msg.IFLA_BRPORT_PEER_LINK),
            msg.IFLA_BRPORT_DUAL_LINK : srcProtInfo.get(msg.IFLA_BRPORT_DUAL_LINK),
            msg.IFLA_BRPORT_BACKUP_PORT  : srcProtInfo.get(msg.IFLA_BRPORT_BACKUP_PORT),
            msg.IFLA_BRPORT_MULTICAST_ROUTER : srcProtInfo.get(msg.IFLA_BRPORT_MULTICAST_ROUTER)
        }

        nlDict = {
            msg.IFLA_IFNAME     : msg.get_attribute_value(msg.IFLA_IFNAME),
            msg.IFLA_PROTO_DOWN : msg.get_attribute_value(msg.IFLA_PROTO_DOWN),
            msg.IFLA_LINKMODE   : msg.get_attribute_value(msg.IFLA_LINKMODE),
            msg.IFLA_MASTER     : msg.get_attribute_value(msg.IFLA_MASTER),
            msg.IFLA_OPERSTATE  : msg.get_attribute_value(msg.IFLA_OPERSTATE),
            msg.IFLA_LINK       : msg.get_attribute_value(msg.IFLA_LINK),
            msg.IFLA_ADDRESS    : msg.get_attribute_value(msg.IFLA_ADDRESS),
            msg.IFLA_LINKINFO   : linkInfo,
            msg.IFLA_AF_SPEC    : afSpec,
            msg.IFLA_PROTINFO   : protInfo
        }

        self.ifInfoLock.acquire()

        ifDict = self.ifInfoByIndex.setdefault(idx, {})
        new_add = False
        if not ifDict:
            new_add = True

        prevOperstate = ifDict.get(nlmanager.nlpacket.Link.IFLA_OPERSTATE)
        prevMac = ifDict.get(nlmanager.nlpacket.Link.IFLA_ADDRESS)
        prevLinkInfo = ifDict.get(nlmanager.nlmanager.Link.IFLA_LINKINFO, {})
        prevLinkData = prevLinkInfo.get(nlmanager.nlmanager.Link.IFLA_INFO_DATA, {})
        prevAdInfo = prevLinkData.get(nlmanager.nlpacket.Link.IFLA_BOND_AD_INFO, {})
        prevAdPartnerMac = prevAdInfo.get(nlmanager.nlpacket.Link.IFLA_BOND_AD_INFO_PARTNER_MAC)

        # If nl overflow happened, clear ifDict and combine again with nlDict
        if 'stale' in ifDict and ifDict['stale'] is True:
            ifDict.clear()

        ifDict[IntfSupport.IFLA_FLAGS] = flags

        #Master if None is not updated in CombineDicts sequence since only non-zero values gets updated.
        ifDict[nlmanager.nlpacket.Link.IFLA_MASTER] = nlDict[msg.IFLA_MASTER]

        self.CombineDicts(ifDict, nlDict)

        name            = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
        operstate       = ifDict.get(nlmanager.nlpacket.Link.IFLA_OPERSTATE)
        mac             = ifDict.get(nlmanager.nlpacket.Link.IFLA_ADDRESS)
        ifInfo          = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        kind            = ifInfo.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND)
        ifData          = ifInfo.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
        vlanId          = ifData.get(nlmanager.nlpacket.Link.IFLA_VLAN_ID, {})
        adInfo          = ifData.get(nlmanager.nlpacket.Link.IFLA_BOND_AD_INFO, {})
        adPartnerMac    = adInfo.get(nlmanager.nlpacket.Link.IFLA_BOND_AD_INFO_PARTNER_MAC)
        vni             = ifData.get(nlmanager.nlpacket.Link.IFLA_VXLAN_ID)
        localip         = ifData.get(nlmanager.nlpacket.Link.IFLA_VXLAN_LOCAL)
        ifProtInfo      = ifDict.get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        brPortState     = ifProtInfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_STATE)
        brPortVlanTunnel = ifProtInfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_VLAN_TUNNEL)
        ifAfSpec        = ifDict.get(nlmanager.nlpacket.Link.IFLA_AF_SPEC, {})
        brVlanTunnelInfo = ifAfSpec.get(nlmanager.nlmanager.Link.IFLA_BRIDGE_VLAN_TUNNEL_INFO, {})

        if name is not None:
            self.ifNameToIndex[name] = idx

        self.ifInfoLock.release()
        Log.log_debug(Log._LOG_DEBUG_INTF, "Adding interface %s" % (name,))
        #Log.log_debug(Log._LOG_DEBUG_INTF, "Adding interface %s : %s" % (name, str(self.ifInfoByIndex[idx])))

        # Inform whomever wants to know about this new/modified interface
        if Parser is not None and prevOperstate != operstate and \
           name in [Parser.args.peerIf, self.GetMasterOfSubIf(Parser.args.peerIf)]:
            peerlinkEvent.set()

        if Parser is not None and prevOperstate != operstate and name == "lo":      
            anycastIp = Parser.args.vxlanAnycast
            if (anycastIp and \
                (self.GetOperStateFromSysFs("lo") != "down") and Parser.args.vxlanAnycastDeleted == False):
                Log.log_error("Anycast Ip Deleted on lo by external app. Re-adding the anycast-ip on lo")
                self.AddIpAddress(nlm, "lo", str(Parser.args.vxlanAnycast), 32)

        if Parser is not None and Parser.isClagBond(name):
            if Parser.args.backupIp and new_add and name is not None:
                Intf.SetBondSysMac(nlm, system_mac, name)
            if prevOperstate != operstate and operstate == nlmanager.nlpacket.Link.IF_OPER_DORMANT:
                Log.log_debug(Log._LOG_DEBUG_INTF, '%s oper state changed to dormant' % name)
                if LacpSync:
                    lacpBypass = 0
                    for slave in self.GetMembersOfBond(name):
                        lacpBypass |= self.GetLacpBypass(slave)
                    lacpInfo = LacpSync.GetOurLacpInfo(name)
                    if lacpInfo and lacpBypass != lacpInfo[5]:
                        LacpSync.CollectLocalInfo(nlm)
                self.SetDormantBondOperState(nlm, name)

            # always check for if_oper_down because partner mac could still
            # be here on first nl notification
            if operstate == nlmanager.nlpacket.Link.IF_OPER_DOWN and LacpSync:
                self.DeleteAdPartnerMacInDict(idx)
                if prevOperstate != operstate:
                    LacpSync.CollectLocalInfo(nlm, name)
                    LacpSync.FlushMacsOnDownIf(name)
                    if self.allClagBondDownEvent:
                        Log.log_debug(Log._LOG_DEBUG_INTF, "Last Clag Bond is Down.")
                        peerlinkEvent.set()

            elif prevAdPartnerMac != adPartnerMac and adPartnerMac and LacpSync:
                Log.log_debug(Log._LOG_DEBUG_INTF, "%s partner mac changed from %r to %r" % (name, prevAdPartnerMac, adPartnerMac))
                intf = Parser.GetClagIntf(name)
                if intf:
                    LacpSync.EvaluateProtoDownBondConflict(nlm, intf)
                LacpSync.CollectLocalInfo(nlm, name)

        elif VxLanSync is not None and kind == 'vxlan':
            operstatestr = self.OperStateToStr(operstate)
            if operstate == nlmanager.nlpacket.Link.IF_OPER_DOWN and not flags & nlmanager.nlpacket.Link.IFF_UP:
                operstatestr = "admin-down"
            if brPortVlanTunnel and brVlanTunnelInfo and Parser.args.singleVxlanDevice:
                VxLanSync.UpdateVniSingleVxLan(nlm, name, brVlanTunnelInfo)

            VxLanSync.AddVxLanInterface(nlm, name, vni, str(localip), operstatestr, brPortState)

        elif keep_going and NeighSync is not None and prevOperstate != operstate and \
             operstate == nlmanager.nlpacket.Link.IF_OPER_UP:
            if kind == 'vlan':
                master = self.GetMasterOfSubIf(name)
                if master:
                    if self.isBridgeName(master) and self.isVlanFilteringBridge(master):
                        NeighSync.SyncOnIfaceUp(name, True)

        if keep_going and NeighSync is not None and operstate == nlmanager.nlpacket.Link.IF_OPER_UP and \
           self.isSubIfName(name) and not self.isVlanFilteringBridge(self.GetBridgeOfMember(name)):
            if brPortState == 3:
                if self.isBridgePeerLink(name):
                    master = self.GetBridgeOfMember(name)
                    Log.log_debug(Log._LOG_DEBUG_INTF,"%s peerlink bond is in forwarding Syncing %s" % (name,master))
                    NeighSync.SyncOnIfaceUp(master, False)
            else :
                Log.log_debug(Log._LOG_DEBUG_INTF,"%s kind bond is not in forwarding" % (name))

        if kind == 'vlan':
            master = self.GetMasterOfSubIf(name)
            if master and self.isBridgeName(master) and self.isVlanFilteringBridge(master):
                self.vlan_id_to_ifname[vlanId] = name

                if operstate == nlmanager.nlpacket.Link.IF_OPER_UP:
                    if prevMac and prevMac != mac:
                        if Parser and Parser.args.permanentMacSync and FdbSync:
                            FdbSync.fdbWorkQueue.put((FdbSync._WORK_QUEUE_PERM_DELETE, (prevMac, vlanId, name, None, None, name)))
                        if NeighSync:
                            NeighSync.SyncOnIfaceMacChange(vlanId, mac)
                    if Parser and Parser.args.permanentMacSync and FdbSync:
                        FdbSync.fdbWorkQueue.put((FdbSync._WORK_QUEUE_PERM_ADD, (mac, vlanId, name, None, None, name)))
                else:
                    if Parser and Parser.args.permanentMacSync and FdbSync:
                        FdbSync.fdbWorkQueue.put((FdbSync._WORK_QUEUE_PERM_DELETE, (mac, vlanId, name, None, None, name)))

        if Parser and kind == 'bridge':
            Parser.bridgePriority = linkData.get(nlmanager.nlpacket.Link.IFLA_BR_PRIORITY)

    def DelInterface(self, idx, family):
        """
        Called by the netlink code when an interface is deleted. This code
        removes the entries in the ifInfoByIndex and ifNameToIndex databases and
        calls any other code which needs to know that the interface no longer
        exists.
        """
        nlm = global_nlm
        self.ifInfoLock.acquire()
        ifDict = self.ifInfoByIndex.get(idx, {})
        name = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
        mac  = ifDict.get(nlmanager.nlpacket.Link.IFLA_ADDRESS)
        if family == socket.AF_BRIDGE:
            ifDict.pop(nlmanager.nlpacket.Link.IFLA_PROTINFO, None)
            ifDict.pop(nlmanager.nlpacket.Link.IFLA_AF_SPEC, None)
            self.ifInfoLock.release()
            Log.log_debug(Log._LOG_DEBUG_INTF, "Removing bridge information from interface %s : %s" % (name, str(ifDict)))
        else:
            ifInfo = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
            kind = ifInfo.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND)
            vlanId = None
            if kind == 'vlan':
                data = ifInfo.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
                vlanId = data.get(nlmanager.nlpacket.Link.IFLA_VLAN_ID)
                master_idx = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINK)
                master_dict = self.ifInfoByIndex.get(master_idx, {})
                master = master_dict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                master_info = master_dict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
                master_kind = master_info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND)
                master_data = master_info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
                master_vlan_aware = master_data.get(nlmanager.nlpacket.Link.IFLA_BR_VLAN_FILTERING)
                if master_kind == "bridge" and master_vlan_aware == 1:
                    self.vlan_id_to_ifname.pop(vlanId)
            self.ifInfoByIndex.pop(idx, None)
            self.ifNameToIndex.pop(name, None)
            self.ifInfoLock.release()
            Log.log_debug(Log._LOG_DEBUG_INTF, "Deleting interface %s : %s" % (name, str(ifDict)))

            if NeighSync and vlanId:
                NeighSync.DelNeighEntries(name, vlanId)

            if Parser and Parser.args.permanentMacSync and FdbSync and vlanId:
                master = self.GetMasterOfSubIf(name)
                FdbSync.fdbWorkQueue.put((FdbSync._WORK_QUEUE_PERM_DELETE, (mac, vlanId, name, None, None, name)))

            # Inform whomever wants to know about this removed interface
            if VxLanSync is not None and kind == 'vxlan':
                VxLanSync.DelVxLanInterface(nlm, name)

    def GetIfIndex(self, ifName):
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName, None)
        self.ifInfoLock.release()
        return idx

    def GetIfName(self, ifIndex):
        self.ifInfoLock.acquire()
        ifName = self.ifInfoByIndex.get(ifIndex, {}).get(nlmanager.nlpacket.Link.IFLA_IFNAME)
        self.ifInfoLock.release()
        return ifName

    def GetLacpBypass(self, ifName):
        self.ifInfoLock.acquire()
        lacpBypass = 0
        idx = self.ifNameToIndex.get(ifName)
        nlmLink = nlmanager.nlpacket.Link
        lacpBypass = self.ifInfoByIndex.get(idx, {}).\
                        get(nlmLink.IFLA_LINKINFO, {}).\
                        get(nlmLink.IFLA_INFO_SLAVE_DATA, {}).\
                        get(nlmLink.IFLA_BOND_SLAVE_AD_RX_BYPASS, 0)
        self.ifInfoLock.release()
        return lacpBypass

    def GetLinkAdminState(self, ifName):
        # return the admin state as non-zero (IFF_UP) or 0
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        flags = self.ifInfoByIndex.get(idx, {}).get(IntfSupport.IFLA_FLAGS, 0)
        self.ifInfoLock.release()
        return flags & nlmanager.nlpacket.Link.IFF_UP

    def SetLinkAdminState(self, nlm, ifName, state):
        if_flags = nlmanager.nlpacket.Link.IFF_UP if state else 0
        if_change = nlmanager.nlpacket.Link.IFF_UP
        debug = nlmanager.nlpacket.RTM_NEWLINK in nlm.debug
        self.ifInfoLock.acquire()
        if_idx = self.ifNameToIndex.get(ifName, 0)
        self.ifInfoLock.release()

        link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_NEWLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
        link.flags = nlmanager.nlpacket.NLM_F_REQUEST
        link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, if_idx, if_flags, if_change)
        link.build_message(next(nlm.sequence), nlm.pid)
        Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting link admin state to %s" % (ifName, str(state)))
        return nlm.tx_nlpacket_raw(link.message)

    def GetLinkProtoDownFlags(self, ifName):
        # return the protodown state as 0 or 1
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        protodown = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTO_DOWN, -1)
        self.ifInfoLock.release()
        return protodown

    def SetLinkProtoDownFlags(self, nlm, ifName, protoDown, batch=False):
        retVal = bytes()
        debug = nlmanager.nlpacket.RTM_NEWLINK in nlm.debug
        self.ifInfoLock.acquire()
        if_idx = self.ifNameToIndex.get(ifName)
        if if_idx is not None:
            self.ifInfoByIndex[if_idx][nlmanager.nlpacket.Link.IFLA_PROTO_DOWN] = protoDown
        self.ifInfoLock.release()

        if if_idx is not None:
            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_NEWLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, if_idx, 0, 0)
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_PROTO_DOWN, protoDown)
            link.build_message(next(nlm.sequence), nlm.pid)
            if batch:
                return link.message
            Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting protodown flag to %s" % (ifName, str(protoDown)))
            nlm.tx_nlpacket_raw(link.message)
        return retVal

    def GetLinkMode(self, ifName):
        # return the link mode
        linkmode = 0
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        mode = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKMODE, 0)
        self.ifInfoLock.release()
        return mode

    def SetLinkMode(self, nlm, ifName, dormant):
        debug = nlmanager.nlpacket.RTM_NEWLINK in nlm.debug
        self.ifInfoLock.acquire()
        if_idx = self.ifNameToIndex.get(ifName)
        self.ifInfoLock.release()

        if if_idx is not None:
            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_NEWLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, if_idx, 0, 0)
            dormant_val = 1 if dormant else 0
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_LINKMODE, dormant_val)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting link mode to %s" % (ifName, str(dormant_val)))
            nlm.tx_nlpacket_raw(link.message)

    def CheckAndUpdateIntfModeDormant(self, nlm):
        '''
        The protoDown state in interface driver could have fallen out of sync
        with clagd
        '''
        if Parser.args.dormantDisable:
            return

        clagIntfs = list(Parser.GetClagBondDB().values()) 
        self.protoDownLock.acquire()
        for intf in clagIntfs:
            intf.SetClagIntfModeDormant(nlm, dormant=True, retainProtoDown=True)
        self.protoDownLock.release()

    def SetProtoDownOnExit(self, nlm, ignoreLocks=False):
        nlmsgs = bytes()
        for intf in list(Parser.GetClagIntfDB(ignoreLocks).values()):
            intf.protoDownFlags |= intf._PROTO_DOWNF_SHUTDOWN
            if intf.intfType == Parser._CLAG_INTF_TYPE_BOND:
                for intfMbr in self.GetBaseInterfaces(intf.intfName):
                    nlmsgs += self.SetLinkProtoDownFlags(nlm, intfMbr, 1, True)
                    Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting protodown flag (on exit) to 1" % (intfMbr,))
            else:
                nlmsgs += self.SetLinkProtoDownFlags(nlm, intf.intfName, 1, True)
                Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting protodown flag (on exit) to 1" % (intf.intfName,))
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def ClearDormantMode(self, nlm, ignoreLocks=False):
        for intf in Parser.GetClagBondDB(ignoreLocks):
            self.SetLinkMode(nlm, intf, 0)

    def GetBackupRole(self): 
        if HealthCheck and HealthCheck.IsBackupRoleAvailable():
            role = HealthCheck.GetBackupRole()
        else:
            role = Parser.clagRole

        return role

    def SetClagRole(self, nlm, newRole, reasonStr, doBackupElection=True):
        if Parser.clagRole == newRole or Parser.GetMismatchState():
            return

        Parser.clagRole = newRole
        if newRole == Parser._ROLE_PRIMARY:
            Log.log("Peerlink: role is now primary; %s" % reasonStr)
        else:
            Parser.clagRole = Parser._ROLE_SECONDARY
            Log.log("Peerlink: role is now secondary; %s" % reasonStr)

        # role over ISL can influence the role over backup
        if doBackupElection and HealthCheck and HealthCheck.GetBackupActive():
            HealthCheck.RunBackupElection(nlm)
        Parser.ListenersRoleUpdate()
        if MrouteSync:
            MrouteSync.ZebraNotifyClagStatusUpdate()

    def GetKCacheLinkCopy(self):
        self.ifInfoLock.acquire()
        ifInfo = copy.deepcopy(self.ifInfoByIndex)
        self.ifInfoLock.release()
        return ifInfo

    def SetDormantBondOperState(self, nlm, bond):
        if LacpSync:
            LacpSync.SetDormantBondOperState(nlm, bond)
        
    def UpdatePendingDormantBonds(self, nlm):
        if not LacpSync:
            return
        clagBonds = Parser.GetClagBondDB()
        for bond in clagBonds:
            self.ifInfoLock.acquire()
            idx = self.ifNameToIndex.get(bond)
            operstate = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_OPERSTATE, 0)
            self.ifInfoLock.release()
            if operstate == nlmanager.nlpacket.Link.IF_OPER_DORMANT:
                LacpSync.SetDormantBondOperState(nlm, bond)

    def SetInitHandShakeDone(self, nlm):
        if not self.initHandShakeDone:
            Log.log("Initial handshake done.")
            self.firstInitHandShakeDone = True
            self.initHandShakeDone = True
            nlmsgs = bytes()
            self.protoDownLock.acquire()
            for intf in list(Parser.GetClagIntfDB().values()):
                if (intf.protoDownFlags & intf._PROTO_DOWNF_INIT):
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, 0, intf._PROTO_DOWNF_INIT, batch=True)
            if nlmsgs:
                nlm.tx_nlpacket_raw(nlmsgs)
            self.protoDownLock.release()

    def ClearInitHandShake(self):
        self.initHandShakeDone = False

    def SetProtoPeerOrBackupState(self, nlm):
        nlmsgs = bytes()
        if not self.isPeerAlive() and HealthCheck and HealthCheck.GetBackupActive() and not HealthCheck.peerInitState:
            if Parser.clagRole == Parser._ROLE_PRIMARY and HealthCheck.IsBackupRoleAvailable() and HealthCheck.GetBackupRole() == "secondary":
                Intf.SetClagRole(nlm, Parser._ROLE_SECONDARY, "backup connectivity restored while the main connection over the peerlink was down", False)
            for intf in list(Parser.GetClagIntfDB().values()):
                if not (intf.protoDownFlags & intf._PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE):
                    if intf.intfType == Parser._CLAG_INTF_TYPE_VXLAN and self.waitAllBondDownEvent.is_set():
                        continue
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, intf._PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE, 0, batch=True)
            #Since we are bringing down the vnis Delete the Ip Address on the lo
            if VxLanSync and Parser.args.vxlanAnycast and HealthCheck.IsBackupRoleAvailable() and \
                    HealthCheck.GetBackupRole() == "secondary":
                Parser.args.vxlanAnycastDeleted = True
                Intf.DelIpAddress(nlm, "lo", Parser.args.vxlanAnycast, 32)
        else:
            #To handle the case when backup goes down, intermittently after peerlink failure 
            if self.waitAllBondDownEvent.is_set():
                self.waitAllBondDownEvent.clear()
            setProtoDnInitDelay = False
            if HealthCheck and HealthCheck.initDelayTimerStartEvent.is_set() and \
               HealthCheck.initDelayBringup() and (Parser.clagRole == Parser._ROLE_SECONDARY and \
                    HealthCheck.IsBackupRoleAvailable() and HealthCheck.GetBackupRole() == "secondary"):
                setProtoDnInitDelay = True
            for intf in list(Parser.GetClagIntfDB().values()):
                delFlags = intf._PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE | intf._PROTO_DOWNF_SINGLE_VXLAN \
                            if (intf.intfType == Parser._CLAG_INTF_TYPE_VXLAN and self.isMlagInSplitBrain()) \
                                else intf._PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE
                addFlags = intf._PROTO_DOWNF_INIT_DELAY if intf.intfType == Parser._CLAG_INTF_TYPE_BOND and \
                            setProtoDnInitDelay else intf._PROTO_DOWNF_NONE
                Log.log_debug(Log._LOG_DEBUG_INTF, "addFlags = %d, delFlags = %d" % (addFlags, delFlags))
                if (intf.protoDownFlags & intf._PROTO_DOWNF_PEER_LINK_DOWN_BACKUP_ACTIVE) or \
                   setProtoDnInitDelay and not (intf.protoDownFlags & intf._PROTO_DOWNF_INIT_DELAY):
                    nlmsgs += intf.UpdateProtoDownFlags(nlm, addFlags, delFlags, batch=True)

        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def isPeerLinkDown(self):
        '''
        Determine if communication with the peer should be possible because the
        interfaces to the peer are all up. This typically involves checking the
        peerlink SVI and the peerlink bond.
        '''
        # Determine if the interfaces to the peer are down
        peerIfDown = self.GetOperStateFromSysFs(Parser.args.peerIf) != "up"
        if self.isSubIfName(Parser.args.peerIf):
            peerIfDown |= self.GetOperStateFromSysFs(self.GetMasterOfSubIf(Parser.args.peerIf)) != "up"
        peerIfs = self.GetLogicalMastersOfSubIf(Parser.args.peerIf)
        for peerIf in peerIfs:
            peerIfDown |= self.GetOperStateFromSysFs(peerIf) != "up"
        return peerIfDown

    #
    #   Bridge interface routines
    #
    def isBridgeName(self, bridgeName):
        '''
        Determine if the agrument passed in is the name of a bridge.
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        isBridge = info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge"
        self.ifInfoLock.release()
        return isBridge

    def isBridgeMember(self, bridgeMemberName):
        '''
        Determines if the specified interface is a member of a bridge.
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        master = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_MASTER)
        info = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        isBridgeMem = info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge"
        self.ifInfoLock.release()
        return isBridgeMem

    def GetBridgeOfMember(self, bridgeMemberName):
        '''
        Returns the name of the bridge of which the specified interface is a member.
        '''
        bridge = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        master = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_MASTER)
        info = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
            bridge = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_IFNAME)
        self.ifInfoLock.release()
        return bridge

    def GetMembersOfBridge(self, bridgeName):
        '''
        Returns a list of interfaces which are members of the specified bridge.
        '''
        bridgeMembers = []
        self.ifInfoLock.acquire()
        bridgeIdx = self.ifNameToIndex.get(bridgeName)
        info = self.ifInfoByIndex.get(bridgeIdx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
            for ifDict in list(self.ifInfoByIndex.values()):
                if ifDict.get(nlmanager.nlpacket.Link.IFLA_MASTER) == bridgeIdx:
                    ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                    if ifName is not None:
                        bridgeMembers.append(ifName)
        self.ifInfoLock.release()
        return bridgeMembers

    def GetAllBridges(self):
        '''
        Returns a list of all bridges in the system
        '''
        bridgeNames = []
        self.ifInfoLock.acquire()
        for ifDict in list(self.ifInfoByIndex.values()):
            info = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
            if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
                brName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                if brName is not None:
                    bridgeNames.append(brName)
        self.ifInfoLock.release()
        return bridgeNames

    def isVlanFilteringBridge(self, bridgeName):
        '''
        Return True if bridge is VLAN filtering or False otherwise
        '''
        vlanFiltering = False
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            vlanFiltering = data.get(nlmanager.nlpacket.Link.IFLA_BR_VLAN_FILTERING) == 1
        self.ifInfoLock.release()
        return vlanFiltering

    def GetBridgePeerVlanMap(self):
        '''
        Returns a dictionary whose keys are bridge names and data is the VLAN ID
        of the peerlink in that bridge. Will only include vlan unaware bridges.
        '''
        peerVlanMap = {}
        subIfVlanMap = self.GetSubIfsVlanMap()
        peerMIf = self.GetMasterOfSubIf(Parser.args.peerIf)
        peerIfs = set(self.GetSubIfsOfMaster(peerMIf)) | set([peerMIf])
        for bridge in self.GetAllBridges():
            if not self.isVlanFilteringBridge(bridge):
                for intf in set(self.GetMembersOfBridge(bridge)) & peerIfs:
                    peerVlanMap[bridge] = subIfVlanMap.get(intf, (None,0))[1]
        return peerVlanMap

    def GetPeerBridgeVlanMap(self):
        '''
        Returns a dictionary whose keys are vlan IDs of the sub interfaces of 
        the peerlink which are members of bridges. The data is a dictionary.
        The keys of the dictionary are the "base" interface names of members
        of the bridge, and the data is the vlan IDs of the base interface in
        the bridge.
        '''
        vlanMap = {}
        masterVlanMap = self.GetMasterIfVlanMap()
        vlanIdMap = self.GetSubIfsVlanMap()
        peerMIf = self.GetMasterOfSubIf(Parser.args.peerIf)
        peerIfs = set(self.GetSubIfsOfMaster(peerMIf)) | set([peerMIf])
        for peerIf in peerIfs:
            peerVlanId = vlanIdMap.get(peerIf, (None, 0))[1]
            bridgeName = self.GetBridgeOfMember(peerIf)
            for member in self.GetMembersOfBridge(bridgeName):
                (intfBase, intfVlanId) = vlanIdMap.get(member, (None, 0))
                if not vlanMap.get(peerVlanId):
                    vlanMap[peerVlanId] = {}
                vlanMap[peerVlanId][intfBase] = intfVlanId
        return vlanMap

    def GetBridgeAgeingTime(self, bridgeName):
        '''
        Returns the number of seconds after which MAC addresses will be aged
        '''
        ageTimer = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            ageTimer = data.get(nlmanager.nlpacket.Link.IFLA_BR_AGEING_TIME)
            # Value in ticks: CLOCKS_PER_SEC = USER_HZ = 100
            ageTimer = int(ageTimer) / 100
        self.ifInfoLock.release()
        return ageTimer

    def GetBridgeMemInterval(self, bridgeName):
        '''
        Returns the multicast group membership interval for a bridge.
        '''
        memIntval = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            memIntval = data.get(nlmanager.nlpacket.Link.IFLA_BR_MCAST_MEMBERSHIP_INTVL)
            # Value in ticks: CLOCKS_PER_SEC = USER_HZ = 100
            memIntval = int(memIntval) / 100
        self.ifInfoLock.release()
        return memIntval

    def GetBridgeQuerierInterval(self, bridgeName):
        '''
        Returns the multicast querier interval for a bridge.
        '''
        querierIntval = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bridge":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            querierIntval = data.get(nlmanager.nlpacket.Link.IFLA_BR_MCAST_QUERIER_INTVL)
            # Value in ticks: CLOCKS_PER_SEC = USER_HZ = 100
            querierIntval = int(querierIntval) / 100
        self.ifInfoLock.release()
        return querierIntval

    def GetPvid(self, intf):
        '''
        Returns the PVID of a bridge port, or None if there is none, as in the
        case of the old bridge driver
        '''
        pvid = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(intf)
        afspec = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_AF_SPEC, {})
        vlanInfo = afspec.get(nlmanager.nlpacket.Link.IFLA_BRIDGE_VLAN_INFO, [])
        for flags, vlanId in vlanInfo:
            if flags & nlmanager.nlpacket.Link.BRIDGE_VLAN_INFO_PVID:
                pvid = vlanId
                break
        self.ifInfoLock.release()
        return pvid

    def GetPvidMap(self):
        '''
        Returns a dictionary: keys = bridge interface names, values = PVID of the bridge port
        '''
        pvidMap = {}
        self.ifInfoLock.acquire()
        for ifDict in list(self.ifInfoByIndex.values()):
            afspec = ifDict.get(nlmanager.nlpacket.Link.IFLA_AF_SPEC, {})
            vlanInfo = afspec.get(nlmanager.nlpacket.Link.IFLA_BRIDGE_VLAN_INFO, [])
            for flags, vlanId in vlanInfo:
                ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                if ifName is not None and flags & nlmanager.nlpacket.Link.BRIDGE_VLAN_INFO_PVID:
                    pvidMap[ifName] = vlanId
                    break
        self.ifInfoLock.release()
        return pvidMap

    def isBridgeLearning(self, bridgeMemberName):
        """
        Returns true if learning is enabled on a bridge member.
        """
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        protinfo = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        learning = protinfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_LEARNING, False)
        self.ifInfoLock.release()
        return learning

    def isBridgeDualLink(self, bridgeMemberName):
        """
        Returns true if dual link attribute is enabled on a bridge member.
        """
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        protinfo = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        duallink = protinfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_DUAL_LINK, False)
        self.ifInfoLock.release()
        return duallink

    def isBridgePeerLink(self, bridgeMemberName):
        """
        Returns true if peer link attribute is enabled on a bridge member.
        """
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        protinfo = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        peerlink = protinfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_PEER_LINK, False)
        self.ifInfoLock.release()
        return peerlink

    def isBridgeBackupPort(self, bridgeMemberName):
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        protinfo = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        backup_port = protinfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_BACKUP_PORT, False)
        self.ifInfoLock.release()
        peerlink = self.GetMasterOfSubIf(Parser.args.peerIf)
        return backup_port if backup_port == self.GetIfIndex(peerlink) else 0

    def isBridgeVlanTunnel(self, bridgeMemberName):
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        protinfo = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        vlanTunnel = protinfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_VLAN_TUNNEL, False)
        self.ifInfoLock.release()
        return vlanTunnel

    def getBridgeMcastRtrType(self, bridgeMemberName):
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bridgeMemberName)
        protinfo = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_PROTINFO, {})
        mcast_rport_type = protinfo.get(nlmanager.nlpacket.Link.IFLA_BRPORT_MULTICAST_ROUTER, False)
        self.ifInfoLock.release()
        return mcast_rport_type

    def SetBridgeDualLink(self, nlm, ifList, enable):
        """
        Enables or disables the bridge port duallink attribute on a list of 
        interface names.
        """
        nlmsgs = bytes()
        duallink = 1 if enable else 0
        protinfo = {
            nlmanager.nlpacket.Link.IFLA_BRPORT_DUAL_LINK : duallink
        }
        for ifName in ifList:
            debug = nlmanager.nlpacket.RTM_SETLINK in nlm.debug
            self.ifInfoLock.acquire()
            if_idx = self.ifNameToIndex.get(ifName, 0)
            self.ifInfoLock.release()

            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_SETLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.family = socket.AF_BRIDGE
            link.body = struct.pack('=BxxxiLL', socket.AF_BRIDGE, if_idx, 0, 0)
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_PROTINFO | nlmanager.nlpacket.NLA_F_NESTED, protinfo)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting duallink to %d" % (ifName, duallink))
            nlmsgs += link.message
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def SetBridgePeerLink(self, nlm, ifList, enable):
        """
        Enables or disables the bridge port peerlink attribute on a list of 
        interface names.
        """
        nlmsgs = bytes()
        peerlink = 1 if enable else 0
        protinfo = {
            nlmanager.nlpacket.Link.IFLA_BRPORT_PEER_LINK : peerlink
        }
        for ifName in ifList:
            debug = nlmanager.nlpacket.RTM_SETLINK in nlm.debug
            self.ifInfoLock.acquire()
            if_idx = self.ifNameToIndex.get(ifName, 0)
            self.ifInfoLock.release()

            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_SETLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.family = socket.AF_BRIDGE
            link.body = struct.pack('=BxxxiLL', socket.AF_BRIDGE, if_idx, 0, 0)
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_PROTINFO | nlmanager.nlpacket.NLA_F_NESTED, protinfo)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting peerlink to %d" % (ifName, peerlink))
            nlmsgs += link.message
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def SetBridgeMulticastRouter(self, nlm, ifList, rtrType):
        """
        Enables or disables the bridge port multicast router attribute on a list of 
        interface names.
        """
        nlmsgs = bytes()
        protinfo = {
            nlmanager.nlpacket.Link.IFLA_BRPORT_MULTICAST_ROUTER : rtrType
        }
        for ifIdx in ifList:
            debug = nlmanager.nlpacket.RTM_SETLINK in nlm.debug

            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_SETLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.family = socket.AF_BRIDGE
            link.body = struct.pack('=BxxxiLL', socket.AF_BRIDGE, ifIdx, 0, 0)
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_PROTINFO | nlmanager.nlpacket.NLA_F_NESTED, protinfo)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log_debug(Log._LOG_DEBUG_INTF, "RtrPort Config to %d Type %d" % (ifIdx, rtrType))
            nlmsgs += link.message
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def SetBridgeLearning(self, nlm, ifList, enable):
        """
        Enables or disables the bridge port learning attribute on a list of 
        interface names.
        """
        nlmsgs = bytes()
        learning = 1 if enable else 0
        protinfo = {
            nlmanager.nlpacket.Link.IFLA_BRPORT_LEARNING : learning
        }
        for ifName in ifList:
            debug = nlmanager.nlpacket.RTM_SETLINK in nlm.debug
            self.ifInfoLock.acquire()
            if_idx = self.ifNameToIndex.get(ifName, 0)
            self.ifInfoLock.release()

            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_SETLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.family = socket.AF_BRIDGE
            link.body = struct.pack('=BxxxiLL', socket.AF_BRIDGE, if_idx, 0, 0)
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_PROTINFO | nlmanager.nlpacket.NLA_F_NESTED, protinfo)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting learning to %d" % (ifName, learning))
            nlmsgs += link.message
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def SetBridgeBackupPort(self, nlm, ifList, enable):
        """
        Enables (sets to the peerlink idx) or disables (sets to 0) the bridge
        port backup_port attribute on a list of interface names.
        """
        if not Parser.isRedirectEnabled():
            return

        peerlink = self.GetMasterOfSubIf(Parser.args.peerIf)
        if not self.GetIfIndex(peerlink):
            Log.log_warn("Cannot get ifindex for peerlink interface")
            return

        nlmsgs = bytes()
        backup_port = self.GetIfIndex(peerlink) if enable else 0
        protinfo = {
            nlmanager.nlpacket.Link.IFLA_BRPORT_BACKUP_PORT : backup_port
        }
        for ifName in ifList:
            debug = nlmanager.nlpacket.RTM_SETLINK in nlm.debug
            self.ifInfoLock.acquire()
            if_idx = self.ifNameToIndex.get(ifName, 0)
            self.ifInfoLock.release()

            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_SETLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST
            link.family = socket.AF_BRIDGE
            link.body = struct.pack('=BxxxiLL', socket.AF_BRIDGE, if_idx, 0, 0)
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_PROTINFO | nlmanager.nlpacket.NLA_F_NESTED, protinfo)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting backup port to %d" % (ifName, backup_port))
            nlmsgs += link.message
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def SetBridgeVlanState(self, nlm, if_idx, vid, state):
        debug = nlmanager.nlpacket.RTM_NEWVLAN in nlm.debug
        vlan = nlmanager.nlpacket.Vlan(nlmanager.nlpacket.RTM_NEWVLAN, debug, Log.logger, Log.getHandlerStr() != "syslog")
        vlan.family = socket.AF_BRIDGE
        vlan.flags = nlmanager.nlpacket.NLM_F_REQUEST
        vlan_info = {
            nlmanager.nlpacket.Vlan.BRIDGE_VLANDB_ENTRY_STATE : state,
            nlmanager.nlpacket.Vlan.BRIDGE_VLANDB_ENTRY_INFO : (0, vid)
        }
        vlan.body = struct.pack('=Bxxxi', socket.AF_BRIDGE, if_idx)
        vlan.add_attribute(nlmanager.nlpacket.Vlan.BRIDGE_VLANDB_ENTRY, vlan_info)
        vlan.build_message(next(nlm.sequence), nlm.pid)
        Log.log_debug(Log._LOG_DEBUG_INTF, "(%d): Setting vlan %d state to %d" % (if_idx, vid, state))
        return nlm.tx_nlpacket_raw(vlan.message)

    #
    #   VLAN Subinterface utility routines
    #
    def isSubIfName(self, subInterfaceName):
        '''
        Determine if the argument passed in is the name of a VLAN subinterface.
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(subInterfaceName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        isSubIf = info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "vlan"
        self.ifInfoLock.release()
        return isSubIf

    def GetMasterOfSubIf(self, subInterfaceName):
        '''
        Returns the name of the interface which is the "master" of the given
        VLAN subinterface
        '''
        ifName = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(subInterfaceName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "vlan":
            master = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINK)
            ifName = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_IFNAME)
        self.ifInfoLock.release()
        return ifName

    def GetLogicalMastersOfSubIf(self, subInterfaceName):
        '''
        Returns the name of the interface which is the "master" of the given
        VLAN subinterface. If the VLAN subinterface is from a bridge, then
        the bridge members with the same VLAN membership are returned.
        '''
        masterIfs = []
        directMaster = self.GetMasterOfSubIf(subInterfaceName)
        if directMaster:
            masterIfs = [directMaster]
            if self.isBridgeName(directMaster):
                vlanId = self.GetVlanIdOfSubIf(subInterfaceName)
                brifs = set(self.GetMembersOfBridge(directMaster))
                vlanifs = set(self.GetMembersOfVlan(vlanId))
                masterIfs = [i for i in brifs & vlanifs]
        return masterIfs

    def GetVlanIdOfSubIf(self, subInterfaceName):
        '''
        Returns the VLAN ID of the VLAN subinterface
        '''
        vlanId = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(subInterfaceName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "vlan":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            vlanId = data.get(nlmanager.nlpacket.Link.IFLA_VLAN_ID)
        self.ifInfoLock.release()
        return vlanId

    def GetSubIfBasedOnVlanId(self, vlanId):
        '''
        Returns the name of the VLAN subinterface for a specific VLAN ID
        '''
        self.ifInfoLock.acquire()
        if_name = self.vlan_id_to_ifname.get(vlanId, None)
        self.ifInfoLock.release()
        return if_name

    def GetSubIfsOfMaster(self, masterIf):
        '''
        Returns a list of VLAN sub-interfaces of the supplied interface
        '''
        subIfs = []
        self.ifInfoLock.acquire()
        masterIdx = self.ifNameToIndex.get(masterIf)
        if masterIdx is not None:
            for ifDict in list(self.ifInfoByIndex.values()):
                if masterIdx == ifDict.get(nlmanager.nlpacket.Link.IFLA_LINK):
                    ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                    if ifName is not None:
                        subIfs.append(ifName)
        self.ifInfoLock.release()
        return subIfs

    def GetSubIfsVlanMap(self):
        '''
        Returns a dictionary which allows mapping of VLAN sub-interface name
        (the key) to the raw interface name and VLAN ID (the data, a tuple).
        '''
        vlanMap = {}
        self.ifInfoLock.acquire()
        for ifDict in list(self.ifInfoByIndex.values()):
            ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
            if ifName is not None:
                info = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
                if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "vlan":
                    data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
                    vlanId = data.get(nlmanager.nlpacket.Link.IFLA_VLAN_ID)
                    linkIdx = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINK)
                    linkName = self.ifInfoByIndex.get(linkIdx,{}).get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                    if vlanId is not None and linkName is not None:
                        vlanMap[ifName] = (linkName, vlanId)
        self.ifInfoLock.release()
        return vlanMap

    def GetMasterIfVlanMap(self):
        '''
        Returns a dictionary which allows mapping of a master interface name
        (the key) to a dictionary of VLAN sub-interfaces on that interface (the
        data). That dictionary contains VLAN IDs (the key) and VLAN sub-interface
        names (the data).
        '''
        vlanMap = {}
        self.ifInfoLock.acquire()
        for ifDict in list(self.ifInfoByIndex.values()):
            ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
            if ifName is not None:
                info = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
                if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "vlan":
                    data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
                    vlanId = data.get(nlmanager.nlpacket.Link.IFLA_VLAN_ID)
                    linkIdx = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINK)
                    linkName = self.ifInfoByIndex.get(linkIdx,{}).get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                    if vlanId is not None and linkName is not None:
                        if linkName not in vlanMap:
                            vlanMap[linkName] = {}
                        vlanMap[linkName][vlanId] = ifName
        self.ifInfoLock.release()
        return vlanMap

    def GetIfVlanMap(self, intf):
        '''
        Returns a list of 128 32-bit integers which are bitmaps of the VLANs 
        enabled on an interface, or the empty list [] if the new bridge driver
        is not being used.
        '''
        vlanMapList = []
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(intf)
        afspec = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_AF_SPEC, {})
        vlanInfo = afspec.get(nlmanager.nlpacket.Link.IFLA_BRIDGE_VLAN_INFO, [])
        startId = None
        for (flags, vlanId) in vlanInfo:
            if not vlanMapList:
                vlanMapList = [0x00000000] * 128
            if flags & nlmanager.nlpacket.Link.BRIDGE_VLAN_INFO_RANGE_BEGIN:
                startId = vlanId
            elif flags & nlmanager.nlpacket.Link.BRIDGE_VLAN_INFO_RANGE_END:
                if startId is not None:
                    for vid in range(startId, vlanId+1):
                        vlanMapList[vid // 32] |= 1 << (vid % 32)
                    startId = None
            else:
                vlanMapList[vlanId // 32] |= 1 << (vlanId % 32)
        self.ifInfoLock.release()
        return vlanMapList

    def GetMembersOfVlan(self, vlanId):
        '''
        Returns a list of interface names which are members of the specified
        VLAN ID, or the empty list if the new bridge driver is not being used.
        '''
        ifs = []
        self.ifInfoLock.acquire()
        for ifDict in list(self.ifInfoByIndex.values()):
            ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
            if ifName is not None:
                afspec = ifDict.get(nlmanager.nlpacket.Link.IFLA_AF_SPEC, {})
                vlanInfo = afspec.get(nlmanager.nlpacket.Link.IFLA_BRIDGE_VLAN_INFO, [])
                startId = None
                for (flags, vid) in vlanInfo:
                    if flags & nlmanager.nlpacket.Link.BRIDGE_VLAN_INFO_RANGE_BEGIN:
                        startId = vid
                    elif flags & nlmanager.nlpacket.Link.BRIDGE_VLAN_INFO_RANGE_END:
                        if startId is not None:
                            if startId <= vlanId <= vid:
                                ifs.append(ifName)
                                break
                            startId = None
                    else:
                        if vid == vlanId:
                            ifs.append(ifName)
                            break
        self.ifInfoLock.release()
        return ifs

    def GetLocalIfBasedOnPeerVlanId(self, vlanId, brPeerVlanMap=None):
        if brPeerVlanMap is None:
            brPeerVlanMap = self.GetBridgePeerVlanMap()

        # assuming vlan unaware bridge
        if brPeerVlanMap != {}:
            for bif, vid in list(brPeerVlanMap.items()):
                if vid == vlanId:
                    return bif

        # assuming vlan aware bridge
        return self.GetSubIfBasedOnVlanId(vlanId)

    def GetClagBondsToPeerLinkVlanConflicts(self):
        vlanDB = VlanSync.GetIntfVlanDB()
        vlanConflictDict = {}
        if not vlanDB:
            return None
        peerLink = Parser.args.peerIf
        if Intf.isSubIfName(peerLink):
            peerLink = Intf.GetLogicalMastersOfSubIf(peerLink)[0]
        peerlinkVlans = vlanDB.pop(peerLink, [])
        for dualIf in vlanDB:
            dualIfVlans = vlanDB.get(dualIf, [])
            vlanConflictDict[dualIf] = "" if  not (set(dualIfVlans)-set(peerlinkVlans)) \
                                        else "not all vlans allowed on peerlink"
        return vlanConflictDict

    #
    #   Bond utility routines
    #
    def isBondName(self, bondName):
        '''
        Determine if the argument passed in is the name of a bond
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bondName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        isBond = info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond"
        self.ifInfoLock.release()
        return isBond

    def isBondMember(self, bondMemberName):
        '''
        Determines if the specified interface is a member of a bond.
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bondMemberName)
        master = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_MASTER)
        info = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        isBondMem = info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond"
        self.ifInfoLock.release()
        return isBondMem

    def GetBondOfMember(self, bondMemberName):
        '''
        Returns the name of the bond of which the specified interface is a member.
        '''
        bond = None
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(bondMemberName)
        master = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_MASTER)
        info = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond":
            bond = self.ifInfoByIndex.get(master, {}).get(nlmanager.nlpacket.Link.IFLA_IFNAME)
        self.ifInfoLock.release()
        return bond

    def GetMembersOfBond(self, bondName):
        '''
        Returns a list of the interfaces which are members fo the specified bond.
        '''
        bondMembers = []
        self.ifInfoLock.acquire()
        bondIdx = self.ifNameToIndex.get(bondName)
        info = self.ifInfoByIndex.get(bondIdx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond":
            for ifDict in list(self.ifInfoByIndex.values()):
                if ifDict.get(nlmanager.nlpacket.Link.IFLA_MASTER) == bondIdx:
                    ifName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                    if ifName is not None:
                        bondMembers.append(ifName)
        self.ifInfoLock.release()
        return bondMembers

    def GetBondMode(self, bondName):
        '''
        Returns the numeric mode of the bond, or None.
        '''
        bondMode = None
        self.ifInfoLock.acquire()
        bondIdx = self.ifNameToIndex.get(bondName)
        info = self.ifInfoByIndex.get(bondIdx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            bondMode = data.get(nlmanager.nlpacket.Link.IFLA_BOND_MODE)
        self.ifInfoLock.release()
        return bondMode

    def GetIntfBondNames(self, intfName):
        '''
        Given the name of an interface, returns the name of the bond on that
        interface, or [] if there is no bond. If the interface is a bond,
        then simply return that name. If the interface is a bond member then
        return the interface's "master". And if the interface is a VLAN sub-
        interface of a bond, then return the master of the subinterface. And
        if the interface is a VLAN sub-interface of a bridge, then return all
        bonds in the bridge which are part of the same VLAN.
        '''
        bondNames = []
        if self.isBondName(intfName):
            bondNames = [intfName]
        elif self.isBondMember(intfName):
            bondNames = [self.GetBondOfMember(intfName)]
        elif self.isSubIfName(intfName) and self.isBondName(self.GetMasterOfSubIf(intfName)):
            bondNames = [self.GetMasterOfSubIf(intfName)]
        elif self.isSubIfName(intfName) and self.isBridgeName(self.GetMasterOfSubIf(intfName)):
            vlanId = self.GetVlanIdOfSubIf(intfName)
            brName = self.GetMasterOfSubIf(intfName)
            brifs = set(self.GetMembersOfBridge(brName))
            vlanifs = set(self.GetMembersOfVlan(vlanId))
            bondNames = [i for i in brifs & vlanifs if self.isBondName(i)]
        return bondNames

    def GetAllBonds(self, excludeIfs=[]):
        bonds = []
        self.ifInfoLock.acquire()
        for ifDict in list(self.ifInfoByIndex.values()):
            info = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
            if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond":
                bondName = ifDict.get(nlmanager.nlpacket.Link.IFLA_IFNAME)
                if bondName and bondName not in excludeIfs:
                    bonds.append(bondName)
        self.ifInfoLock.release()
        return bonds

    def GetAllClagBonds(self, excludeIfs=[]):
        bonds = self.GetAllBonds(excludeIfs)
        clagBonds = [bond for bond in bonds if Parser.GetBondClagId(bond) or Parser.args.forceDynamic]
        return clagBonds

    def SetBondSysMac(self, nlm, sys_mac, bondName, dupChecks=False):
        '''
        Sets the ad_actor_system value for a bond
        '''
        debug = nlmanager.nlpacket.RTM_NEWLINK in nlm.debug
        self.ifInfoLock.acquire()
        if_idx = self.ifNameToIndex.get(bondName)
        self.ifInfoLock.release()

        if if_idx is not None and self.GetBondMode(bondName) == 4:
            if sys_mac == "00:00:00:00:00:00":
                sys_mac = self.GetIfMac(bondName)
            initState = self.GetLinkAdminState(bondName)
            if initState:
                self.SetLinkAdminState(nlm, bondName, 0)

            link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_NEWLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
            link.flags = nlmanager.nlpacket.NLM_F_REQUEST | nlmanager.nlpacket.NLM_F_ACK
            link.body = struct.pack('=Bxxxiii', socket.AF_UNSPEC, if_idx, 0, 0)
            info = {
                nlmanager.nlpacket.Link.IFLA_INFO_KIND : "bond",
                nlmanager.nlpacket.Link.IFLA_INFO_DATA : {
                    nlmanager.nlpacket.Link.IFLA_BOND_AD_ACTOR_SYSTEM : sys_mac
                }
            }
            link.add_attribute(nlmanager.nlpacket.Link.IFLA_LINKINFO, info)
            link.build_message(next(nlm.sequence), nlm.pid)
            Log.log("(%s): Setting ad_actor_system to %s" % (bondName, sys_mac))
            try:
                nlm.tx_nlpacket_get_response(link)
            except nlmanager.nlmanager.NetlinkError:
                Log.log_error("(%s): Failed to set ad_actor_system to %s" % (bondName, sys_mac))

            if initState:
                self.SetLinkAdminState(nlm, bondName, 1)

    def SetBondsSysMac(self, nlm, sys_mac, bonds, excludeIfs=[]):
        for bond in bonds:
            if bond not in excludeIfs:
                self.SetBondSysMac(nlm, sys_mac, bond)

    def SetAllClagBondsSysMac(self, nlm, sys_mac, excludeIfs=[]):
        global system_mac
        bonds = self.GetAllClagBonds(excludeIfs)
        if sys_mac != system_mac:
            self.SetBondsSysMac(nlm, sys_mac, bonds, excludeIfs)
            system_mac = sys_mac

    def GetBondSysMac(self, bondName):
        '''
        Returns the ad_actor_system of a bond
        '''
        sysMac = None
        self.ifInfoLock.acquire()
        bondIdx = self.ifNameToIndex.get(bondName)
        info = self.ifInfoByIndex.get(bondIdx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        if info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND) == "bond":
            data = info.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
            sysMac = data.get(nlmanager.nlpacket.Link.IFLA_BOND_AD_ACTOR_SYSTEM)
        self.ifInfoLock.release()
        return sysMac

    def GetBondPartnerMac(self, bondName):
        '''
        Returns the ad_partner_mac of a bond.
        '''
        partnerMac = None
        partner_mac_file = _InterfacePath + str(bondName) + "/bonding/ad_partner_mac"
        try:
            with open(partner_mac_file) as f:
                partnerMac = f.readline().strip()
        except IOError:
            partnerMac = None
        return partnerMac

    #
    #   VxLan Interface routines
    #
    def SetVxLanLocalIp(self, nlm, vxlanInfo):
        """
        Set the local IP attribute for a list of VxLAN interfaces. The vxlanInfo
        parameter is a list of tuples. Each tuple contains two items: the name
        of the VxLAN interface and the local IP address.
        """
        nlmsgs = bytes()
        for vxlanName, localIp in vxlanInfo:
            if localIp == 'None':
                Log.log_error("Interface %s: No local Tunnel IP address configured" % (vxlanName, ))
            else:
                debug = nlmanager.nlpacket.RTM_NEWLINK in nlm.debug
                self.ifInfoLock.acquire()
                if_idx = self.ifNameToIndex.get(vxlanName, 0)
                self.ifInfoLock.release()

                ifInfo = {
                    nlmanager.nlpacket.Link.IFLA_INFO_KIND : "vxlan",
                    nlmanager.nlpacket.Link.IFLA_INFO_DATA : {
                        nlmanager.nlpacket.Link.IFLA_VXLAN_LOCAL : ipaddress.ip_address(localIp)
                    }
                }
                link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_NEWLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
                link.flags = nlmanager.nlpacket.NLM_F_REQUEST
                link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, if_idx, 0, 0)
                link.add_attribute(nlmanager.nlpacket.Link.IFLA_LINKINFO, ifInfo)
                link.build_message(next(nlm.sequence), nlm.pid)
                Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting localIp to %s" % (vxlanName, localIp))
                nlmsgs += link.message
        if nlmsgs:
            nlm.tx_nlpacket_raw(nlmsgs)

    def GetVxlanLocalIp(self, vxlanIntf):
        Intf.ifInfoLock.acquire()
        idx = Intf.ifNameToIndex.get(vxlanIntf)
        ifDict = Intf.ifInfoByIndex.get(idx, {})
        ifInfo = ifDict.get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        data = ifInfo.get(nlmanager.nlpacket.Link.IFLA_INFO_DATA, {})
        localip = data.get(nlmanager.nlpacket.Link.IFLA_VXLAN_LOCAL)
        Intf.ifInfoLock.release()
        return localip

    #
    #   Dual Connected Interfaces - Sort of specific to CLAG
    #
    def GetDualIfs(self):
        self.dualIfsLock.acquire()
        dualIfs = copy.deepcopy(self.dualIfs)
        self.dualIfsLock.release()
        return dualIfs

    def SetDualIfs(self, dualIfs):
        self.dualIfsLock.acquire()
        self.dualIfs = copy.deepcopy(dualIfs)
        self.dualIfsLock.release()

    def AddDualIfs(self, dualIfs):
        self.dualIfsLock.acquire()
        self.dualIfs.update(dualIfs)
        self.dualIfsLock.release()

    def DelDualIfs(self, dualIfs):
        self.dualIfsLock.acquire()
        for dualIf in dualIfs:
            self.dualIfs.pop(dualIf, None)
        self.dualIfsLock.release()

    def isDualIf(self, intf):
        self.dualIfsLock.acquire()
        isDual = intf in self.dualIfs
        self.dualIfsLock.release()
        return isDual

    # ClagId matching Interfaces - dict of interfaces with matching clagId or
    # vni (doesn't include op-state or partner-mac comparison)
    def GetClagIdMatchIfs(self):
        self.dualIfsLock.acquire()
        clagIdMatchIfs =\
               {iface:self.clagIdMatchIfs[iface][self.CLAG_ID_MATCHIF_IDX_RIF]\
                for iface in self.clagIdMatchIfs}
        self.dualIfsLock.release()
        return clagIdMatchIfs

    def GetClagIdMatchInfo(self):
        self.dualIfsLock.acquire()
        clagIdMatchIfs = copy.deepcopy(self.clagIdMatchIfs)
        self.dualIfsLock.release()
        return clagIdMatchIfs

    def AddClagIdMatchIfs(self, clagIdMatchIfs):
        self.dualIfsLock.acquire()
        self.clagIdMatchIfs.update(clagIdMatchIfs)
        self.dualIfsLock.release()

    def DelClagIdMatchIfs(self, clagIdMatchIfs):
        self.dualIfsLock.acquire()
        for clagIf in clagIdMatchIfs:
            self.clagIdMatchIfs.pop(clagIf, None)
        self.dualIfsLock.release()

    def SetPeerAlive(self, nlm, alive):
        self.protoDownLock.acquire()
        if Parser.args.initDelay and HealthCheck and not HealthCheck.initState and not \
           HealthCheck.peerInitState  and \
            (Parser.clagRole == Parser._ROLE_SECONDARY and HealthCheck.GetBackupActive() and \
            HealthCheck.IsBackupRoleAvailable() and HealthCheck.GetBackupRole() == "secondary") \
            and not Parser.sendGoodbye:
            if alive:
                HealthCheck.initDelayEvent.clear()
                HealthCheck.initDelayTimerStartEvent.set()
            else:
                if not self.allClagBondDownEvent:
                    Log.log("Setting Wait on All Clag Bonds Down Event")
                    self.waitAllBondDownEvent.set()
        self.peerAlive = alive
        self.SetProtoPeerOrBackupState(nlm)
        self.protoDownLock.release()
        if self.peerAlive:
            if LacpSync:
                LacpSync.EvaluateAndUpdateConflicts()
        else:
            Parser.ResetClagConflicts() 
        Parser.NotifyPeerCommChange()

    def SetPeerDeathPending(self, pending):
        if pending == True:
            Log.log("Peer link is down; checking if the peer switch is alive.")
        self.peerDeathPending = pending

    def GetPeerDeathPending(self):
        return self.peerDeathPending

    def ClearPeerDeathPending(self, nlm):
        if self.peerDeathPending:
            peerSwitchDead = False if HealthCheck.GetBackupActive() else True
            if peerSwitchDead:
                # Take over as primary
                Intf.SetClagRole(nlm, Parser._ROLE_PRIMARY, "backup became inactive", doBackupElection=False)
                HealthCheck.ForceInitDelayExpiry("peer is dead")
            PeerIsNotActive(nlm, peerSwitchDead)
            self.peerDeathPending = False
            
    def isPeerAlive(self):
        return self.peerAlive

    def isBackupActive(self):
        if HealthCheck and HealthCheck.GetBackupActive():
            return True
        return False

    def isMlagInSplitBrain(self):
        if (not self.isPeerAlive() and not self.isBackupActive()):
            return True
        return False

    #
    #   Other interface utility routines
    #
    def isPortName(self, portName):
        '''
        Determine if the argument passed in is the name is a "base" interface
        (not a bond, VLAN subinterface, bridge, etc...)
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(portName)
        info = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_LINKINFO, {})
        kind = info.get(nlmanager.nlpacket.Link.IFLA_INFO_KIND)
        self.ifInfoLock.release()

        return kind in ('bcm_knet', 'tuntap')

    def isIfName(self, ifName):
        '''
        Determine if the argument passed in is the name of an interface
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        self.ifInfoLock.release()
        return idx is not None

    def AnycastIpAddOk(self):
        if HealthCheck and HealthCheck.initDelayBringup():
            self.anycastIpAddDelayed = True
            return False
        return True

    def AddIpAddress(self, nlm, ifName, ipAddressStr, prefix):
        """
        Add an IP address to an interface
        """
        debug = nlmanager.nlpacket.RTM_NEWADDR in nlm.debug
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        self.ifInfoLock.release()
        if ipAddressStr:
            ipAddress = ipaddress.ip_address(ipAddressStr)

        if idx is not None:
            addr = nlmanager.nlpacket.Address(nlmanager.nlpacket.RTM_NEWADDR, debug, Log.logger, Log.getHandlerStr() != "syslog")
            addr.flags = nlmanager.nlpacket.NLM_F_CREATE | nlmanager.nlpacket.NLM_F_REPLACE | nlmanager.nlpacket.NLM_F_REQUEST
            addr.family = socket.AF_INET
            addr.body = struct.pack('=4Bi', socket.AF_INET, prefix, 0, 0, idx)
            addr.add_attribute(nlmanager.nlpacket.Address.IFA_LOCAL, ipAddress)
            addr.add_attribute(nlmanager.nlpacket.Address.IFA_ADDRESS, ipAddress)
            addr.build_message(next(nlm.sequence), nlm.pid)
            Log.log("(%s): Adding IP address %s/%d" % (ifName, ipAddress, prefix))
            nlm.tx_nlpacket_raw(addr.message)

    def DelIpAddress(self, nlm, ifName, ipAddressStr, prefix):
        """
        Remove an IP address from an interface
        """
        debug = nlmanager.nlpacket.RTM_DELADDR in nlm.debug
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        self.ifInfoLock.release()
        if ipAddressStr:
            ipAddress = ipaddress.ip_address(ipAddressStr)

        if idx is not None:
            addr = nlmanager.nlpacket.Address(nlmanager.nlpacket.RTM_DELADDR, debug, Log.logger, Log.getHandlerStr() != "syslog")
            addr.flags = nlmanager.nlpacket.NLM_F_REQUEST
            addr.family = socket.AF_INET
            addr.body = struct.pack('=4Bi', socket.AF_INET, prefix, 0, 0, idx)
            addr.add_attribute(nlmanager.nlpacket.Address.IFA_LOCAL, ipAddress)
            addr.add_attribute(nlmanager.nlpacket.Address.IFA_ADDRESS, ipAddress)
            addr.build_message(next(nlm.sequence), nlm.pid)
            Log.log("(%s): Deleting IP address %s/%d" % (ifName, ipAddress, prefix))
            nlm.tx_nlpacket_raw(addr.message)

    def GetIpAddress(self, ifName):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        ifreq = struct.pack('16sH14s', ifName, socket.AF_INET, '\x00'*14)
        try:
            res = fcntl.ioctl(s.fileno(), 0x8915, ifreq)
        except:
            return None
        ip = struct.unpack('16sH2x4s8x', res)[2]
        return socket.inet_ntoa(ip)

    def GetIpAddressInSameNet(self, ifName, ipAddr):
        v6Family = False
        try:
            ipAddrByte = subprocess.check_output([ "/bin/ip", "addr", "show", ifName ])
        except subprocess.CalledProcessError as e:
            ipAddrByte = e.output
        ipAddrStr = ipAddrByte.decode("utf-8")
        if ipAddr == 'linklocal' or (ipAddr.count(":") > 0):
            if ipAddr == 'linklocal': 
                ipAddr = 'fe80::0'
            v6Family = True
        for ifIpStr in re.findall('\s+inet6?\s+(\S+/\d+)\s+', ipAddrStr):
            if ipaddress.ip_address(ipAddr) in ipaddress.ip_network(ifIpStr, False):
                return str(ipaddress.IPv6Interface(ifIpStr).ip)  if v6Family else \
                            str(ipaddress.IPv4Interface(ifIpStr).ip)
        return None

    def GetBaseInterfaces(self, ifName):
        '''
        Given an interface, returns an unordered set of "base" interfaces which
        are controlled by that interface. A "base" interface is one which does
        not control other (lower) interfaces. Specifically:
           1. For bridges this means the "base" interface(s) of all bridge 
              member interface(s).
           2. For bonds, this means the "base" interface(s) of all enslaved
              interface(s).
           3. For VLAN sub-interfaces, this means the "base" interface(s) of
              the interface from which the VLAN was created.
           4. All other interfaces are base interfaces.
        '''
        if self.isBridgeName(ifName):
            baseIfs = set()
            for bintf in self.GetMembersOfBridge(ifName):
                baseIfs |= self.GetBaseInterfaces(bintf)
            return baseIfs
        elif self.isBondName(ifName):
            baseIfs = set()
            for bintf in self.GetMembersOfBond(ifName):
                baseIfs |= self.GetBaseInterfaces(bintf)
            return baseIfs
        elif self.isSubIfName(ifName):
            baseIfs = set()
            for bintf in self.GetLogicalMastersOfSubIf(ifName):
                baseIfs |= self.GetBaseInterfaces(bintf)
            return baseIfs
        else:
            return {ifName}

    def GetIfMac(self, ifName):
        '''
        Given an interface name, return the MAC address of that interface.
        '''
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        mac = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_ADDRESS)
        self.ifInfoLock.release()
        return mac

    def OperStateToStr(self, operstate):
        """
        Convert an operstate numeric value to a string
        """
        operstate_str = {
            nlmanager.nlpacket.Link.IF_OPER_UNKNOWN        : "unknown",
            nlmanager.nlpacket.Link.IF_OPER_NOTPRESENT     : "notpresent",
            nlmanager.nlpacket.Link.IF_OPER_DOWN           : "down",
            nlmanager.nlpacket.Link.IF_OPER_LOWERLAYERDOWN : "lowerlayerdown",
            nlmanager.nlpacket.Link.IF_OPER_TESTING        : "testing",
            nlmanager.nlpacket.Link.IF_OPER_DORMANT        : "dormant",
            nlmanager.nlpacket.Link.IF_OPER_UP             : "up"
        }
        return operstate_str.get(operstate, "unknown")

    def GetOperState(self, ifName):
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        operstate = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_OPERSTATE)
        self.ifInfoLock.release()
        operstatestr = self.OperStateToStr(operstate)
        if operstate is None:
            operstatestr = self.GetOperStateFromSysFs(ifName)
        return operstatestr

    def GetOperStateFromKCache(self, ifName):
        self.ifInfoLock.acquire()
        idx = self.ifNameToIndex.get(ifName)
        operstate = self.ifInfoByIndex.get(idx, {}).get(nlmanager.nlpacket.Link.IFLA_OPERSTATE)
        self.ifInfoLock.release()
        return self.OperStateToStr(operstate)

    def GetOperStateFromSysFs(self, ifName):
        '''
        Given an interface name, return the operational state
        '''
        if self.isIfName(ifName):
            try:
                for line in open(_InterfacePath + str(ifName) + "/operstate"):
                    return line.strip()
            except IOError:
                pass
        return "unknown"

    def SetOperStateUp(self, nlm, ifName):
        # we only change the oper state if it is dormant
        oldState = self.GetOperStateFromSysFs(ifName)
        if oldState == 'dormant':
            debug = nlmanager.nlpacket.RTM_NEWLINK in nlm.debug
            self.ifInfoLock.acquire()
            if_idx = self.ifNameToIndex.get(ifName)
            self.ifInfoLock.release()

            if if_idx is not None:
                link = nlmanager.nlpacket.Link(nlmanager.nlpacket.RTM_NEWLINK, debug, Log.logger, Log.getHandlerStr() != "syslog")
                link.flags = nlmanager.nlpacket.NLM_F_REQUEST
                link.body = struct.pack('=BxxxiLL', socket.AF_UNSPEC, if_idx, 0, 0)
                link.add_attribute(nlmanager.nlpacket.Link.IFLA_OPERSTATE, nlmanager.nlpacket.Link.IF_OPER_UP)
                link.build_message(next(nlm.sequence), nlm.pid)
                Log.log_debug(Log._LOG_DEBUG_INTF, "(%s): Setting operstate to up" % (ifName,))
                nlm.tx_nlpacket_raw(link.message)

    def ReadyToRunClagConflictChecks(self):
        return self.isPeerAlive() and Parser.syncDoneFromPeer

    def PeerLinkChangeDelayedUpdates(self, nlm):
        Log.log("PeerLinkChange delayed updates")
        if not self.waitAllBondDownEvent.is_set():
            return
        self.waitAllBondDownEvent.clear()
        self.SetProtoPeerOrBackupState(nlm)
        if LacpSync:
            LacpSync.newLacpDataEvent.set()
        if VxLanSync:
            VxLanSync.newVxLanDataEvent.set()
        Parser.ListenersStateUpdate("down")

#-------------------------------------------------------------------------------
#
#   The init code
#
#-------------------------------------------------------------------------------

def ClagInit():
    # Create a log for our output
    global Log
    Log = Logger()

    ThreadMonitor = clag.threadmonitor.ThreadMonitor(Log)
    monitoring_thread = ThreadMonitor.start_monitoring(seconds_frozen=7, test_interval=100)

    Log.log("Beginning execution of clagd version %s" % (ClagParser._ClagVersion,))
    Log.log("Invoked with: " + " ".join(sys.argv))
    util.queueAddNameMap(peerSendQueue, 'peerSendQueue')

    # Create the daemon
    global Daemon
    Daemon = ClagDaemon()

    # Interface utility routines
    nlm = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_INIT_ID)
    global Intf
    Intf = IntfSupport(nlm)

    # Parse the command line parameters
    global Parser
    Parser = ClagParser()
    Parser.ParseCmdLine()
    Parser.DumpParameters()

    # Update the peerProtoBuf value with the passed arg value
    Parser.peerProtoBuf = Parser.args.protobuf

    global system_mac
    system_mac = Parser.args.sysMac

    # Make sure script is invoked with root privledges
    if os.getuid() != 0:
        Log.log_error("You must be root to run this command.")
        print("You must be root to run this command.")
        sys.exit(-1)

    if not Parser.args.backupIp:
        Log.log("Backup IP has not been configured")

def ClagReloadConfig(nlm):
    clagIdCfg = reloadconfig.getClagIdConfig(Log)
    for bond in clagIdCfg:
        clagIdInfo = clagIdCfg[bond]
        Parser.setBondClagId(nlm, bond, clagIdInfo.get('clagId'))
        if Intf.GetOperState(bond) == nlmanager.nlpacket.Link.IFF_DORMANT:
            Intf.SetDormantBondOperState(nlm, bond)
    Parser.setReloadDone()


#-------------------------------------------------------------------------------
#
#   The main runtime code
#
#-------------------------------------------------------------------------------

def ClagRun(nlm):
    Daemon.start()

    # Perform operations based on the command line parameter settings
    if Parser.args.allowPartnerMacDup:
        Log.log_warn("Allowing duplicate LACP partner MACs")
    Log.log("Role is now %s" % (Parser.clagRole,))
    Log.setQuiet(Parser.args.quiet)
    Log.setDebug(Parser.args.debug)
    Log.setVerbose(Parser.args.verbose)
    Log.setHandler(Parser.args.log)

    if Parser.args.csuSupport:
        global csu_client
        csu_client = clagdcsu.CSUClient(Log, Intf, Parser)

    Parser.clagId[0] = Parser.args.priority
    Parser.clagId[1] = Intf.GetIfMac(Parser.args.peerIf)
    Parser.currLacpPoll = Parser.getCfgLacpPoll()
    ConfigurePeerLearning(nlm, True)

    global Cmd
    global FdbSync
    global LacpSync
    global VxLanSync
    global NeighSync
    global HealthCheck
    global ClagNetLink
    global MrouteSync
    global MdbSync
    global VlanSync
    global DataSyncs
    global exitEvent
    ClagNetLink = clag.clagnetlink.ClagNl(Daemon, Log, Intf, Parser)
    FdbSync  = clag.fdbsync.fdbsync(Daemon, Intf, Parser, Log, peerSendQueue, ClagNetLink)
    MdbSync  = clag.mdbsync.mdbsync(Daemon, Intf, Parser, Log, peerSendQueue, ClagNetLink)
    if Parser.args.neighSync:
        NeighSync = clag.neighsync.neighsync(Daemon, Intf, Parser, Log, peerSendQueue, ClagNetLink)
    VlanSync = clag.vlansync.vlansync(Intf, Parser, Log, peerSendQueue)
    LacpSync = clag.lacpsync.lacpsync(Daemon, Intf, Parser, Log, peerSendQueue, FdbSync)
    MrouteSync = clag.mroutesync.mroutesync(Daemon, Intf, Parser, Log, peerSendQueue)
    VxLanSync = clag.vxlansync.vxlansync(Daemon, Intf, Parser, Log, MrouteSync, peerSendQueue)
    DataSyncs = [LacpSync, VxLanSync, FdbSync, MdbSync, VlanSync, MrouteSync]
    if NeighSync:
        DataSyncs.append(NeighSync)
    LacpSync.DataSyncs = DataSyncs
    VxLanSync.DataSyncs = DataSyncs

    exitEvent = clag.clagthread.Event()
    HealthCheck = clag.healthcheck.HealthCheck(Daemon, Parser, Log, Intf, ClagNetLink)
    Cmd = clag.cmdsrv.cmdsrv(Daemon, Intf, Parser, Log, LacpSync, VxLanSync, FdbSync, MdbSync, NeighSync, VlanSync, MrouteSync, HealthCheck)
    LacpSync.Cmd = Cmd
    VxLanSync.Cmd = Cmd

    ClagReloadConfig(nlm)
    if Parser.isRedirectEnabled():
        ConfigureBridgeBackupPort(nlm, True)

    # Create the threads which do the work
    global Threads
    for threadName in Threads:
        Threads[threadName][1] = threading.Thread(None, Threads[threadName][0], threadName)
        Threads[threadName][1].daemon = True

    # Start the threads
    for threadName in Threads:
        Threads[threadName][1].start()

    # Some of the python thread apis use systemd time. If the systemd timestamp
    # changes, the threads are stuck in wait state resulting clagd process timeout.
    # The thread monitors the systemd timestamp and sets the thread events on timestamp change
    sysTimeWatchDog = threading.Thread(None, SysTimeWatchDogT, "SysTimeWatchDogT")
    sysTimeWatchDog.daemon = True
    sysTimeWatchDog.start()

    # Tell systemd that we are initialized and ready
    clagSdnotify.sd_notify(0, bytes("READY=1", "utf-8"))

    # Take the day off
    while not exitEvent.wait(60*60*24):
        pass


#-------------------------------------------------------------------------------
#
#   Main program entry point
#
#-------------------------------------------------------------------------------

def main():
    status = 0
    try:
        nlm = nlmanager.nlmanager.NetlinkManager(util.CLAG_NLM_MAIN_ID)
        ClagRun(nlm)
    except RuntimeError as e:
        if Log:
            Log.log_error(str(e))
        else:
            print((str(e)))
        status = 2
    except KeyboardInterrupt:
        pass
    except Exception:
        (exc_type, exc_value, exc_traceback) = sys.exc_info()
        err = "".join(traceback.format_exception(exc_type, exc_value,
                                                 exc_traceback))
        if "release unlocked lock" not in err:
           if Log:
               Log.log_error("unhandled exception: %s" % (err,))
           else:
               print(("unhandled exception: %s" % (err,)))
           status = 3

    do_exit(nlm, status)


#-------------------------------------------------------------------------------
#
#   Are we being executed or imported?
#
#-------------------------------------------------------------------------------

if __name__ == '__main__':

    ClagInit()

    if Parser.args.daemon:
        context = daemon.DaemonContext(prevent_core = False)
        context.signal_map = {
            signal.SIGHUP:  signal_handler,
            signal.SIGTERM: signal_handler,
            signal.SIGRTMIN: signal_handler,
            signal.SIGRTMIN+1: signal_handler,
            signal.SIGRTMIN+2: signal_handler,
            signal.SIGRTMIN+3: signal_handler
        }
        context.umask = 0o022
        context.pidfile = Daemon.lock
        if Daemon.isClagAlreadyRunning():
            Log.log_error("Unable to start clagd - clagd is already running.")
            sys.exit(-1)
        Daemon.lock.break_lock()
        context.open()
        try:
            with context:
                main()
        except lockfile.NotLocked:
            pass
    else:
        signal.signal(signal.SIGHUP, signal_handler)
        signal.signal(signal.SIGTERM, signal_handler)
        signal.signal(signal.SIGRTMIN, signal_handler)
        signal.signal(signal.SIGRTMIN+1, signal_handler)
        signal.signal(signal.SIGRTMIN+2, signal_handler)
        signal.signal(signal.SIGRTMIN+3, signal_handler)
        main()

