No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

cmk_parser.py 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. #!/usr/bin/env python
  2. # This file is part of dnmapper, an AS--level mapping tool
  3. # Licensed under GNU General Public License v3 or later
  4. # Written by Sebastian Lohff (seba@someserver.de)
  5. from __future__ import print_function
  6. from collections import OrderedDict
  7. import re
  8. import socket
  9. from backend.exceptions import RouterParserException
  10. def err(msg):
  11. raise RouterParserException(msg)
  12. def getBGPData(ip, asno):
  13. rawData = getDataFromHost(ip)
  14. if not rawData:
  15. err("Could not get data from host (empty response)")
  16. router = parseBGPData(rawData, asno)
  17. router["ip"] = ip
  18. return router
  19. def getDataFromHost(ip):
  20. socket.setdefaulttimeout(5)
  21. x = socket.socket()
  22. x.connect((ip, 6556))
  23. f = x.makefile()
  24. data = f.read()
  25. x.close()
  26. return data
  27. def parseBGPData(raw, asno):
  28. d = re.search(r"(?:^|\n)<<<(quagga|bird)>>>\n(.*?)(?:$|<<<[^\n]+>>>)", raw, re.DOTALL)
  29. if not d:
  30. err("Data not found in check mk output")
  31. # mkify
  32. raw = d.group(2).split("\n")
  33. arr = list(filter(lambda _z: _z, map(lambda _y: list(filter(lambda _x: _x, re.split(r"\s+", _y))), raw)))
  34. # parse for bird/quagga
  35. result = None
  36. if d.group(1) == "quagga":
  37. result = parseQuagga(arr, raw, asno)
  38. else:
  39. result = parseBird(arr, raw, asno)
  40. return result
  41. def parseQuagga(data, raw, asno):
  42. status = _quaggaFindCommand(data, "show ip bgp sum")
  43. if status[0][0:3] == ['IPv4', 'Unicast', 'Summary:']:
  44. del(status[0])
  45. if status[0][0:3] != ['BGP', 'router', 'identifier']:
  46. print(status)
  47. err("Couldn't find router id in quagga output")
  48. peers = _quaggaFindNeighbors(data)
  49. if asno and int(asno) != int(status[0][7]):
  50. err("AS number (%s) does not match as number from quagga (%s)" % (asno, status[0][7]))
  51. routes = _quaggaFindRoutes(raw)
  52. return {"local_id": status[0][3].strip(","), "local_as": int(status[0][7]), "peers": peers, "routes": routes}
  53. def parseBird(data, raw, asno):
  54. status = _birdFindTable(data, "show status")
  55. if status[2][0] != "1011-Router":
  56. err("Couldn't find router id in bird output")
  57. peers = list(filter(lambda _x: _x["type"] == "BGP", _birdMakeProtocols(data)))
  58. if asno == None:
  59. err("Host is bird")
  60. # FIXME
  61. routes = _birdFindRoutes(data)
  62. return {"local_id": status[2][3], "local_as": int(asno), "peers": peers, "routes": routes}
  63. def _birdFindTable(info, command):
  64. """ find command output of a bird command, e.g. "show bgp neighbors" """
  65. command = ["bird>"] + command.split(" ")
  66. commandInfo = []
  67. editNextLine = False
  68. for line in info:
  69. if not commandInfo:
  70. if line == command:
  71. commandInfo.append(line)
  72. editNextLine = True
  73. else:
  74. if editNextLine:
  75. editNextLine = False
  76. commandInfo.append(line[1:])
  77. elif line[0] == "bird>":
  78. return commandInfo
  79. else:
  80. commandInfo.append(line)
  81. return []
  82. def _birdFindProtocols(info):
  83. """ return a list of tuples (protoname, protoinfo) """
  84. protocolTable = _birdFindTable(info, "show protocols all")
  85. protocols = OrderedDict()
  86. currProto = None
  87. for line in protocolTable[2:]:
  88. if line[0][0:4] == "1002":
  89. currProto = line[0][5:]
  90. protocols[currProto] = [[currProto] + line[1:]]
  91. elif currProto == None:
  92. err("No proto selected, couldn't parse line:", line)
  93. else:
  94. protocols[currProto].append(line)
  95. return protocols
  96. def _birdMakeProtocols(info):
  97. """ Parse birds show protocols all output """
  98. # proto: name, type, description, state (up/down?), up-since
  99. # routes imported, exported, preferred
  100. # also: routing stats (
  101. # bgp special stuff: state, neighbor (address, as, id) (id not available when down)
  102. # state (established, active)
  103. # if error, last error is avilable
  104. protocols = []
  105. for proto, data in _birdFindProtocols(info).items():
  106. protoInfo = {
  107. "name": proto,
  108. "type": data[0][1],
  109. "table": data[0][2],
  110. "state": data[0][3],
  111. "last_change": data[0][4],
  112. "info": " ".join(data[0][5:]),
  113. "description": " ".join(data[1][2:]),
  114. "routes": {
  115. "imported": data[5][1],
  116. "exported": data[5][3],
  117. "preferred": data[5][5],
  118. }
  119. }
  120. if protoInfo["type"] == "BGP":
  121. found = False
  122. for n, line in enumerate(data):
  123. if line[0:2] == ["BGP", "state:"]:
  124. found = True
  125. protoInfo["BGP"] = {
  126. "state": data[n][2],
  127. "online": data[n][2] == "Established",
  128. "neighbor_address": data[n+1][2],
  129. "neighbor_as": int(data[n+2][2]),
  130. "neighbor_id": data[n+3][2] if len(data) > n+3 and data[n+3][0:2] == ["Neighbor", "ID:"] else None,
  131. "last_error": " ".join(data[n+3][2:]) if len(data) > n+3 and data[n+3][0:2] == ["Last", "error:"] else None,
  132. }
  133. if not found:
  134. protoInfo["BGP"] = None
  135. protocols.append(protoInfo)
  136. return protocols
  137. def _birdFindRoutes(info):
  138. output = _birdFindTable(info, "show route all")
  139. if len(output) < 1:
  140. # no data found
  141. return None
  142. def handleCandidate(routes, candidate):
  143. if candidate:
  144. # path, nexthop, network
  145. for key in ["path", "nexthop", "network", "iBGP"]:
  146. if key not in candidate:
  147. return
  148. route = {"prefix": candidate["network"], "nexthop": candidate["nexthop"],
  149. "path": list(map(int, candidate["path"])), "iBGP": candidate["iBGP"]}
  150. routes.append(route)
  151. routes = []
  152. candidate = None
  153. lastIP = None
  154. for line in output:
  155. if line[0].startswith("1007-"):
  156. # new route!
  157. handleCandidate(routes, candidate)
  158. if line[0] != "1007-":
  159. # line has a network, use it!
  160. lastIP = line[0][5:]
  161. candidate = {"network": lastIP, "iBGP": None}
  162. elif candidate is not None:
  163. # search bgp attributes
  164. if line[0] == "1012-":
  165. pass
  166. k, v = line[1], line[2:]
  167. else:
  168. k, v = line[0], line[1:]
  169. k = k.rstrip(":")
  170. if k == "BGP.next_hop":
  171. candidate["nexthop"] = v[0]
  172. elif k == "BGP.as_path":
  173. candidate["path"] = v
  174. handleCandidate(routes, candidate)
  175. return routes
  176. def _quaggaFindCommand(info, cmd):
  177. # ['core-frunde#', 'show', 'ip', 'bgp', 'sum']
  178. # ['core-frunde#', 'show', 'ip', 'bgp', 'neighbors']
  179. output = []
  180. cmd = cmd.split(" ")
  181. prompt = None
  182. for line in info:
  183. if line[1:] == cmd:
  184. prompt = line[0]
  185. elif line[0] == prompt:
  186. # done
  187. return output
  188. elif prompt != None:
  189. output.append(line)
  190. err("Could not find command '%s' in output" % " ".join(cmd))
  191. def _quaggaFindNeighbors(info):
  192. #['BGP', 'neighbor', 'is', '10.50.1.2,', 'remote', 'AS', '65001,', 'local', 'AS', '65001,', 'internal', 'link']
  193. output = _quaggaFindCommand(info, "show ip bgp neighbors")
  194. start = ["BGP", "neighbor", "is"]
  195. curr = None
  196. rawNeighbors = []
  197. for line in output:
  198. if line[0:3] == start:
  199. if curr:
  200. rawNeighbors.append(curr)
  201. curr = [line]
  202. elif curr:
  203. curr.append(line)
  204. else:
  205. err("Could not find start of neighbors")
  206. if curr:
  207. rawNeighbors.append(curr)
  208. curr = None
  209. neighbors = []
  210. neighborDict = OrderedDict()
  211. for raw in rawNeighbors:
  212. descrIdx = 1 if raw[1][0] == "Description:" else 0
  213. if raw[descrIdx + 1][0] == "Hostname:":
  214. descrIdx += 1
  215. peerdict = {
  216. "neighbor_address": raw[0][3].rstrip(","),
  217. "neighbor_as": int(raw[0][6].rstrip(",")),
  218. "local_as": int(raw[0][9].rstrip(",")),
  219. "description": " ".join(raw[1][1:]) if descrIdx else "No description",
  220. "neighbor_id": raw[1+descrIdx][6].strip(","),
  221. "state": raw[2+descrIdx][3].strip(","),
  222. "routes": {
  223. "imported": 0,
  224. },
  225. "BGP": {
  226. "state": raw[2+descrIdx][3].strip(","),
  227. "online": raw[2+descrIdx][3].strip(",") == "Established",
  228. "neighbor_id": raw[1+descrIdx][6].strip(","),
  229. "neighbor_address": raw[0][3].rstrip(","),
  230. "neighbor_as": int(raw[0][6].rstrip(",")),
  231. "state": raw[2+descrIdx][3].strip(","),
  232. },
  233. }
  234. for line in raw:
  235. if line[1:3] == ["accepted", "prefixes"]:
  236. # woooo
  237. peerdict["routes"]["imported"] = int(line[0])
  238. break
  239. neighbors.append(peerdict)
  240. neighborDict[peerdict["neighbor_address"]] = peerdict
  241. return neighbors
  242. def _quaggaFindRoutes(raw):
  243. # from # show ip bgp to Total number of prefixes XX
  244. # BGP table version is 0, local router ID is 10.50.0.1
  245. # *> 10.3.14.0/27 10.75.0.22 0 65002 65112 i
  246. cmdre = re.compile(r"^([^\s#]+#) show ip bgp$")
  247. routere = re.compile(r"^(?P<status>.)(?P<status2>.)(?P<origin>.)(?P<network>[0-9./]+)?\s+(?P<nexthop>[0-9./]+)[\s0-9i?]+$")
  248. # find output
  249. output = []
  250. prompt = None
  251. for line in raw:
  252. if not prompt:
  253. m = cmdre.match(line)
  254. if m:
  255. prompt = m.group(1) + " "
  256. else:
  257. if line.startswith(prompt):
  258. break
  259. else:
  260. output.append(line)
  261. if len(output) < 1:
  262. # no data found
  263. return None
  264. routes = []
  265. foundTable = False
  266. lastIP = None
  267. for line in output:
  268. if not foundTable:
  269. if line.endswith("Metric LocPrf Weight Path"):
  270. foundTable = True
  271. else:
  272. if line != '':
  273. if line.startswith("Total number of prefixes") or line.startswith("Displayed "):
  274. break
  275. else:
  276. # parse one route line
  277. #print(line)
  278. m = routere.match(line)
  279. d = m.groupdict()
  280. if d["network"]:
  281. lastIP = d["network"]
  282. else:
  283. d["network"] = lastIP
  284. # "parse" path (everything after 61 chars, but no i)
  285. path = list(filter(lambda _x: _x not in ('', 'i'), line[61:].split(" ")))
  286. # currently skip incomplete routes
  287. if '?' not in path:
  288. route = {"prefix": d["network"], "nexthop": d["nexthop"],
  289. "path": list(map(int, path)), "iBGP": d["origin"] == "i"}
  290. routes.append(route)
  291. return routes