dnmapper/backend/cmk_parser.py

355 lines
11 KiB
Python

#!/usr/bin/env python
# This file is part of dnmapper, an AS--level mapping tool
# Licensed under GNU General Public License v3 or later
# Written by Sebastian Lohff (seba@someserver.de)
from __future__ import print_function
from collections import OrderedDict
import re
import socket
from backend.exceptions import RouterParserException
def err(msg):
raise RouterParserException(msg)
def getBGPData(ip, asno):
rawData = getDataFromHost(ip)
if not rawData:
err("Could not get data from host (empty response)")
router = parseBGPData(rawData, asno)
router["ip"] = ip
return router
def getDataFromHost(ip):
socket.setdefaulttimeout(5)
x = socket.socket()
x.connect((ip, 6556))
f = x.makefile()
data = f.read()
x.close()
return data
def parseBGPData(raw, asno):
d = re.search(r"(?:^|\n)<<<(quagga|bird)>>>\n(.*?)(?:$|<<<[^\n]+>>>)", raw, re.DOTALL)
if not d:
err("Data not found in check mk output")
# mkify
raw = d.group(2).split("\n")
arr = list(filter(lambda _z: _z, map(lambda _y: list(filter(lambda _x: _x, re.split(r"\s+", _y))), raw)))
# parse for bird/quagga
result = None
if d.group(1) == "quagga":
result = parseQuagga(arr, raw, asno)
else:
result = parseBird(arr, raw, asno)
return result
def parseQuagga(data, raw, asno):
status = _quaggaFindCommand(data, "show ip bgp sum")
if status[0][0:3] == ['IPv4', 'Unicast', 'Summary:']:
del(status[0])
if status[0][0:3] != ['BGP', 'router', 'identifier']:
print(status)
err("Couldn't find router id in quagga output")
peers = _quaggaFindNeighbors(data)
if asno and int(asno) != int(status[0][7]):
err("AS number (%s) does not match as number from quagga (%s)" % (asno, status[0][7]))
routes = _quaggaFindRoutes(raw)
return {"local_id": status[0][3].strip(","), "local_as": int(status[0][7]), "peers": peers, "routes": routes}
def parseBird(data, raw, asno):
status = _birdFindTable(data, "show status")
if status[2][0] != "1011-Router":
err("Couldn't find router id in bird output")
peers = list(filter(lambda _x: _x["type"] == "BGP", _birdMakeProtocols(data)))
if asno == None:
err("Host is bird")
# FIXME
routes = _birdFindRoutes(data)
return {"local_id": status[2][3], "local_as": int(asno), "peers": peers, "routes": routes}
def _birdFindTable(info, command):
""" find command output of a bird command, e.g. "show bgp neighbors" """
command = ["bird>"] + command.split(" ")
commandInfo = []
editNextLine = False
for line in info:
if not commandInfo:
if line == command:
commandInfo.append(line)
editNextLine = True
else:
if editNextLine:
editNextLine = False
commandInfo.append(line[1:])
elif line[0] == "bird>":
return commandInfo
else:
commandInfo.append(line)
return []
def _birdFindProtocols(info):
""" return a list of tuples (protoname, protoinfo) """
protocolTable = _birdFindTable(info, "show protocols all")
protocols = OrderedDict()
currProto = None
for line in protocolTable[2:]:
if line[0][0:4] == "1002":
currProto = line[0][5:]
protocols[currProto] = [[currProto] + line[1:]]
elif currProto == None:
err("No proto selected, couldn't parse line:", line)
else:
protocols[currProto].append(line)
return protocols
def _birdMakeProtocols(info):
""" Parse birds show protocols all output """
# proto: name, type, description, state (up/down?), up-since
# routes imported, exported, preferred
# also: routing stats (
# bgp special stuff: state, neighbor (address, as, id) (id not available when down)
# state (established, active)
# if error, last error is avilable
protocols = []
for proto, data in _birdFindProtocols(info).items():
protoInfo = {
"name": proto,
"type": data[0][1],
"table": data[0][2],
"state": data[0][3],
"last_change": data[0][4],
"info": " ".join(data[0][5:]),
"description": " ".join(data[1][2:]),
"routes": {
"imported": data[5][1],
"exported": data[5][3],
"preferred": data[5][5],
}
}
if protoInfo["type"] == "BGP":
found = False
for n, line in enumerate(data):
if line[0:2] == ["BGP", "state:"]:
found = True
protoInfo["BGP"] = {
"state": data[n][2],
"online": data[n][2] == "Established",
"neighbor_address": data[n+1][2],
"neighbor_as": int(data[n+2][2]),
"neighbor_id": data[n+3][2] if len(data) > n+3 and data[n+3][0:2] == ["Neighbor", "ID:"] else None,
"last_error": " ".join(data[n+3][2:]) if len(data) > n+3 and data[n+3][0:2] == ["Last", "error:"] else None,
}
if not found:
protoInfo["BGP"] = None
protocols.append(protoInfo)
return protocols
def _birdFindRoutes(info):
output = _birdFindTable(info, "show route all")
if len(output) < 1:
# no data found
return None
def handleCandidate(routes, candidate):
if candidate:
# path, nexthop, network
for key in ["path", "nexthop", "network", "iBGP"]:
if key not in candidate:
return
route = {"prefix": candidate["network"], "nexthop": candidate["nexthop"],
"path": list(map(int, candidate["path"])), "iBGP": candidate["iBGP"]}
routes.append(route)
routes = []
candidate = None
lastIP = None
for line in output:
if line[0].startswith("1007-"):
# new route!
handleCandidate(routes, candidate)
if line[0] != "1007-":
# line has a network, use it!
lastIP = line[0][5:]
candidate = {"network": lastIP, "iBGP": None}
elif candidate is not None:
# search bgp attributes
if line[0] == "1012-":
pass
k, v = line[1], line[2:]
else:
k, v = line[0], line[1:]
k = k.rstrip(":")
if k == "BGP.next_hop":
candidate["nexthop"] = v[0]
elif k == "BGP.as_path":
candidate["path"] = v
handleCandidate(routes, candidate)
return routes
def _quaggaFindCommand(info, cmd):
# ['core-frunde#', 'show', 'ip', 'bgp', 'sum']
# ['core-frunde#', 'show', 'ip', 'bgp', 'neighbors']
output = []
cmd = cmd.split(" ")
prompt = None
for line in info:
if line[1:] == cmd:
prompt = line[0]
elif line[0] == prompt:
# done
return output
elif prompt != None:
output.append(line)
err("Could not find command '%s' in output" % " ".join(cmd))
def _quaggaFindNeighbors(info):
#['BGP', 'neighbor', 'is', '10.50.1.2,', 'remote', 'AS', '65001,', 'local', 'AS', '65001,', 'internal', 'link']
output = _quaggaFindCommand(info, "show ip bgp neighbors")
start = ["BGP", "neighbor", "is"]
curr = None
rawNeighbors = []
for line in output:
if line[0:3] == start:
if curr:
rawNeighbors.append(curr)
curr = [line]
elif curr:
curr.append(line)
else:
err("Could not find start of neighbors")
if curr:
rawNeighbors.append(curr)
curr = None
neighbors = []
neighborDict = OrderedDict()
for raw in rawNeighbors:
descrIdx = 1 if raw[1][0] == "Description:" else 0
if raw[descrIdx + 1][0] == "Hostname:":
descrIdx += 1
peerdict = {
"neighbor_address": raw[0][3].rstrip(","),
"neighbor_as": int(raw[0][6].rstrip(",")),
"local_as": int(raw[0][9].rstrip(",")),
"description": " ".join(raw[1][1:]) if descrIdx else "No description",
"neighbor_id": raw[1+descrIdx][6].strip(","),
"state": raw[2+descrIdx][3].strip(","),
"routes": {
"imported": 0,
},
"BGP": {
"state": raw[2+descrIdx][3].strip(","),
"online": raw[2+descrIdx][3].strip(",") == "Established",
"neighbor_id": raw[1+descrIdx][6].strip(","),
"neighbor_address": raw[0][3].rstrip(","),
"neighbor_as": int(raw[0][6].rstrip(",")),
"state": raw[2+descrIdx][3].strip(","),
},
}
for line in raw:
if line[1:3] == ["accepted", "prefixes"]:
# woooo
peerdict["routes"]["imported"] = int(line[0])
break
neighbors.append(peerdict)
neighborDict[peerdict["neighbor_address"]] = peerdict
return neighbors
def _quaggaFindRoutes(raw):
# from # show ip bgp to Total number of prefixes XX
# BGP table version is 0, local router ID is 10.50.0.1
# *> 10.3.14.0/27 10.75.0.22 0 65002 65112 i
cmdre = re.compile(r"^([^\s#]+#) show ip bgp$")
routere = re.compile(r"^(?P<status>.)(?P<status2>.)(?P<origin>.)(?P<network>[0-9./]+)?\s+(?P<nexthop>[0-9./]+)[\s0-9i?]+$")
# find output
output = []
prompt = None
for line in raw:
if not prompt:
m = cmdre.match(line)
if m:
prompt = m.group(1) + " "
else:
if line.startswith(prompt):
break
else:
output.append(line)
if len(output) < 1:
# no data found
return None
routes = []
foundTable = False
lastIP = None
for line in output:
if not foundTable:
if line.endswith("Metric LocPrf Weight Path"):
foundTable = True
else:
if line != '':
if line.startswith("Total number of prefixes") or line.startswith("Displayed "):
break
else:
# parse one route line
#print(line)
m = routere.match(line)
d = m.groupdict()
if d["network"]:
lastIP = d["network"]
else:
d["network"] = lastIP
# "parse" path (everything after 61 chars, but no i)
path = list(filter(lambda _x: _x not in ('', 'i'), line[61:].split(" ")))
# currently skip incomplete routes
if '?' not in path:
route = {"prefix": d["network"], "nexthop": d["nexthop"],
"path": list(map(int, path)), "iBGP": d["origin"] == "i"}
routes.append(route)
return routes