def OnUpdate(ax, axd, ospf_state):
    print('updated bird-ospf state: {0}'.format(time.time()))

    # general info (router id etc)
    axd.RegisterVar('ospfGeneralGroup', 0)
    axd.RegisterVar("ospfRouterId.0", ospf_state["general_info"]["router_id"])
    axd.RegisterVar("ospfAdminStat.0",
                    ospf_state["general_info"]["admin_state"])
    axd.RegisterVar("ospfVersionNumber.0", 2)

    # register ospf areas
    axd.RegisterVar("ospfAreaEntry", 0)
    for area in ospf_state["areas"]:
        axd.RegisterVar("ospfAreaId.%s.0" % area["area_id"],
                        SnmpIpAddress(area["area_id"]))

    # register ospf interfaces
    axd.RegisterVar("ospfIfEntry", 0)
    for interface in ospf_state["interfaces"]:
        axd.RegisterVar("ospfIfIpAddress.%s.0" % interface["ipaddress"],
                        SnmpIpAddress(interface["ipaddress"]))
    for interface in ospf_state["interfaces"]:
        axd.RegisterVar("ospfIfAreaId.%s.0" % interface["ipaddress"],
                        interface["area"])
    for interface in ospf_state["interfaces"]:
        axd.RegisterVar("ospfIfState.%s.0" % interface["ipaddress"],
                        interface["state"])

    # register ospf neighbors
    axd.RegisterVar("ospfNbrEntry", 0)
    for nbrid, nbr in ospf_state['neighbors']:
        axd.RegisterVar("ospfNbrIpAddr.%s.0" % nbrid,
                        SnmpIpAddress(nbr["rtrip"]))
    for nbrid, nbr in ospf_state['neighbors']:
        axd.RegisterVar("ospfNbrRtrId.%s.0" % nbrid, SnmpIpAddress(nbrid))
    for nbrid, nbr in ospf_state['neighbors']:
        axd.RegisterVar("ospfNbrPriority.%s.0" % nbrid, nbr["pri"])
    for nbrid, nbr in ospf_state['neighbors']:
        axd.RegisterVar("ospfNbrState.%s.0" % nbrid, nbr["state"])

    return
Exemple #2
0
def OnUpdate(ax, axd, state):
    def state2int(state):
        if state.lower().startswith("full"):
            return 8
        elif state.lower().startswith("loading"):
            return 7
        elif state.lower().startswith("exchange"):
            return 6
        elif state.lower().startswith("exstart"):
            return 5
        elif state.lower().startswith("2-way"):
            return 4
        elif state.lower().startswith("init"):
            return 3
        elif state.lower().startswith("attempt"):
            return 2
        elif state.lower().startswith("down"):
            return 1
        else:
            return 1

    print('updated bird-ospf state: {0}'.format(time.time()))
    ## register variables
    axd.RegisterVar('ospf', 0)

    # get ip-sorted neighbors
    nbrs = []
    for nbrid in sorted(state["ospf-neighbors"].keys(), BirdAgent.ipCompare):
        nbrs.append((nbrid, state["ospf-neighbors"][nbrid]))

    # register in MIB-sort:
    for nbrid, nbr in nbrs:
        axd.RegisterVar("ospfNbrIpAddr.%s.0" % nbrid,
                        SnmpIpAddress(nbr["rtrip"]))
    for nbrid, nbr in nbrs:
        axd.RegisterVar("ospfNbrRtrId.%s.0" % nbrid, SnmpIpAddress(nbrid))
    for nbrid, nbr in nbrs:
        axd.RegisterVar("ospfNbrPriority.%s.0" % nbrid, nbr["pri"])
    for nbrid, nbr in nbrs:
        axd.RegisterVar("ospfNbrState.%s.0" % nbrid, state2int(nbr["state"]))
    return
