#!/usr/bin/python3 # # Check state of OSPF sessions in Bird Internet Routing Daemon # # Maximilian Wilhelm # -- Wed 26 Apr 2017 07:26:48 PM CEST # from __future__ import print_function import argparse import os.path import re import subprocess import sys def read_interfaces_from_file (file_path, missing_ok): interfaces = [] # If we shouldn't care, we won't care if it's not there. if not os.path.isfile (file_path) and missing_ok: return interfaces try: with open (args.interfaces_down_ok_file, 'r') as ido_fh: for iface in ido_fh.readlines (): if not iface.startswith ('#'): interfaces.append (iface.strip ()) except IOError as err: errno, strerror = err.args print ("Failed to read interfaces_down_ok from '%s': %s" % (args.interfaces_down_ok_file, strerror)) sys.exit (1) return interfaces def format_peer (ifname, iface_config, peer): global args if args.no_ptp_ip and iface_config['Type'] == 'ptp': return ifname return "%s/%s" % (ifname, peer) parser = argparse.ArgumentParser (description = 'check bird OSPF sessions') parser.add_argument ('--proto', '-p', help = 'IP protocol version to check', default = '4', choices = ['4', '6']) parser.add_argument ('--protocol', '-P', help = 'Bird OSPF protocol instance name to check', default = "") 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.") 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.") parser.add_argument ('--ignore_missing_file', help = "Ignore a possible non-existent file given as --interfaces_down_ok_file", action = 'store_true') parser.add_argument ('--no_ptp_ip', help = "Do not print router ID/IP on ptp interfaces", action = 'store_true') args = parser.parse_args () # Are some interfaces ok being down? interfaces_down_ok = [] if args.interfaces_down_ok: interfaces_down_ok = args.interfaces_down_ok.split () if args.interfaces_down_ok_file: interfaces_down_ok.extend (read_interfaces_from_file (args.interfaces_down_ok_file, args.ignore_missing_file)) ################################################################################ # Query OSPF protocl information from bird # ################################################################################ cmds = { '4' : '/usr/sbin/birdc', '6' : '/usr/sbin/birdc6', } cmd_interfaces = [ "/usr/bin/sudo", cmds[args.proto], "show ospf interface %s" % args.protocol ] cmd_neighbors = [ "/usr/bin/sudo", cmds[args.proto], "show ospf neighbors %s" % args.protocol ] try: interfaces_fh = subprocess.Popen (cmd_interfaces, bufsize = 4194304, stdout = subprocess.PIPE) if interfaces_fh.returncode: print ("Failed to get OSPF interfaces from bird: %s" % str (" ".join ([line.strip () for line in interfaces_fh.stdout.readlines ()]))) sys.exit (1) neighbors_fh = subprocess.Popen (cmd_neighbors, bufsize = 4194304, stdout = subprocess.PIPE) if neighbors_fh.returncode: print ("Failed to get OSPF neighbors from bird: %s" % str (" ".join ([line.strip () for line in neighbors_fh.stdout.readlines ()]))) sys.exit (1) # cmd exited with non-zero code except subprocess.CalledProcessError as c: print ("Failed to get OSPF information from bird: %s" % c.output) sys.exit (1) # This should not have happend. except Exception as e: print ("Unknown error while getting OSPF information from bird: %s" % str (e)) sys.exit (3) ################################################################################ # Parse interfaces and neighbors # ################################################################################ interfaces = {} interface_re = re.compile (r'^Interface (.+) \(') state_re = re.compile (r'(Type|State): (.+)$') stub_re = re.compile (r'\(stub\)') # Parse interfaces interface = None for line in interfaces_fh.stdout.readlines (): line = line.strip () # Python3 glue if sys.version_info >= (3, 0): line = str (line, encoding='utf-8') # Create empty interface hash match = interface_re.search (line) if match: interface = match.group (1) interfaces[interface] = {} continue # Store Type and State attributes match = state_re.search (line) if match: interfaces[interface][match.group (1)] = match.group (2) # Delete any stub interfaces from our list for iface in list (interfaces): if stub_re.search (interfaces[iface]['State']): del interfaces[iface] ok = [] broken = [] down = [] neighbor_re = re.compile (r'^([0-9a-fA-F.:]+)\s+(\d+)\s+([\w/-]+)\s+([0-9:]+)\s+([\w.-]+)\s+([\w.:]+)') # Read and check all neighbor states for line in neighbors_fh.stdout.readlines (): line = line.strip () # Python3 glue if sys.version_info >= (3, 0): line = str (line, encoding='utf-8') match = neighbor_re.search (line) if match: peer = match.group (1) state = match.group (3) ifname = match.group (5) interface = interfaces[ifname] # Mark interfaces as "up" in bird interface['up'] = 1 # State FULL is awesome. if 'Full' in state: ok.append (format_peer (ifname, interface, peer)) # In broadcast areas there are only two FULL sessions (to the DR and BDR) # all other sessions will be 2-Way/Other which is perfectly fine. elif state == "2-Way/Other" and interface['Type'] == "broadcast": ok.append (format_peer (ifname, interface, peer)) # Everything else is considered broken. # Likely some ExStart/* etc. pointing to possible MTU troubles. else: broken.append ("%s:%s" % (format_peer (ifname, interface, peer), state)) # Check for any interfaces which should have (at least) an OSPF peer # but don't appear in the neighbors list for iface in interfaces.keys (): if iface in interfaces_down_ok: ok.append ("%s (Down/OK)" % iface) elif "up" not in interfaces[iface]: down.append (iface) ################################################################################ # Prepare output # ################################################################################ ret_code = 0 # Any down interfaces? if len (down) > 0: ret_code = 2 print ("DOWN: %s" % ", ".join (sorted (down))) # Any broken sessions? if len (broken) > 0: # Issue a warning when there are issues.. if ret_code < 2: ret_code = 1 print ("BROKEN: %s" % ", ".join (sorted (broken))) # And the good ones if len (ok) > 0: print ("OK: %s" % ", ".join (sorted (ok))) sys.exit (ret_code)