check_bird_ospf 6.3 KB

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