class BirdAgent:
    def __init__(self, cfgfile, birdcli, netstatcmd="netstat -na"):
        self.cfgfile = cfgfile
        self.birdcli = birdcli
        self.netstatcmd = netstatcmd

    bgp_states = {
        "idle": 1,
        "connect": 2,
        "active": 3,
        "opensent": 4,
        "openconfirm": 5,
        "established": 6,
    }

    _re_config_include = re.compile("^include\s*\"(/[^\"]*)\".*$")
    _re_config_bgp_proto_begin = re.compile(
        "^protocol bgp ([a-zA-Z0-9_]+) .* \{$")
    _re_config_local_as = re.compile(
        "local ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) as ([0-9]+);")
    _re_config_bgp_holdtime = re.compile("hold time ([0-9]+);")
    _re_config_bgp_keepalive = re.compile("keepalive time ([0-9]+);")
    _re_config_remote_peer = re.compile(
        "neighbor ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) as ([0-9]+);")
    _re_config_timeformat = re.compile("\s*timeformat\s+protocol\s*\"%s\"\s*;")
    _re_config_proto_end = re.compile("^\}$")

    _re_birdcli_bgp_begin = re.compile(
        "([a-zA-Z0-9_]+) *BGP * [a-zA-Z0-9_]+ * [a-zA-Z0-9]+ * ([a-zA-Z0-9:]+) *"
    )
    _re_birdcli_bgp_peer = {
        "bgpPeerIdentifier":
        re.compile("Neighbor ID:.* ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"),
        "bgpPeerState":
        re.compile("BGP state:.* ([a-zA-Z]+)"),
        "bgpPeerLocalAddr":
        re.compile("Source address:.* ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"),
        "bgpPeerRemoteAddr":
        re.compile("Neighbor address:.* ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"),
        "bgpPeerRemoteAs":
        re.compile("Neighbor AS:.* ([0-9]+)"),
        "bgpPeerInUpdates":
        re.compile(
            "Import updates:\ +([0-9]+) .*[0-9\-]+.*[0-9\-]+.*[0-9\-]+.*[0-9\-]+"
        ),
        "bgpPeerOutUpdates":
        re.compile(
            "Export updates:\ +([0-9]+) .*[0-9\-]+.*[0-9\-]+.*[0-9\-]+.*[0-9\-]+"
        ),
        "bgpPeerHoldTime":
        re.compile("Hold timer:.* ([0-9]+)/[0-9]+"),
        "bgpPeerHoldTimeConfigured":
        re.compile("Hold timer:.* [0-9]+/([0-9]+)"),
        "bgpPeerKeepAlive":
        re.compile("Keepalive timer:.* ([0-9]+)/[0-9]+"),
        "bgpPeerKeepAliveConfigured":
        re.compile("Keepalive timer:.* [0-9]+/([0-9]+)"),
        "bgpPeerLastError":
        re.compile("Last error:\ +[a-zA-Z0-9-_\ ]+$")
    }
    _re_birdcli_bgp_end = re.compile("^$")

    _re_birdcli_ospf_neighbor = re.compile(
        "^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+([0-9]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"
    )

    _re_netstat = re.compile(
        "^tcp\s+[0-9]+\s+[0-9]+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+)\s+ESTABLISHED"
    )

    bgp_keys = [
        'bgpPeerIdentifier',
        'bgpPeerState',
        'bgpPeerAdminStatus',
        'bgpPeerNegotiatedVersion',
        'bgpPeerLocalAddr',
        'bgpPeerLocalPort',
        'bgpPeerRemoteAddr',
        'bgpPeerRemotePort',
        'bgpPeerRemoteAs',
        'bgpPeerInUpdates',
        'bgpPeerOutUpdates',
        'bgpPeerInTotalMessages',
        'bgpPeerOutTotalMessages',
        'bgpPeerLastError',
        'bgpPeerFsmEstablishedTransitions',
        'bgpPeerFsmEstablishedTime',
        'bgpPeerConnectRetryInterval',
        'bgpPeerHoldTime',
        'bgpPeerKeepAlive',
        'bgpPeerHoldTimeConfigured',
        'bgpPeerKeepAliveConfigured',
        'bgpPeerMinASOriginationInterval',
        'bgpPeerMinRouteAdvertisementInterval',
        'bgpPeerInUpdateElapsedTime',
    ]

    bgp_defaults = {
        'bgpPeerIdentifier': SnmpIpAddress("0.0.0.0"),
        'bgpPeerLocalAddr': SnmpIpAddress("0.0.0.0"),
        'bgpPeerHoldTime': 0,
        'bgpPeerHoldTimeConfigured': 0,
        'bgpPeerKeepAlive': 0,
        'bgpPeerKeepAliveConfigured': 0,
        'bgpPeerState': 1,
        'bgpPeerInUpdates': SnmpCounter32(0),
        'bgpPeerOutUpdates': SnmpCounter32(0),
        'bgpPeerAdminStatus': 2,
        'bgpPeerConnectRetryInterval': 0,
        'bgpPeerFsmEstablishedTime': SnmpGauge32(0),
        'bgpPeerFsmEstablishedTransitions': SnmpCounter32(0),
        'bgpPeerInTotalMessages': SnmpCounter32(0),
        'bgpPeerInUpdateElapsedTime': SnmpGauge32(0),
        'bgpPeerLastError': '0',
        'bgpPeerMinASOriginationInterval': 15,
        'bgpPeerMinRouteAdvertisementInterval': 30,
        'bgpPeerNegotiatedVersion': 0,
        'bgpPeerOutTotalMessages': SnmpCounter32(0),
    }

    @staticmethod
    def ipCompare(ip1, ip2):
        lst1 = "%3s.%3s.%3s.%3s" % tuple(ip1.split("."))
        lst2 = "%3s.%3s.%3s.%3s" % tuple(ip2.split("."))
        return cmp(lst1, lst2)

    @staticmethod
    def combinedConfigLines(filename):
        """
		yield the whole bird configuration file line by line;
		all include-statements are resolved/unrolled
		"""
        with open(filename, "r") as bird_conf:
            for line in bird_conf:
                line = line.strip()
                match = BirdAgent._re_config_include.search(line)
                if not match:
                    yield line
                else:
                    for subconf in glob.glob(match.group(1)):
                        yield "# subconf: %s (from %s)" % (subconf, line)
                        for subline in BirdAgent.combinedConfigLines(subconf):
                            yield subline

    @staticmethod
    def bgpKeys():
        return BirdAgent.bgp_keys

    def getOSPFState(self, ospf_instance):
        """
		fetch OSPF-related state from:
		* parsing `birdc show ospf neighbors $ospf` output
		"""

        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen([self.birdcli, "show", "ospf", "neighbors", ospf_instance], \
          stdout=subprocess.PIPE)
        output = birdc.communicate()[0]
        if birdc.returncode != 0:
            print("ERROR: bird-CLI (querying ospf neighbors) %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        neighbors = {}
        for line in output.split("\n"):
            match = self._re_birdcli_ospf_neighbor.search(line)
            if match:
                rtrid, pri, state, deadtime, iface, rtrip = match.groups()
                neighbors[rtrid] = {}
                neighbors[rtrid]["pri"] = int(pri)
                neighbors[rtrid]["state"] = state
                neighbors[rtrid]["deadtime"] = deadtime
                neighbors[rtrid]["iface"] = iface
                neighbors[rtrid]["rtrip"] = rtrip
        return {"ospf-neighbors": neighbors}

    def getBGPState(self):
        """
		fetch BGP-related state from:
		* parsing configuration file
		* parsing `birdc show protocols all` output
		* parsing `netstat` output
		"""

        current_time = int(time.time())

        # fetch some data from the configuration:
        cfg = {}
        cfg["bgp-peers"] = {}
        proto = None
        for line in BirdAgent.combinedConfigLines(self.cfgfile):
            if self._re_config_timeformat:
                cfg["timeformat"] = True
            match = self._re_config_bgp_proto_begin.search(line)
            if match:
                proto = match.group(1)
                cfg["bgp-peers"][proto] = {}
            if proto:
                match = self._re_config_local_as.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerLocalAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerLocalAs"] = int(
                        match.group(2))
                    if not cfg.has_key("bgpLocalAs"):
                        cfg["bgpLocalAs"] = int(match.group(2))
                    elif cfg["bgpLocalAs"] != int(match.group(2)):
                        print("WARNING: multiple local AS: %i/%i"% \
                          (cfg["bgpLocalAs"],int(match.group(2))))
                match = self._re_config_remote_peer.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerRemoteAs"] = int(
                        match.group(2))

                match = self._re_config_bgp_holdtime.search(line)
                if match:
                    cfg["bgp-peers"][proto]["bgpPeerHoldTimeConfigured"] = int(
                        match.group(1))

                match = self._re_config_bgp_keepalive.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerKeepAliveConfigured"] = int(match.group(1))

            if self._re_config_proto_end.search(line):
                proto = None
        if not cfg.has_key("timeformat"):
            print("WARNING: timeformat not configured for this agent's use.")

        state = cfg.copy()
        bgp_proto = None
        ospf_proto = None
        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen([self.birdcli, "show", "protocols", "all"], \
          stdout=subprocess.PIPE)
        output = birdc.communicate()[0]
        if birdc.returncode != 0:
            print("ERROR: bird-CLI %s failed: %i" %
                  (self.birdcli, birdc.returncode))
        for line in output.split("\n"):
            match = self._re_birdcli_bgp_begin.search(line)
            if match:
                bgp_proto = match.group(1)
                state["bgp-peers"][bgp_proto] = {}
                timestamp = int(match.group(2))
                if not state["bgp-peers"].has_key(bgp_proto):
                    print("WARNING: proto \"%s\" not in config, skipping" %
                          bgp_proto)
                    continue
                state["bgp-peers"][bgp_proto][
                    "bgpPeerFsmEstablishedTime"] = SnmpGauge32(current_time -
                                                               timestamp)
            if bgp_proto:
                for peerprop_name, peerprop_re in self._re_birdcli_bgp_peer.items(
                ):
                    match = peerprop_re.search(line)
                    if match:
                        if peerprop_name == 'bgpPeerState':
                            state["bgp-peers"][bgp_proto][peerprop_name] = \
                              self.bgp_states[match.group(1).lower()]
                        elif peerprop_name in [
                                'bgpPeerIdentifier', 'bgpPeerLocalAddr',
                                'bgpPeerRemoteAddr'
                        ]:
                            state["bgp-peers"][bgp_proto][
                                peerprop_name] = SnmpIpAddress(match.group(1))
                        elif peerprop_name in [
                                'bgpPeerInUpdates', 'bgpPeerOutUpdates'
                        ]:
                            state["bgp-peers"][bgp_proto][
                                peerprop_name] = SnmpCounter32(match.group(1))
                        else:
                            state["bgp-peers"][bgp_proto][peerprop_name] = int(
                                match.group(1))
            if self._re_birdcli_bgp_end.search(line):
                bgp_proto = None

        # use netstat to query for tcp:179 connections
        bgp_sessions = {}
        netstat = subprocess.Popen( \
          "%s | grep '^tcp.*:179.*ESTABLISHED'"%self.netstatcmd,
          shell=True, stdout=subprocess.PIPE)
        for line in netstat.communicate()[0].split("\n"):
            match = self._re_netstat.search(line)
            if not match:
                continue
            # key 4-tuples by remote ip: src-addr, src-port, dst-addr, dst-port
            bgp_sessions[match.group(3)] = match.groups()

        # now match the tcp:179 4-tuples with bgp-state,
        # and enrich state by local+remote ports
        for proto in state["bgp-peers"].keys():
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = 0
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = 0
            if not bgp_sessions.has_key(
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"]):
                # print("INFO: proto %s has no bgp session."%proto)
                continue
            srcip, srcport, dstip, dstport = bgp_sessions[
                state["bgp-peers"][proto]["bgpPeerRemoteAddr"]]
            if srcip != state["bgp-peers"][proto]["bgpPeerLocalAddr"] or \
              dstip != state["bgp-peers"][proto]["bgpPeerRemoteAddr"]:
                print("WARNING: proto %s has invalid BGP session (%s / %s)" %
                      (proto, srcip, dstip))
                continue
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = int(srcport)
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = int(dstport)

        return state
    def getBGPState(self):
        """
		fetch BGP-related state from:
		* parsing configuration file
		* parsing `birdc show protocols all` output
		* parsing `netstat` output
		"""

        current_time = int(time.time())

        # fetch some data from the configuration:
        cfg = {}
        cfg["bgp-peers"] = {}
        proto = None
        for line in BirdAgent.combinedConfigLines(self.cfgfile):
            if self._re_config_timeformat:
                cfg["timeformat"] = True
            match = self._re_config_bgp_proto_begin.search(line)
            if match:
                proto = match.group(1)
                cfg["bgp-peers"][proto] = {}
            if proto:
                match = self._re_config_local_as.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerLocalAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerLocalAs"] = int(
                        match.group(2))
                    if not cfg.has_key("bgpLocalAs"):
                        cfg["bgpLocalAs"] = int(match.group(2))
                    elif cfg["bgpLocalAs"] != int(match.group(2)):
                        print("WARNING: multiple local AS: %i/%i"% \
                          (cfg["bgpLocalAs"],int(match.group(2))))
                match = self._re_config_remote_peer.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerRemoteAs"] = int(
                        match.group(2))

                match = self._re_config_bgp_holdtime.search(line)
                if match:
                    cfg["bgp-peers"][proto]["bgpPeerHoldTimeConfigured"] = int(
                        match.group(1))

                match = self._re_config_bgp_keepalive.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerKeepAliveConfigured"] = int(match.group(1))

            if self._re_config_proto_end.search(line):
                proto = None
        if not cfg.has_key("timeformat"):
            print("WARNING: timeformat not configured for this agent's use.")

        state = cfg.copy()
        bgp_proto = None
        ospf_proto = None
        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen([self.birdcli, "show", "protocols", "all"], \
          stdout=subprocess.PIPE)
        output = birdc.communicate()[0]
        if birdc.returncode != 0:
            print("ERROR: bird-CLI %s failed: %i" %
                  (self.birdcli, birdc.returncode))
        for line in output.split("\n"):
            match = self._re_birdcli_bgp_begin.search(line)
            if match:
                bgp_proto = match.group(1)
                state["bgp-peers"][bgp_proto] = {}
                timestamp = int(match.group(2))
                if not state["bgp-peers"].has_key(bgp_proto):
                    print("WARNING: proto \"%s\" not in config, skipping" %
                          bgp_proto)
                    continue
                state["bgp-peers"][bgp_proto][
                    "bgpPeerFsmEstablishedTime"] = SnmpGauge32(current_time -
                                                               timestamp)
            if bgp_proto:
                for peerprop_name, peerprop_re in self._re_birdcli_bgp_peer.items(
                ):
                    match = peerprop_re.search(line)
                    if match:
                        if peerprop_name == 'bgpPeerState':
                            state["bgp-peers"][bgp_proto][peerprop_name] = \
                              self.bgp_states[match.group(1).lower()]
                        elif peerprop_name in [
                                'bgpPeerIdentifier', 'bgpPeerLocalAddr',
                                'bgpPeerRemoteAddr'
                        ]:
                            state["bgp-peers"][bgp_proto][
                                peerprop_name] = SnmpIpAddress(match.group(1))
                        elif peerprop_name in [
                                'bgpPeerInUpdates', 'bgpPeerOutUpdates'
                        ]:
                            state["bgp-peers"][bgp_proto][
                                peerprop_name] = SnmpCounter32(match.group(1))
                        else:
                            state["bgp-peers"][bgp_proto][peerprop_name] = int(
                                match.group(1))
            if self._re_birdcli_bgp_end.search(line):
                bgp_proto = None

        # use netstat to query for tcp:179 connections
        bgp_sessions = {}
        netstat = subprocess.Popen( \
          "%s | grep '^tcp.*:179.*ESTABLISHED'"%self.netstatcmd,
          shell=True, stdout=subprocess.PIPE)
        for line in netstat.communicate()[0].split("\n"):
            match = self._re_netstat.search(line)
            if not match:
                continue
            # key 4-tuples by remote ip: src-addr, src-port, dst-addr, dst-port
            bgp_sessions[match.group(3)] = match.groups()

        # now match the tcp:179 4-tuples with bgp-state,
        # and enrich state by local+remote ports
        for proto in state["bgp-peers"].keys():
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = 0
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = 0
            if not bgp_sessions.has_key(
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"]):
                # print("INFO: proto %s has no bgp session."%proto)
                continue
            srcip, srcport, dstip, dstport = bgp_sessions[
                state["bgp-peers"][proto]["bgpPeerRemoteAddr"]]
            if srcip != state["bgp-peers"][proto]["bgpPeerLocalAddr"] or \
              dstip != state["bgp-peers"][proto]["bgpPeerRemoteAddr"]:
                print("WARNING: proto %s has invalid BGP session (%s / %s)" %
                      (proto, srcip, dstip))
                continue
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = int(srcport)
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = int(dstport)

        return state
Exemple #5
0
class BirdAgent(object):
    def __init__(self, cfgfile, birdcli, sscmd):
        self.cfgfile = cfgfile
        self.birdcli = birdcli
        self.sscmd = sscmd

    bgp_states = {
        "idle": 1,
        "connect": 2,
        "active": 3,
        "opensent": 4,
        "openconfirm": 5,
        "established": 6,
    }

    _re_config_include = re.compile("^include\s*\"(/[^\"]*)\".*$")
    _re_config_bgp_proto_begin = re.compile(
        "^protocol bgp ([a-zA-Z0-9_]+).*\{$")
    _re_config_local_as = re.compile(
        "local ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) as ([0-9]+);")
    _re_config_bgp_holdtime = re.compile("hold time ([0-9]+);")
    _re_config_bgp_keepalive = re.compile("keepalive time ([0-9]+);")
    _re_config_remote_peer = re.compile(
        "neighbor ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) as ([0-9]+);")
    _re_config_timeformat = re.compile(
        "\s*timeformat\s+protocol\s+iso\s+long\s+;")
    _re_config_proto_end = re.compile("^\}$")

    _re_birdcli_bgp_begin = re.compile(
        "^([a-zA-Z0-9_]+)\s+BGP\s+[a-zA-Z0-9_]+\s+[a-zA-Z0-9]+\s+(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d).*$"
    )
    _re_birdcli_bgp_peer = {
        "bgpPeerIdentifier":
        re.compile("^\s+Neighbor ID:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$"),
        "bgpPeerState":
        re.compile("^\s+BGP state:\s+([a-zA-Z]+)$"),
        "bgpPeerLocalAddr":
        re.compile("^\s+Source address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$"),
        "bgpPeerRemoteAddr":
        re.compile(
            "^\s+Neighbor address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$"),
        "bgpPeerRemoteAs":
        re.compile("^\s+Neighbor AS:\s+([0-9]+)$"),
        "bgpPeerInUpdates":
        re.compile(
            "^\s+Import updates:\s+([0-9]+)\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+$"
        ),
        "bgpPeerOutUpdates":
        re.compile(
            "^\s+Export updates:\s+([0-9]+)\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+$"
        ),
        "bgpPeerHoldTime":
        re.compile("^\s+Hold timer:\s+([0-9]+)\/[0-9]+$"),
        "bgpPeerHoldTimeConfigured":
        re.compile("^\s+Hold timer:\s+[0-9]+\/([0-9]+)$"),
        "bgpPeerKeepAlive":
        re.compile("^\s+Keepalive timer:\s+([0-9]+)\/[0-9]+$"),
        "bgpPeerKeepAliveConfigured":
        re.compile("^\s+Keepalive timer:\s+[0-9]+\/([0-9]+)$"),
        "bgpPeerLastError":
        re.compile("^\s+Last error:\s+([a-zA-Z0-9-_\ ]+)$")
    }
    _re_birdcli_bgp_end = re.compile("^$")

    _re_birdcli_ospf_routerid = re.compile(
        "^Router ID is\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$")
    _re_birdcli_ospf_proto_status = re.compile(
        "^.+?\s+OSPF\s+.+?\s+(.+?)\s+(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d)\s+(.+?)$"
    )
    _re_birdcli_ospf_area = re.compile(
        "^\s+Area:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+\([0-9]+\)(.+?)?$")
    _re_birdcli_ospf_interfaces = re.compile("^Interface\s+(.+?)\s+\(.+?\)$")
    _re_birdcli_ospf_interface = {
        "state":
        re.compile("^\s+State:\s+([a-zA-Z]+)$"),
        "ipaddress":
        re.compile("^Interface\s+.+?\s+\((.+?)\)$"),
        "area":
        re.compile(
            "^\s+Area:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+\([0-9]+\)(.+?)?$")
    }
    _re_birdcli_ospf_neighbor = re.compile(
        "^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\s+([0-9]+)\s+(\S+)\s+(\S+)\s+(\S+)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)"
    )

    _re_ss = re.compile(
        "^[0-9]+\s+[0-9]+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(?:%[a-z0-9-\.]+)?:([0-9]+)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(?:%[a-z0-9-\.]+?)?:([0-9]+)"
    )

    bgp_keys = [
        'bgpPeerIdentifier',
        'bgpPeerState',
        'bgpPeerAdminStatus',
        'bgpPeerNegotiatedVersion',
        'bgpPeerLocalAddr',
        'bgpPeerLocalPort',
        'bgpPeerRemoteAddr',
        'bgpPeerRemotePort',
        'bgpPeerRemoteAs',
        'bgpPeerInUpdates',
        'bgpPeerOutUpdates',
        'bgpPeerInTotalMessages',
        'bgpPeerOutTotalMessages',
        'bgpPeerLastError',
        'bgpPeerFsmEstablishedTransitions',
        'bgpPeerFsmEstablishedTime',
        'bgpPeerConnectRetryInterval',
        'bgpPeerHoldTime',
        'bgpPeerKeepAlive',
        'bgpPeerHoldTimeConfigured',
        'bgpPeerKeepAliveConfigured',
        'bgpPeerMinASOriginationInterval',
        'bgpPeerMinRouteAdvertisementInterval',
        'bgpPeerInUpdateElapsedTime',
    ]

    bgp_defaults = {
        'bgpPeerIdentifier': SnmpIpAddress("0.0.0.0"),
        'bgpPeerLocalAddr': SnmpIpAddress("0.0.0.0"),
        'bgpPeerLocalPort': 0,
        'bgpPeerRemoteAs': 0,
        'bgpPeerRemotePort': 0,
        'bgpPeerHoldTime': 0,
        'bgpPeerHoldTimeConfigured': 0,
        'bgpPeerKeepAlive': 0,
        'bgpPeerKeepAliveConfigured': 0,
        'bgpPeerState': 1,
        'bgpPeerInUpdates': SnmpCounter32(0),
        'bgpPeerOutUpdates': SnmpCounter32(0),
        'bgpPeerAdminStatus': 2,
        'bgpPeerConnectRetryInterval': 0,
        'bgpPeerFsmEstablishedTime': SnmpGauge32(0),
        'bgpPeerFsmEstablishedTransitions': SnmpCounter32(0),
        'bgpPeerInTotalMessages': SnmpCounter32(0),
        'bgpPeerInUpdateElapsedTime': SnmpGauge32(0),
        'bgpPeerLastError': '0',
        'bgpPeerMinASOriginationInterval': 15,
        'bgpPeerMinRouteAdvertisementInterval': 30,
        'bgpPeerNegotiatedVersion': 0,
        'bgpPeerOutTotalMessages': SnmpCounter32(0),
    }

    @staticmethod
    def ipCompare(ip1, ip2):
        lst1 = "%3s.%3s.%3s.%3s" % tuple(ip1.split("."))
        lst2 = "%3s.%3s.%3s.%3s" % tuple(ip2.split("."))
        return cmp(lst1, lst2)

    @staticmethod
    def combinedConfigLines(filename):
        """
        yield the whole bird configuration file line by line;
        all include-statements are resolved/unrolled
        """
        try:
            with open(filename, "r") as bird_conf:
                for line in bird_conf:
                    line = line.strip()
                    match = BirdAgent._re_config_include.search(line)
                    if not match:
                        yield line
                    else:
                        for subconf in glob.glob(match.group(1)):
                            yield "# subconf: %s (from %s)" % (subconf, line)
                            for subline in BirdAgent.combinedConfigLines(
                                    subconf):
                                yield subline
        except IOError:
            print("ERROR: Unable to open %s, terminating..." % filename)
            sys.exit(1)
        except Exception as e:
            print(
                "ERROR: Unexpected error in combinedConfigLines(): [%s], terminating"
                % e)
            sys.exit(1)

    @staticmethod
    def bgpKeys():
        return BirdAgent.bgp_keys

    def getBGPState(self):
        """
        fetch BGP-related state from:
        * parsing configuration file
        * parsing `birdc show protocols all` output
        * parsing `ss` output
        """

        timezone = get_localzone()
        current_time = datetime.now(pytz.utc)

        # fetch some data from the configuration:
        cfg = {}
        cfg["bgp-peers"] = {}
        proto = None
        for line in BirdAgent.combinedConfigLines(self.cfgfile):
            if self._re_config_timeformat:
                cfg["timeformat"] = True
            match = self._re_config_bgp_proto_begin.search(line)
            if match:
                proto = match.group(1)
                cfg["bgp-peers"][proto] = {}
            if proto:
                match = self._re_config_local_as.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerLocalAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerLocalAs"] = int(
                        match.group(2))
                    if "bgpLocalAs" not in cfg:
                        cfg["bgpLocalAs"] = int(match.group(2))
                    elif cfg["bgpLocalAs"] != int(match.group(2)):
                        print("WARNING: multiple local AS: %i/%i" %
                              (cfg["bgpLocalAs"], int(match.group(2))))

                match = self._re_config_remote_peer.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerRemoteAs"] = int(
                        match.group(2))

                match = self._re_config_bgp_holdtime.search(line)
                if match:
                    cfg["bgp-peers"][proto]["bgpPeerHoldTimeConfigured"] = int(
                        match.group(1))

                match = self._re_config_bgp_keepalive.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerKeepAliveConfigured"] = int(match.group(1))

            if self._re_config_proto_end.search(line):
                proto = None

        if "timeformat" not in cfg:
            print(
                "ERROR: timeformat not configured for this agent's use, terminating..."
            )
            sys.exit(1)

        # Validate protocol's config
        for proto in cfg["bgp-peers"]:
            if "bgpPeerLocalAddr" not in cfg["bgp-peers"][proto] and \
               "bgpPeerLocalAs" not in cfg["bgp-peers"][proto]:
                print(
                    "WARNING: Protocol \"%s\" does not have a properly formated 'local <ip> as <asn>;' line in the config"
                    % proto)

            if "bgpPeerRemoteAddr" not in cfg["bgp-peers"][proto] and \
                    "bgpPeerRemoteAs" not in cfg["bgp-peers"][proto]:
                print(
                    "WARNING: Protocol \"%s\" does not have a properly formated 'neighbor <ip> as <asn>;' line in the config"
                    % proto)

        state = cfg.copy()
        bgp_proto = None
        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen([self.birdcli, "show", "protocols", "all"],
                                 stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        for line in output.split("\n"):
            match = self._re_birdcli_bgp_begin.search(line)
            if match:
                bgp_proto = match.group(1)
                timestamp = dateutil.parser.parse(match.group(2))
                if not timestamp.tzinfo:
                    timestamp = timezone.localize(timestamp)
                if bgp_proto not in state["bgp-peers"]:
                    print("WARNING: proto \"%s\" not in config, skipping" %
                          bgp_proto)
                    continue
                state["bgp-peers"][bgp_proto][
                    "bgpPeerFsmEstablishedTime"] = SnmpGauge32(
                        abs(current_time - timestamp).total_seconds())
            if bgp_proto:
                try:
                    for peerprop_name, peerprop_re in list(
                            self._re_birdcli_bgp_peer.items()):
                        match = peerprop_re.search(line)
                        if match:
                            if peerprop_name == 'bgpPeerState':
                                if not match.group(1).lower() == 'down':
                                    state["bgp-peers"][bgp_proto][peerprop_name] = \
                                        self.bgp_states[match.group(1).lower()]
                                else:
                                    # handle disabled (down) protocols
                                    state["bgp-peers"][bgp_proto][
                                        peerprop_name] = int(1)
                                    state["bgp-peers"][bgp_proto][
                                        "bgpPeerAdminStatus"] = int(1)
                                    state["bgp-peers"][bgp_proto][
                                        "bgpPeerFsmEstablishedTime"] = int(0)

                            elif peerprop_name in [
                                    'bgpPeerIdentifier', 'bgpPeerLocalAddr',
                                    'bgpPeerRemoteAddr'
                            ]:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = SnmpIpAddress(
                                        match.group(1))
                            elif peerprop_name in [
                                    'bgpPeerInUpdates', 'bgpPeerOutUpdates'
                            ]:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = SnmpCounter32(
                                        match.group(1))
                            else:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = int(match.group(1))
                except:
                    print(
                        "WARNING: Unable to process \"%s\" as \"%s\" for protocol \"%s\""
                        % (match.group(1), peerprop_name, bgp_proto))

            if self._re_birdcli_bgp_end.search(line):
                bgp_proto = None

        # use ss to query for source and destination ports of the bgp protocols
        bgp_sessions = {}
        try:
            ss = subprocess.Popen(self.sscmd,
                                  shell=True,
                                  stdout=subprocess.PIPE)

            for line in ss.communicate()[0].decode('utf-8',
                                                   'ignore').split("\n"):
                match = self._re_ss.search(line)
                if not match:
                    continue
                # key 4-tuples by remote ip: src-addr, src-port, dst-addr, dst-port
                bgp_sessions[match.group(3)] = match.groups()
        except subprocess.CalledProcessError as e:
            print(
                "ERROR: Error executing \"ss\" command [%s], terminating..." %
                e)
            sys.exit(1)

        # match the connection 4-tuples with bgp-state
        for proto in list(state["bgp-peers"].keys()):

            # enrich the state by local+remote ports
            try:
                srcip, srcport, dstip, dstport = bgp_sessions[
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"]]
            except:
                print("INFO: Protocol \"%s\" has no active BGP session." %
                      proto)
                try:
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"] = \
                        cfg["bgp-peers"][proto]["bgpPeerRemoteAddr"]
                    continue
                except:
                    state["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress("0.0.0.0")
                    continue

            # Check for mismatch between config and ss output
            if srcip != state["bgp-peers"][proto]["bgpPeerLocalAddr"] or \
                    dstip != state["bgp-peers"][proto]["bgpPeerRemoteAddr"]:
                print(
                    "WARNING: Protocol \"%s\" has mismatch between the configuration file (local: %s, neighbor %s) and the active BGP session (local: %s, neighbor: %s)"
                    % (proto, state["bgp-peers"][proto]["bgpPeerLocalAddr"],
                       state["bgp-peers"][proto]["bgpPeerRemoteAddr"], srcip,
                       dstip))
                continue

            # populate the ports
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = int(srcport)
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = int(dstport)

        return state

    def ospfState2int(self, state):
        if state.lower().startswith("full"):
            return 8
        elif state.lower().startswith("loading"):
            return 7
        elif state.lower().startswith("exchange"):
            return 6
        elif state.lower().startswith("exstart"):
            return 5
        elif state.lower().startswith("2-way"):
            return 4
        elif state.lower().startswith("init"):
            return 3
        elif state.lower().startswith("attempt"):
            return 2
        elif state.lower().startswith("down"):
            return 1
        else:
            return 1

    def getOSPFGeneralInfo(self, ospf_instance):
        """
        fetch OSPF general information from:
        * parsing `birdc show status` output
        * parsing `birdc show proto $ospf_instance` output
        """
        general_info = {}

        birdc = subprocess.Popen([self.birdcli, "show", "status"],
                                 stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI (querying ospf) %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        for line in output.split("\n"):
            match = self._re_birdcli_ospf_routerid.search(line)
            if match:
                general_info["router_id"] = match.groups()[0]

        # get ospf protocol info
        birdc = subprocess.Popen(
            [self.birdcli, "show", "proto", ospf_instance],
            stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI (querying proto ospf) %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        for line in output.split("\n"):
            match = self._re_birdcli_ospf_proto_status.search(line)
            if match:
                admin_state, since, state = match.groups()
                general_info[
                    "admin_state"] = "enabled" if admin_state == "up" else "disabled"
                general_info["up_since"] = since
                general_info["state"] = state

        return general_info

    def getOSPFAreas(self, ospf_instance):
        """
        fetch OSPF general information from:
        * parsing `birdc show ospf $ospf_instance` output
        """
        areas = []

        birdc = subprocess.Popen([self.birdcli, "show", "ospf", ospf_instance],
                                 stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI (querying ospf areas) %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        for line in output.split("\n"):
            match = self._re_birdcli_ospf_area.search(line)
            if match:
                area = {}
                area["area_id"] = match.groups()[0]
                areas.append(area)

        return areas

    def getOSPFInterfaces(self, ospf_instance):
        """
        fetch OSPF general information from:
        * parsing `birdc show ospf interface $ospf_instance` output
        """
        interfaces = []

        birdc = subprocess.Popen(
            [self.birdcli, "show", "ospf", "interface", ospf_instance],
            stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI (querying ospf interfaces) %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        # create a list of all interfaces
        for line in output.split("\n"):
            match = self._re_birdcli_ospf_interfaces.search(line)
            if match:
                interface = {}
                interface["interface_name"] = match.groups()[0]
                interfaces.append(interface)

        # get detail information
        for interface in interfaces:
            birdc = subprocess.Popen([
                self.birdcli, "show", "ospf", "interface", ospf_instance,
                "\"%s\"" % interface["interface_name"]
            ],
                                     stdout=subprocess.PIPE)
            output = birdc.communicate()[0].decode('utf-8', 'ignore')
            if birdc.returncode != 0:
                print(
                    "ERROR: bird-CLI (querying ospf interfaces) %s failed: %i"
                    % (self.birdcli, birdc.returncode))

            for line in output.split("\n"):
                matchipaddress = self._re_birdcli_ospf_interface[
                    "ipaddress"].search(line)
                if matchipaddress:
                    interface["ipaddress"] = matchipaddress.groups()[0].split(
                        '/', 1)[0]

                matcharea = self._re_birdcli_ospf_interface["area"].search(
                    line)
                if matcharea:
                    interface["area"] = matcharea.groups()[0]

                matchstate = self._re_birdcli_ospf_interface["state"].search(
                    line)
                if matchstate:
                    interface["state"] = matchstate.groups()[0]

        return interfaces

    def getOSPFNeighbors(self, ospf_instance):
        """
        fetch OSPF-neighbors from:
        * parsing `birdc show ospf neighbors $ospf_instance` output
        """

        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen(
            [self.birdcli, "show", "ospf", "neighbors", ospf_instance],
            stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI (querying ospf neighbors) %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        neighbors = {}
        for line in output.split("\n"):
            match = self._re_birdcli_ospf_neighbor.search(line)
            if match:
                rtrid, pri, state, deadtime, iface, rtrip = match.groups()
                neighbors[rtrid] = {}
                neighbors[rtrid]["pri"] = int(pri)
                neighbors[rtrid]["state"] = self.ospfState2int(state)
                neighbors[rtrid]["deadtime"] = deadtime
                neighbors[rtrid]["iface"] = iface
                neighbors[rtrid]["rtrip"] = rtrip

        # ip-sorted neighbors
        neighbors_sorted = []
        for nbrid in sorted(neighbors.keys(),
                            key=functools.cmp_to_key(self.ipCompare)):
            neighbors_sorted.append((nbrid, neighbors[nbrid]))

        return neighbors_sorted

    def getOSPFState(self, ospf_instance):
        """
        fetch OSPF state information
        """
        ospf_state = {}
        ospf_state["general_info"] = self.getOSPFGeneralInfo(ospf_instance)
        ospf_state["areas"] = self.getOSPFAreas(ospf_instance)
        ospf_state["interfaces"] = self.getOSPFInterfaces(ospf_instance)
        ospf_state["neighbors"] = self.getOSPFNeighbors(ospf_instance)

        return ospf_state
Exemple #6
0
    def getBGPState(self):
        """
        fetch BGP-related state from:
        * parsing configuration file
        * parsing `birdc show protocols all` output
        * parsing `ss` output
        """

        timezone = get_localzone()
        current_time = datetime.now(pytz.utc)

        # fetch some data from the configuration:
        cfg = {}
        cfg["bgp-peers"] = {}
        proto = None
        for line in BirdAgent.combinedConfigLines(self.cfgfile):
            if self._re_config_timeformat:
                cfg["timeformat"] = True
            match = self._re_config_bgp_proto_begin.search(line)
            if match:
                proto = match.group(1)
                cfg["bgp-peers"][proto] = {}
            if proto:
                match = self._re_config_local_as.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerLocalAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerLocalAs"] = int(
                        match.group(2))
                    if "bgpLocalAs" not in cfg:
                        cfg["bgpLocalAs"] = int(match.group(2))
                    elif cfg["bgpLocalAs"] != int(match.group(2)):
                        print("WARNING: multiple local AS: %i/%i" %
                              (cfg["bgpLocalAs"], int(match.group(2))))

                match = self._re_config_remote_peer.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerRemoteAs"] = int(
                        match.group(2))

                match = self._re_config_bgp_holdtime.search(line)
                if match:
                    cfg["bgp-peers"][proto]["bgpPeerHoldTimeConfigured"] = int(
                        match.group(1))

                match = self._re_config_bgp_keepalive.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerKeepAliveConfigured"] = int(match.group(1))

            if self._re_config_proto_end.search(line):
                proto = None

        if "timeformat" not in cfg:
            print(
                "ERROR: timeformat not configured for this agent's use, terminating..."
            )
            sys.exit(1)

        # Validate protocol's config
        for proto in cfg["bgp-peers"]:
            if "bgpPeerLocalAddr" not in cfg["bgp-peers"][proto] and \
               "bgpPeerLocalAs" not in cfg["bgp-peers"][proto]:
                print(
                    "WARNING: Protocol \"%s\" does not have a properly formated 'local <ip> as <asn>;' line in the config"
                    % proto)

            if "bgpPeerRemoteAddr" not in cfg["bgp-peers"][proto] and \
                    "bgpPeerRemoteAs" not in cfg["bgp-peers"][proto]:
                print(
                    "WARNING: Protocol \"%s\" does not have a properly formated 'neighbor <ip> as <asn>;' line in the config"
                    % proto)

        state = cfg.copy()
        bgp_proto = None
        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen([self.birdcli, "show", "protocols", "all"],
                                 stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        for line in output.split("\n"):
            match = self._re_birdcli_bgp_begin.search(line)
            if match:
                bgp_proto = match.group(1)
                timestamp = dateutil.parser.parse(match.group(2))
                if not timestamp.tzinfo:
                    timestamp = timezone.localize(timestamp)
                if bgp_proto not in state["bgp-peers"]:
                    print("WARNING: proto \"%s\" not in config, skipping" %
                          bgp_proto)
                    continue
                state["bgp-peers"][bgp_proto][
                    "bgpPeerFsmEstablishedTime"] = SnmpGauge32(
                        abs(current_time - timestamp).total_seconds())
            if bgp_proto:
                try:
                    for peerprop_name, peerprop_re in list(
                            self._re_birdcli_bgp_peer.items()):
                        match = peerprop_re.search(line)
                        if match:
                            if peerprop_name == 'bgpPeerState':
                                if not match.group(1).lower() == 'down':
                                    state["bgp-peers"][bgp_proto][peerprop_name] = \
                                        self.bgp_states[match.group(1).lower()]
                                else:
                                    # handle disabled (down) protocols
                                    state["bgp-peers"][bgp_proto][
                                        peerprop_name] = int(1)
                                    state["bgp-peers"][bgp_proto][
                                        "bgpPeerAdminStatus"] = int(1)
                                    state["bgp-peers"][bgp_proto][
                                        "bgpPeerFsmEstablishedTime"] = int(0)

                            elif peerprop_name in [
                                    'bgpPeerIdentifier', 'bgpPeerLocalAddr',
                                    'bgpPeerRemoteAddr'
                            ]:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = SnmpIpAddress(
                                        match.group(1))
                            elif peerprop_name in [
                                    'bgpPeerInUpdates', 'bgpPeerOutUpdates'
                            ]:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = SnmpCounter32(
                                        match.group(1))
                            else:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = int(match.group(1))
                except:
                    print(
                        "WARNING: Unable to process \"%s\" as \"%s\" for protocol \"%s\""
                        % (match.group(1), peerprop_name, bgp_proto))

            if self._re_birdcli_bgp_end.search(line):
                bgp_proto = None

        # use ss to query for source and destination ports of the bgp protocols
        bgp_sessions = {}
        try:
            ss = subprocess.Popen(self.sscmd,
                                  shell=True,
                                  stdout=subprocess.PIPE)

            for line in ss.communicate()[0].decode('utf-8',
                                                   'ignore').split("\n"):
                match = self._re_ss.search(line)
                if not match:
                    continue
                # key 4-tuples by remote ip: src-addr, src-port, dst-addr, dst-port
                bgp_sessions[match.group(3)] = match.groups()
        except subprocess.CalledProcessError as e:
            print(
                "ERROR: Error executing \"ss\" command [%s], terminating..." %
                e)
            sys.exit(1)

        # match the connection 4-tuples with bgp-state
        for proto in list(state["bgp-peers"].keys()):

            # enrich the state by local+remote ports
            try:
                srcip, srcport, dstip, dstport = bgp_sessions[
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"]]
            except:
                print("INFO: Protocol \"%s\" has no active BGP session." %
                      proto)
                try:
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"] = \
                        cfg["bgp-peers"][proto]["bgpPeerRemoteAddr"]
                    continue
                except:
                    state["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress("0.0.0.0")
                    continue

            # Check for mismatch between config and ss output
            if srcip != state["bgp-peers"][proto]["bgpPeerLocalAddr"] or \
                    dstip != state["bgp-peers"][proto]["bgpPeerRemoteAddr"]:
                print(
                    "WARNING: Protocol \"%s\" has mismatch between the configuration file (local: %s, neighbor %s) and the active BGP session (local: %s, neighbor: %s)"
                    % (proto, state["bgp-peers"][proto]["bgpPeerLocalAddr"],
                       state["bgp-peers"][proto]["bgpPeerRemoteAddr"], srcip,
                       dstip))
                continue

            # populate the ports
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = int(srcport)
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = int(dstport)

        return state
class BirdAgent(object):
    def __init__(self, cfgfile, birdcli, sscmd):
        self.cfgfile = cfgfile
        self.birdcli = birdcli
        self.sscmd = sscmd

    bgp_states = {
        "idle": 1,
        "connect": 2,
        "active": 3,
        "opensent": 4,
        "openconfirm": 5,
        "established": 6,
    }

    _re_config_include = re.compile("^include\s*\"(/[^\"]*)\".*$")
    _re_config_bgp_proto_begin = re.compile(
        "^protocol bgp ([a-zA-Z0-9_]+).*\{$")
    _re_config_local_as = re.compile(
        "local ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) as ([0-9]+);")
    _re_config_bgp_holdtime = re.compile("hold time ([0-9]+);")
    _re_config_bgp_keepalive = re.compile("keepalive time ([0-9]+);")
    _re_config_remote_peer = re.compile(
        "neighbor ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) as ([0-9]+);")
    _re_config_timeformat = re.compile(
        "\s*timeformat\s+protocol\s+iso\s+long\s+;")
    _re_config_proto_end = re.compile("^\}$")

    _re_birdcli_bgp_begin = re.compile(
        "^([a-zA-Z0-9_]+)\s+BGP\s+[a-zA-Z0-9-_]+\s+[a-zA-Z0-9]+\s+(\d\d\d\d-\d\d-\d\d\s\d\d:\d\d:\d\d).*$"
    )
    _re_birdcli_bgp_peer = {
        "bgpPeerIdentifier":
        re.compile("^\s+Neighbor ID:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$"),
        "bgpPeerState":
        re.compile("^\s+BGP state:\s+([a-zA-Z]+)$"),
        "bgpPeerLocalAddr":
        re.compile("^\s+Source address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$"),
        "bgpPeerRemoteAddr":
        re.compile(
            "^\s+Neighbor address:\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$"),
        "bgpPeerRemoteAs":
        re.compile("^\s+Neighbor AS:\s+([0-9]+)$"),
        "bgpPeerInUpdates":
        re.compile(
            "^\s+Import updates:\s+([0-9]+)\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+$"
        ),
        "bgpPeerOutUpdates":
        re.compile(
            "^\s+Export updates:\s+([0-9]+)\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+\s+[0-9\-]+$"
        ),
        "bgpPeerHoldTime":
        re.compile("^\s+Hold timer:\s+([0-9]+)\/[0-9]+$"),
        "bgpPeerHoldTimeConfigured":
        re.compile("^\s+Hold timer:\s+[0-9]+\/([0-9]+)$"),
        "bgpPeerKeepAlive":
        re.compile("^\s+Keepalive timer:\s+([0-9]+)\/[0-9]+$"),
        "bgpPeerKeepAliveConfigured":
        re.compile("^\s+Keepalive timer:\s+[0-9]+\/([0-9]+)$"),
        "bgpPeerLastError":
        re.compile("^\s+Last error:\s+([a-zA-Z0-9-_\ ]+)$")
    }
    _re_birdcli_bgp_end = re.compile("^$")

    _re_ss = re.compile(
        "^[0-9]+\s+[0-9]+\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(?:%[a-z0-9-\.]+)?:([0-9]+)\s+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(?:%[a-z0-9-\.]+?)?:([0-9]+)"
    )

    bgp_keys = [
        'bgpPeerIdentifier',
        'bgpPeerState',
        'bgpPeerAdminStatus',
        'bgpPeerNegotiatedVersion',
        'bgpPeerLocalAddr',
        'bgpPeerLocalPort',
        'bgpPeerRemoteAddr',
        'bgpPeerRemotePort',
        'bgpPeerRemoteAs',
        'bgpPeerInUpdates',
        'bgpPeerOutUpdates',
        'bgpPeerInTotalMessages',
        'bgpPeerOutTotalMessages',
        'bgpPeerLastError',
        'bgpPeerFsmEstablishedTransitions',
        'bgpPeerFsmEstablishedTime',
        'bgpPeerConnectRetryInterval',
        'bgpPeerHoldTime',
        'bgpPeerKeepAlive',
        'bgpPeerHoldTimeConfigured',
        'bgpPeerKeepAliveConfigured',
        'bgpPeerMinASOriginationInterval',
        'bgpPeerMinRouteAdvertisementInterval',
        'bgpPeerInUpdateElapsedTime',
    ]

    bgp_defaults = {
        'bgpPeerIdentifier': SnmpIpAddress("0.0.0.0"),
        'bgpPeerLocalAddr': SnmpIpAddress("0.0.0.0"),
        'bgpPeerLocalPort': 0,
        'bgpPeerRemoteAs': 0,
        'bgpPeerRemotePort': 0,
        'bgpPeerHoldTime': 0,
        'bgpPeerHoldTimeConfigured': 0,
        'bgpPeerKeepAlive': 0,
        'bgpPeerKeepAliveConfigured': 0,
        'bgpPeerState': 1,
        'bgpPeerInUpdates': SnmpCounter32(0),
        'bgpPeerOutUpdates': SnmpCounter32(0),
        'bgpPeerAdminStatus': 2,
        'bgpPeerConnectRetryInterval': 0,
        'bgpPeerFsmEstablishedTime': SnmpGauge32(0),
        'bgpPeerFsmEstablishedTransitions': SnmpCounter32(0),
        'bgpPeerInTotalMessages': SnmpCounter32(0),
        'bgpPeerInUpdateElapsedTime': SnmpGauge32(0),
        'bgpPeerLastError': '0',
        'bgpPeerMinASOriginationInterval': 15,
        'bgpPeerMinRouteAdvertisementInterval': 30,
        'bgpPeerNegotiatedVersion': 0,
        'bgpPeerOutTotalMessages': SnmpCounter32(0),
    }

    @staticmethod
    def ipCompare(ip1, ip2):
        lst1 = "%3s.%3s.%3s.%3s" % tuple(ip1.split("."))
        lst2 = "%3s.%3s.%3s.%3s" % tuple(ip2.split("."))
        return cmp(lst1, lst2)

    @staticmethod
    def combinedConfigLines(filename):
        """
        yield the whole bird configuration file line by line;
        all include-statements are resolved/unrolled
        """
        try:
            with open(filename, "r") as bird_conf:
                for line in bird_conf:
                    line = line.strip()
                    match = BirdAgent._re_config_include.search(line)
                    if not match:
                        yield line
                    else:
                        for subconf in glob.glob(match.group(1)):
                            yield "# subconf: %s (from %s)" % (subconf, line)
                            for subline in BirdAgent.combinedConfigLines(
                                    subconf):
                                yield subline
        except IOError:
            print("ERROR: Unable to open %s, terminating..." % filename)
            sys.exit(1)
        except Exception as e:
            print(
                "ERROR: Unexpected error in combinedConfigLines(): [%s], terminating"
                % e)
            sys.exit(1)

    @staticmethod
    def bgpKeys():
        return BirdAgent.bgp_keys

    def getBGPState(self):
        """
        fetch BGP-related state from:
        * parsing configuration file
        * parsing `birdc show protocols all` output
        * parsing `ss` output
        """

        timezone = get_localzone()
        current_time = datetime.now(pytz.utc)

        # fetch some data from the configuration:
        cfg = {}
        cfg["bgp-peers"] = {}
        proto = None
        for line in BirdAgent.combinedConfigLines(self.cfgfile):
            if self._re_config_timeformat:
                cfg["timeformat"] = True
            match = self._re_config_bgp_proto_begin.search(line)
            if match:
                proto = match.group(1)
                cfg["bgp-peers"][proto] = {}
            if proto:
                match = self._re_config_local_as.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerLocalAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerLocalAs"] = int(
                        match.group(2))
                    if "bgpLocalAs" not in cfg:
                        cfg["bgpLocalAs"] = int(match.group(2))
                    elif cfg["bgpLocalAs"] != int(match.group(2)):
                        print("WARNING: multiple local AS: %i/%i" %
                              (cfg["bgpLocalAs"], int(match.group(2))))

                match = self._re_config_remote_peer.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress(match.group(1))
                    cfg["bgp-peers"][proto]["bgpPeerRemoteAs"] = int(
                        match.group(2))

                match = self._re_config_bgp_holdtime.search(line)
                if match:
                    cfg["bgp-peers"][proto]["bgpPeerHoldTimeConfigured"] = int(
                        match.group(1))

                match = self._re_config_bgp_keepalive.search(line)
                if match:
                    cfg["bgp-peers"][proto][
                        "bgpPeerKeepAliveConfigured"] = int(match.group(1))

            if self._re_config_proto_end.search(line):
                proto = None

        if "timeformat" not in cfg:
            print(
                "ERROR: timeformat not configured for this agent's use, terminating..."
            )
            sys.exit(1)

        # Validate protocol's config
        for proto in cfg["bgp-peers"]:
            if not cfg["bgp-peers"][proto]: continue
            if "bgpPeerLocalAddr" not in cfg["bgp-peers"][proto] and \
               "bgpPeerLocalAs" not in cfg["bgp-peers"][proto]:
                print(
                    "WARNING: Protocol \"%s\" does not have a properly formated 'local <ip> as <asn>;' line in the config"
                    % proto)

            if "bgpPeerRemoteAddr" not in cfg["bgp-peers"][proto] and \
                    "bgpPeerRemoteAs" not in cfg["bgp-peers"][proto]:
                print(
                    "WARNING: Protocol \"%s\" does not have a properly formated 'neighbor <ip> as <asn>;' line in the config"
                    % proto)

        state = cfg.copy()
        bgp_proto = None
        # "with"-context-manager for Popen not available in python < 3.2
        birdc = subprocess.Popen([self.birdcli, "show", "protocols", "all"],
                                 stdout=subprocess.PIPE)
        output = birdc.communicate()[0].decode('utf-8', 'ignore')
        if birdc.returncode != 0:
            print("ERROR: bird-CLI %s failed: %i" %
                  (self.birdcli, birdc.returncode))

        for line in output.split("\n"):
            match = self._re_birdcli_bgp_begin.search(line)
            if match:
                bgp_proto = match.group(1)
                timestamp = dateutil.parser.parse(match.group(2))
                if not timestamp.tzinfo:
                    timestamp = timezone.localize(timestamp)
                if bgp_proto not in state["bgp-peers"]:
                    print("WARNING: proto \"%s\" not in config, skipping" %
                          bgp_proto)
                    continue
                state["bgp-peers"][bgp_proto][
                    "bgpPeerFsmEstablishedTime"] = SnmpGauge32(
                        abs(current_time - timestamp).total_seconds())
            if bgp_proto:
                try:
                    for peerprop_name, peerprop_re in list(
                            self._re_birdcli_bgp_peer.items()):
                        match = peerprop_re.search(line)
                        if match:
                            if peerprop_name == 'bgpPeerState':
                                if not match.group(1).lower() == 'down':
                                    state["bgp-peers"][bgp_proto][peerprop_name] = \
                                        self.bgp_states[match.group(1).lower()]
                                else:
                                    # handle disabled (down) protocols
                                    state["bgp-peers"][bgp_proto][
                                        peerprop_name] = int(1)
                                    state["bgp-peers"][bgp_proto][
                                        "bgpPeerAdminStatus"] = int(1)
                                    state["bgp-peers"][bgp_proto][
                                        "bgpPeerFsmEstablishedTime"] = int(0)

                            elif peerprop_name in [
                                    'bgpPeerIdentifier', 'bgpPeerLocalAddr',
                                    'bgpPeerRemoteAddr'
                            ]:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = SnmpIpAddress(
                                        match.group(1))
                            elif peerprop_name in [
                                    'bgpPeerInUpdates', 'bgpPeerOutUpdates'
                            ]:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = SnmpCounter32(
                                        match.group(1))
                            else:
                                state["bgp-peers"][bgp_proto][
                                    peerprop_name] = int(match.group(1))
                except:
                    print(
                        "WARNING: Unable to process \"%s\" as \"%s\" for protocol \"%s\""
                        % (match.group(1), peerprop_name, bgp_proto))

            if self._re_birdcli_bgp_end.search(line):
                bgp_proto = None

        # use ss to query for source and destination ports of the bgp protocols
        bgp_sessions = {}
        try:
            ss = subprocess.Popen(self.sscmd,
                                  shell=True,
                                  stdout=subprocess.PIPE)

            for line in ss.communicate()[0].decode('utf-8',
                                                   'ignore').split("\n"):
                match = self._re_ss.search(line)
                if not match:
                    continue
                # key 4-tuples by remote ip: src-addr, src-port, dst-addr, dst-port
                bgp_sessions[match.group(3)] = match.groups()
        except subprocess.CalledProcessError as e:
            print(
                "ERROR: Error executing \"ss\" command [%s], terminating..." %
                e)
            sys.exit(1)

        # match the connection 4-tuples with bgp-state
        for proto in list(state["bgp-peers"].keys()):
            if not state["bgp-peers"][proto]: continue

            # enrich the state by local+remote ports
            try:
                srcip, srcport, dstip, dstport = bgp_sessions[
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"]]
            except:
                print("INFO: Protocol \"%s\" has no active BGP session." %
                      proto)
                try:
                    state["bgp-peers"][proto]["bgpPeerRemoteAddr"] = \
                        cfg["bgp-peers"][proto]["bgpPeerRemoteAddr"]
                    continue
                except:
                    state["bgp-peers"][proto][
                        "bgpPeerRemoteAddr"] = SnmpIpAddress("0.0.0.0")
                    continue

            # Check for mismatch between config and ss output
            if srcip != state["bgp-peers"][proto]["bgpPeerLocalAddr"] or \
                    dstip != state["bgp-peers"][proto]["bgpPeerRemoteAddr"]:
                print(
                    "WARNING: Protocol \"%s\" has mismatch between the configuration file (local: %s, neighbor %s) and the active BGP session (local: %s, neighbor: %s)"
                    % (proto, state["bgp-peers"][proto]["bgpPeerLocalAddr"],
                       state["bgp-peers"][proto]["bgpPeerRemoteAddr"], srcip,
                       dstip))
                continue

            # populate the ports
            state["bgp-peers"][proto]["bgpPeerLocalPort"] = int(srcport)
            state["bgp-peers"][proto]["bgpPeerRemotePort"] = int(dstport)

        return state