check_bird_ospf 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. #!/usr/bin/python
  2. #
  3. # Check state of OSPF sessions in Bird Internet Routing Daemon
  4. #
  5. # Maximilian Wilhelm <max@rfc2324.org>
  6. # -- Wed 26 Apr 2017 07:26:48 PM CEST
  7. #
  8. import argparse
  9. import os.path
  10. import re
  11. import subprocess
  12. import sys
  13. def read_interfaces_from_file (file_path, missing_ok):
  14. interfaces = []
  15. # If we shouldn't care, we won't care if it's not there.
  16. if not os.path.isfile (file_path) and missing_ok:
  17. return interfaces
  18. try:
  19. with open (args.interfaces_down_ok_file, 'r') as ido_fh:
  20. for iface in ido_fh.readlines ():
  21. if not iface.startswith ('#'):
  22. interfaces.append (iface.strip ())
  23. except IOError as (errno, strerror):
  24. print "Failed to read interfaces_down_ok from '%s': %s" % (args.interfaces_down_ok_file, strerror)
  25. sys.exit (1)
  26. return interfaces
  27. parser = argparse.ArgumentParser (description = 'check bird OSPF sessions')
  28. parser.add_argument ('--proto', '-p', help = 'IP protocol version to check', default = '4', choices = ['4', '6'])
  29. parser.add_argument ('--protocol', '-P', help = 'Bird OSPF protocol instance name to check', default = "")
  30. parser.add_argument ('--interfaces_down_ok', metavar = "LIST", help = "List of interfaces which are OK to have no OSPF neighbor. Provide a space separated list.")
  31. parser.add_argument ('--interfaces_down_ok_file', metavar = "FILENAME", help = "List of interfaces which are OK to have no OSPF neighbor. Provide one interfaces per line.")
  32. parser.add_argument ('--ignore_missing_file', help = "Ignore a possible non-existent file given as --interfaces_down_ok_file", action = 'store_true')
  33. args = parser.parse_args ()
  34. # Are some interfaces ok being down?
  35. interfaces_down_ok = []
  36. if args.interfaces_down_ok:
  37. interfaces_down_ok = args.interfaces_down_ok.split ()
  38. if args.interfaces_down_ok_file:
  39. interfaces_down_ok.extend (read_interfaces_from_file (args.interfaces_down_ok_file, args.ignore_missing_file))
  40. ################################################################################
  41. # Query OSPF protocl information from bird #
  42. ################################################################################
  43. cmds = {
  44. '4' : '/usr/sbin/birdc',
  45. '6' : '/usr/sbin/birdc6',
  46. }
  47. cmd_interfaces = [ "/usr/bin/sudo", cmds[args.proto], "show ospf interface %s" % args.protocol ]
  48. cmd_neighbors = [ "/usr/bin/sudo", cmds[args.proto], "show ospf neighbors %s" % args.protocol ]
  49. try:
  50. interfaces_fh = subprocess.Popen (cmd_interfaces, bufsize = 4194304, stdout = subprocess.PIPE)
  51. if interfaces_fh.returncode:
  52. print "Failed to get OSPF interfaces from bird: %s" % str (" ".join ([line.strip () for line in interfaces_fh.stdout.readlines ()]))
  53. sys.exit (1)
  54. neighbors_fh = subprocess.Popen (cmd_neighbors, bufsize = 4194304, stdout = subprocess.PIPE)
  55. if neighbors_fh.returncode:
  56. print "Failed to get OSPF neighbors from bird: %s" % str (" ".join ([line.strip () for line in neighbors_fh.stdout.readlines ()]))
  57. sys.exit (1)
  58. # cmd exited with non-zero code
  59. except subprocess.CalledProcessError as c:
  60. print "Failed to get OSPF information from bird: %s" % c.output
  61. sys.exit (1)
  62. # This should not have happend.
  63. except Exception as e:
  64. print "Unknown error while getting OSPF information from bird: %s" % str (e)
  65. sys.exit (3)
  66. ################################################################################
  67. # Parse interfaces and neighbors #
  68. ################################################################################
  69. interfaces = {}
  70. interface_re = re.compile (r'^Interface (.+) \(')
  71. state_re = re.compile (r'(Type|State): (.+)$')
  72. stub_re = re.compile (r'\(stub\)')
  73. # Parse interfaces
  74. interface = None
  75. for line in interfaces_fh.stdout.readlines ():
  76. line = line.strip ()
  77. # Create empty interface hash
  78. match = interface_re.search (line)
  79. if match:
  80. interface = match.group (1)
  81. interfaces[interface] = {}
  82. continue
  83. # Store Type and State attributes
  84. match = state_re.search (line)
  85. if match:
  86. interfaces[interface][match.group (1)] = match.group (2)
  87. # Delete any stub interfaces from our list
  88. for iface in interfaces.keys ():
  89. if stub_re.search (interfaces[iface]['State']):
  90. del interfaces[iface]
  91. ok = []
  92. broken = []
  93. down = []
  94. neighbor_re = re.compile (r'^([0-9a-fA-F.:]+)\s+(\d+)\s+([\w/-]+)\s+([0-9:]+)\s+([\w.-]+)\s+([\w.:]+)')
  95. # Read and check all neighbor states
  96. for line in neighbors_fh.stdout.readlines ():
  97. line = line.strip ()
  98. match = neighbor_re.search (line)
  99. if match:
  100. peer = match.group (1)
  101. state = match.group (3)
  102. ifname = match.group (5)
  103. interface = interfaces[ifname]
  104. # Mark interfaces as "up" in bird
  105. interface['up'] = 1
  106. # State FULL is awesome.
  107. if 'Full' in state:
  108. ok.append ("%s/%s" % (ifname, peer))
  109. # In broadcast areas there are only two FULL sessions (to the DR and BDR)
  110. # all other sessions will be 2-Way/Other which is perfectly fine.
  111. elif state == "2-Way/Other" and interface['Type'] == "broadcast":
  112. ok.append ("%s/%s" % (ifname, peer))
  113. # Everything else is considered broken.
  114. # Likely some ExStart/* etc. pointing to possible MTU troubles.
  115. else:
  116. broken.append ("%s/%s:%s" % (ifname, peer, state))
  117. # Check for any interfaces which should have (at least) an OSPF peer
  118. # but don't appear in the neighbors list
  119. for iface in interfaces.keys ():
  120. if iface in interfaces_down_ok:
  121. ok.append ("%s (Down/OK)" % iface)
  122. elif "up" not in interfaces[iface]:
  123. down.append (iface)
  124. ################################################################################
  125. # Prepare output #
  126. ################################################################################
  127. ret_code = 0
  128. # Any down interfaces?
  129. if len (down) > 0:
  130. ret_code = 2
  131. print "DOWN: %s" % ", ".join (sorted (down))
  132. # Any broken sessions?
  133. if len (broken) > 0:
  134. # Issue a warning when there are issues..
  135. if ret_code < 2:
  136. ret_code = 1
  137. print "BROKEN: %s" % ", ".join (sorted (broken))
  138. # And the good ones
  139. if len (ok) > 0:
  140. print "OK: %s" % ", ".join (sorted (ok))
  141. sys.exit (ret_code)