2023-04-21 23:54:20 -06:00
#!/usr/bin/env python3
import argparse
import sys
import time
import traceback
2023-05-26 17:06:27 -06:00
from ipaddress import ip_address , ip_network
2023-04-21 23:54:20 -06:00
import numpy as np
import requests
from urllib3 . exceptions import InsecureRequestWarning
import checker . nagios as nagios
from checker . markdown import list_to_markdown_table
2023-04-21 23:54:20 -06:00
from checker . units import filesize
2023-04-21 23:54:20 -06:00
2023-05-26 17:06:27 -06:00
def is_internet_traffic ( ip ) :
private_networks = [
ip_network ( " 10.0.0.0/8 " ) ,
ip_network ( " 172.16.0.0/12 " ) ,
ip_network ( " 192.168.0.0/16 " ) ,
]
return not any ( ip in network for network in private_networks )
def get_traffic_top ( args , interface ) :
response = requests . get ( f ' https:// { args . opnsense } /api/diagnostics/traffic/top/ { interface } ' ,
headers = { ' Accept ' : ' application/json ' } , auth = ( args . key , args . secret ) , verify = False ,
timeout = 10 )
if response . status_code != 200 :
print ( f ' UNKNOWN: unable to query OPNsense API for { interface } : { response . status_code } \n { response . text } ' )
sys . exit ( nagios . UNKNOWN )
return response . json ( )
2023-04-21 23:54:20 -06:00
def main ( ) :
parser = argparse . ArgumentParser ( description = ' Check OPNsense network traffic for a host. ' )
parser . add_argument ( ' --opnsense ' , required = True , help = ' OPNsense hostname or IP address. ' )
parser . add_argument ( ' --key ' , required = True , help = ' OPNsense API key. ' )
parser . add_argument ( ' --secret ' , required = True , help = ' OPNsense API secret. ' )
2023-05-06 14:51:50 -06:00
parser . add_argument ( ' --interface ' , required = True , help = ' Interface to check (e.g., lan). Can be something like " lan,wan " ' )
2023-04-21 23:54:20 -06:00
parser . add_argument ( ' --host ' , required = True , help = ' Address of the host to check. ' )
2023-04-21 23:54:20 -06:00
parser . add_argument ( ' --duration ' , default = 10 , type = int , help = ' How many seconds to gather statistics. ' )
2023-05-06 14:51:50 -06:00
parser . add_argument ( ' --fail-empty ' , action = ' store_true ' , help = ' If the API did not return any data, fail with UNKNOWN. Otherwise, assume that there was no traffic. ' )
parser . add_argument ( ' --bandwidth ' , type = float , required = True , help = ' Bandwidth speed in Mbps. Used to calculate percentage. ' )
2023-05-06 15:55:46 -06:00
parser . add_argument ( ' --bandwidth-critical ' , type = int , default = 75 , help = ' Critical if percent of bandwidth usage is greater than or equal to this. ' )
parser . add_argument ( ' --bandwidth-warn ' , type = int , default = 50 , help = ' Warning if percent of bandwidth usage is greater than or equal to this. ' )
parser . add_argument ( ' --conn-critical ' , type = int , default = - 1 , help = ' Set critical level for number of connections. Default: -1 (disabled). ' )
parser . add_argument ( ' --conn-warn ' , type = int , default = - 1 , help = ' Set warning level for number of connections. Default: -1 (disabled). ' )
2023-04-21 23:54:20 -06:00
args = parser . parse_args ( )
check_result = { }
interface_names = { }
requests . packages . urllib3 . disable_warnings ( category = InsecureRequestWarning )
# Map interface names to their internal names
interfaces_mapping = requests . get ( f ' https:// { args . opnsense } /api/diagnostics/traffic/interface ' ,
2023-05-03 12:10:40 -06:00
headers = { ' Accept ' : ' application/json ' } , auth = ( args . key , args . secret ) ,
verify = False , timeout = 10 )
2023-04-21 23:54:20 -06:00
if interfaces_mapping . status_code != 200 :
2023-05-03 12:10:40 -06:00
print (
f ' UNKNOWN: unable to query OPNsense API for interface mappings: { interfaces_mapping . status_code } \n { interfaces_mapping . text } ' )
2023-04-21 23:54:20 -06:00
sys . exit ( nagios . UNKNOWN )
interfaces_mapping = interfaces_mapping . json ( ) [ ' interfaces ' ]
interfaces_to_check = set ( args . interface . split ( ' , ' ) )
for name , interface in interfaces_mapping . items ( ) :
if interface [ ' name ' ] in interfaces_to_check :
interfaces_to_check . remove ( interface [ ' name ' ] )
interface_names [ interface [ ' name ' ] ] = name
if not len ( interface_names . keys ( ) ) :
print ( f ' UNKNOWN: did not find any valid interface names! Double-check the name. ' )
sys . exit ( nagios . UNKNOWN )
for name , interface in interface_names . items ( ) :
# Fetch the data
traffic_data = [ ]
for _ in range ( args . duration ) :
2023-05-26 17:06:27 -06:00
start_time = time . time ( )
2023-04-21 23:54:20 -06:00
response = requests . get ( f ' https:// { args . opnsense } /api/diagnostics/traffic/top/ { interface } ' ,
2023-05-03 12:10:40 -06:00
headers = { ' Accept ' : ' application/json ' } , auth = ( args . key , args . secret ) , verify = False ,
timeout = 10 )
2023-05-26 17:06:27 -06:00
end_time = time . time ( )
api_request_time = end_time - start_time
2023-04-21 23:54:20 -06:00
if response . status_code != 200 :
print ( f ' UNKNOWN: unable to query OPNsense API for { interface } : { response . status_code } \n { response . text } ' )
sys . exit ( nagios . UNKNOWN )
for item in response . json ( ) . get ( interface , { } ) . get ( ' records ' , False ) :
if item [ ' address ' ] == args . host :
traffic_data . append ( item )
2023-05-26 17:06:27 -06:00
adjusted_sleep_duration = max ( 1 - api_request_time , 0 )
time . sleep ( adjusted_sleep_duration )
2023-04-21 23:54:20 -06:00
2023-05-03 12:10:40 -06:00
if not len ( traffic_data ) and args . fail_empty :
print ( ' UNKNOWN: Interface or host not found in OPNsense API response. Raw response: ' )
print ( traffic_data )
sys . exit ( nagios . UNKNOWN )
elif not len ( traffic_data ) :
2023-05-06 14:51:50 -06:00
# There was no traffic.
2023-05-03 12:10:40 -06:00
check_result [ name ] = {
2023-05-03 12:48:44 -06:00
' rate_in ' : 0 ,
' rate_out ' : 0 ,
' cumulative_in ' : 0 ,
' cumulative_out ' : 0 ,
2023-05-03 14:07:59 -06:00
' connections ' : 0
2023-05-03 12:12:07 -06:00
}
2023-05-03 12:48:44 -06:00
else :
try :
check_result [ name ] = {
' rate_in ' : np . average ( [ x [ ' rate_bits_in ' ] for x in traffic_data ] ) ,
' rate_out ' : np . average ( [ x [ ' rate_bits_out ' ] for x in traffic_data ] ) ,
' cumulative_in ' : np . average ( [ x [ ' cumulative_bytes_in ' ] for x in traffic_data ] ) ,
' cumulative_out ' : np . average ( [ x [ ' cumulative_bytes_out ' ] for x in traffic_data ] ) ,
' connections ' : int ( np . average ( [ len ( x [ ' details ' ] ) for x in traffic_data ] ) )
}
except Exception as e :
print ( f ' UNKNOWN: Failed to parse traffic data: " { e } " ' )
print ( traceback . format_exc ( ) )
print ( ' ' )
print ( ' Raw data: ' )
print ( traffic_data )
sys . exit ( nagios . UNKNOWN )
2023-04-21 23:54:20 -06:00
2023-05-06 15:55:46 -06:00
warn_b_value = ( args . bandwidth * args . bandwidth_warn / 100 ) * 1e+6
crit_b_value = ( args . bandwidth * args . bandwidth_critical / 100 ) * 1e+6
2023-04-21 23:54:20 -06:00
exit_code = nagios . OK
critical = [ ]
warn = [ ]
ok = [ ]
perf_data = [ ]
2023-05-06 15:55:46 -06:00
output_table = [
( ' Host ' , ' Interface ' , ' Rate In ' , ' Rate Out ' , ' Cumulative In ' , ' Cumulative Out ' , ' Connections ' , ' Status ' )
]
def check_b ( name , value ) :
nonlocal exit_code
if value > = crit_b_value :
critical . append ( ( name , filesize ( value ) ) )
2023-04-21 23:54:20 -06:00
exit_code = nagios . CRITICAL
2023-05-06 15:55:46 -06:00
return ' [CRITICAL] ' , nagios . CRITICAL
elif value > = warn_b_value :
warn . append ( ( name , filesize ( value ) ) )
2023-04-21 23:54:20 -06:00
exit_code = nagios . WARNING
2023-05-06 15:55:46 -06:00
return ' [WARNING] ' , nagios . WARNING
2023-04-21 23:54:20 -06:00
else :
2023-05-06 15:55:46 -06:00
ok . append ( ( name , filesize ( value ) ) )
return ' [OK] ' , nagios . OK
for name , data in check_result . items ( ) :
status = ' [OK] '
in_status , in_rc = check_b ( ' rate_in ' , data [ ' rate_in ' ] )
if in_rc > = exit_code :
status = in_status
2023-04-21 23:54:20 -06:00
2023-05-06 15:55:46 -06:00
out_status , out_rc = check_b ( ' rate_out ' , data [ ' rate_out ' ] )
if out_rc > = exit_code :
status = out_status
if data [ ' connections ' ] > = args . conn_critical > 0 :
critical . append ( ( ' connections ' , data [ ' connections ' ] ) )
exit_code = nagios . CRITICAL
status = ' [CRITICAL] '
elif data [ ' connections ' ] > = args . conn_warn > 0 :
warn . append ( ( ' connections ' , data [ ' connections ' ] ) )
exit_code = nagios . WARNING
status = ' [WARNING] '
else :
ok . append ( ( ' connections ' , data [ ' connections ' ] ) )
perf_data . append ( f ' \' { name } _rate_in \' = { int ( data [ " rate_in " ] ) } B; { warn_b_value } ; { crit_b_value } ;0; ' )
perf_data . append ( f ' \' { name } _rate_out \' = { int ( data [ " rate_out " ] ) } B; { warn_b_value } ; { crit_b_value } ;0; ' )
perf_data . append ( f ' \' { name } _cumulative_in \' = { int ( data [ " cumulative_in " ] ) } B; { warn_b_value } ; { crit_b_value } ;0; ' )
perf_data . append ( f ' \' { name } _cumulative_out \' = { int ( data [ " cumulative_out " ] ) } B; { warn_b_value } ; { crit_b_value } ;0; ' )
perf_data . append ( f ' \' { name } _connections \' = { int ( data [ " connections " ] ) } B; { warn_b_value } ; { crit_b_value } ;0; ' )
2023-05-26 17:06:27 -06:00
output_table . append ( ( args . host , name , filesize ( data [ ' rate_in ' ] ) , filesize ( data [ ' rate_out ' ] ) ,
filesize ( data [ ' cumulative_in ' ] ) , filesize ( data [ ' cumulative_out ' ] ) , data [ ' connections ' ] ,
status ) )
2023-05-06 15:55:46 -06:00
if len ( critical ) :
x = [ ' CRITICAL: ' ]
for i in critical :
x . append ( f ' { i [ 0 ] } : { i [ 1 ] } , ' )
print ( ' ' . join ( x ) . strip ( ' , ' ) )
if len ( warn ) :
x = [ ' WARN: ' ]
for i in warn :
x . append ( f ' { i [ 0 ] } : { i [ 1 ] } ' )
print ( ' ' . join ( x ) . strip ( ' , ' ) )
if not len ( warn ) and not len ( critical ) :
print ( f ' OK: bandwidth is below { args . bandwidth } Mbps. ' )
print ( list_to_markdown_table ( output_table , align = ' left ' , seperator = ' ! ' , borders = False ) )
2023-04-21 23:54:20 -06:00
print ( f ' | { " " . join ( perf_data ) } ' )
2023-04-21 23:54:20 -06:00
sys . exit ( exit_code )
if __name__ == " __main__ " :
try :
main ( )
except Exception as e :
print ( f ' UNKNOWN: exception " { e } " ' )
print ( traceback . format_exc ( ) )
sys . exit ( nagios . UNKNOWN )