check_bird_ospf 6.5 KB

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